File › Options is now a pane list (General, Display, Proofing, Save, Language, Accessibility, Advanced, Agents), one function per pane in crates/ui-egui/src/options.rs. Every setting shown is wired: - General: theme, interface size (#475), mini toolbar on selection, dark page, community button, user name and initials (comments use the initials). - Display: tab characters, spaces, paragraph marks and hidden text shown with Show/Hide ¶ off; rulers; print the page colour; update fields before printing. - Proofing: ignore words in capitals / with numbers / web and file addresses (proof::Options, passed through layout), check spelling and mark grammar as you type, custom dictionary add/remove (kept in ui.json). - Save: AutoSave. Language: interface language. Accessibility: Read Aloud speed and citations. Advanced: typing replaces selection, Insert key overtype, overtype (text.overtype), recent documents count, measurement units (dialogs, Layout ribbon indents and rulers). file.options reads and sets the engine-side settings headlessly and only opens the page when given no setting. ui.json loads leniently: a bad value falls back to its default instead of dropping every setting. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
88a45329b2
commit
2e5ecb916b
|
|
@ -131,6 +131,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
|||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-11 | File › Options in Word's panes (#485): General, Display, Proofing, Save, Language, Accessibility, Advanced, Agents; initials, individual formatting marks, spelling ignore options, custom dictionary, overtype, typing replaces selection, measurement units, recent-documents count, print options; `file.options` reads and sets them headlessly |
|
||||
| 2026-10-11 | Right-click Wrap Text submenu on pictures, shapes and text boxes: wrapping styles, Move with Text / Fix Position on Page (#513) |
|
||||
| 2026-10-11 | Paragraph dialog options (#512): mirror indents (`w:mirrorIndents`, laid out on even pages), same-style spacing, suppress line numbers, don't hyphenate |
|
||||
| 2026-10-11 | Screen readers can read the document canvas (#488): paragraphs, headings, lists, tables, links, pictures and the caret exposed through AccessKit |
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ fn load_prefs(app: &mut WordApp) {
|
|||
}
|
||||
if let Some(p) = prefs_path()
|
||||
&& let Ok(bytes) = std::fs::read(&p)
|
||||
&& let Ok(ui) = serde_json::from_slice::<UiState>(&bytes)
|
||||
&& let Some(ui) = UiState::from_json(&bytes)
|
||||
{
|
||||
app.apply_prefs(ui);
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ fn saved_window() -> Option<WindowGeometry> {
|
|||
return None;
|
||||
}
|
||||
let bytes = std::fs::read(prefs_path()?).ok()?;
|
||||
serde_json::from_slice::<UiState>(&bytes).ok()?.window?.sanitized()
|
||||
UiState::from_json(&bytes)?.window?.sanitized()
|
||||
}
|
||||
|
||||
fn save_prefs(app: &WordApp) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub const MAX_AUTHOR_CHARS: usize = 255;
|
|||
/// Sets the user name that new tracked changes and comments are recorded under. The name is
|
||||
/// trimmed, stripped of control characters (they can't go into the saved XML) and cut to
|
||||
/// [`MAX_AUTHOR_CHARS`]; a blank name is refused so revisions always carry an author.
|
||||
fn set_author(s: &mut Session, v: &Value) -> CmdResult {
|
||||
pub(crate) fn set_author(s: &mut Session, v: &Value) -> CmdResult {
|
||||
let name: String = p::req_str(v, "name")?.trim().chars().filter(|c| !c.is_control()).take(MAX_AUTHOR_CHARS).collect();
|
||||
let name = name.trim_end();
|
||||
if name.is_empty() {
|
||||
|
|
@ -69,11 +69,6 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
CommandSpec::new("file.properties", "Properties", "File › Info", properties)
|
||||
.params(r#"{"title"?, "subject"?, "author"?, "keywords"?, "comments"?, "category"?, "custom"?: {name: string | null (removes)}}"#),
|
||||
CommandSpec::new("file.info", "Info", "File", info).pure(),
|
||||
CommandSpec::new("file.options", "Options", "File", |s, _| {
|
||||
s.ui_requests.push(json!({"open": "options"}));
|
||||
sel_result(s)
|
||||
})
|
||||
.pure(),
|
||||
CommandSpec::new("file.setAuthor", "User Name", "File › Options › General", set_author)
|
||||
.params(r#"{"name": string (author of new tracked changes and comments)}"#)
|
||||
.pure(),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub mod lists;
|
|||
pub mod mailings;
|
||||
pub mod moves;
|
||||
pub mod objects;
|
||||
pub mod options;
|
||||
pub mod page;
|
||||
pub mod para;
|
||||
pub mod paste;
|
||||
|
|
@ -68,6 +69,7 @@ pub fn registry() -> Registry {
|
|||
v.extend(table_draw::specs());
|
||||
v.extend(review::specs());
|
||||
v.extend(file::specs());
|
||||
v.extend(options::specs());
|
||||
v.extend(design::specs());
|
||||
v.extend(references::specs());
|
||||
v.extend(fields::specs());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
//! File › Options (#485): the settings agents can read and change without the window — user
|
||||
//! name and initials, typing (replace selection, overtype), measurement units, formatting marks
|
||||
//! and proofing. The Options page in the app sets them through the same command.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::{CmdError, CmdResult, CommandSpec, Prefs, Session, p};
|
||||
|
||||
/// Options panes, in the order the page lists them (the `pane` param of `file.options`).
|
||||
pub const PANES: [&str; 8] = ["general", "display", "proofing", "save", "language", "accessibility", "advanced", "agents"];
|
||||
|
||||
/// Longest initials kept, in characters.
|
||||
pub const MAX_INITIALS_CHARS: usize = 9;
|
||||
/// Longest word the custom dictionary takes, in characters.
|
||||
pub const MAX_WORD_CHARS: usize = 64;
|
||||
/// Most words one call may add or remove.
|
||||
const MAX_WORDS_PER_CALL: usize = 1000;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
CommandSpec::new("file.options", "Options", "File", options)
|
||||
.params(
|
||||
r#"{"pane"?: "general|display|proofing|save|language|accessibility|advanced|agents", "author"?: string, "initials"?: string, "units"?: "inches|centimeters|millimeters|points|picas", "typingReplacesSelection"?: bool, "insertKeyOvertype"?: bool, "overtype"?: bool, "marks"?: {"tabs"?: bool, "spaces"?: bool, "paragraphs"?: bool, "hidden"?: bool}, "checkSpelling"?: bool, "markGrammar"?: bool, "ignoreUppercase"?: bool, "ignoreNumbers"?: bool, "ignoreInternet"?: bool, "addWords"?: [string], "removeWords"?: [string]} — with any setting it changes them and opens nothing; without, it opens File › Options (at `pane`). Returns the settings."#,
|
||||
)
|
||||
.pure(),
|
||||
CommandSpec::new("text.overtype", "Overtype", "File › Options › Advanced", |s, v| {
|
||||
s.prefs.overtype = p::bool(v, "value").unwrap_or(!s.prefs.overtype);
|
||||
Ok(json!({"value": s.prefs.overtype}))
|
||||
})
|
||||
.params(r#"{"value"?: bool (default: toggle)}"#)
|
||||
.pure(),
|
||||
]
|
||||
}
|
||||
|
||||
/// The on/off settings by their `file.options` name.
|
||||
fn flags() -> [(&'static str, fn(&mut Prefs) -> &mut bool); 7] {
|
||||
[
|
||||
("typingReplacesSelection", |p| &mut p.typing_replaces_selection),
|
||||
("insertKeyOvertype", |p| &mut p.insert_key_overtype),
|
||||
("overtype", |p| &mut p.overtype),
|
||||
("markGrammar", |p| &mut p.mark_grammar),
|
||||
("ignoreUppercase", |p| &mut p.ignore_uppercase),
|
||||
("ignoreNumbers", |p| &mut p.ignore_numbers),
|
||||
("ignoreInternet", |p| &mut p.ignore_internet),
|
||||
]
|
||||
}
|
||||
|
||||
fn mark_flags() -> [(&'static str, fn(&mut wordcraft_layout::display::FormattingMarks) -> &mut bool); 4] {
|
||||
[("tabs", |m| &mut m.tabs), ("spaces", |m| &mut m.spaces), ("paragraphs", |m| &mut m.paragraphs), ("hidden", |m| &mut m.hidden)]
|
||||
}
|
||||
|
||||
fn bool_param(v: &Value, k: &str) -> Result<Option<bool>, CmdError> {
|
||||
match v.get(k) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(Value::Bool(b)) => Ok(Some(*b)),
|
||||
Some(_) => Err(CmdError::Params(format!("`{k}` must be true or false"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Words for the custom dictionary: trimmed, single words, at most [`MAX_WORD_CHARS`] long.
|
||||
fn words_param(v: &Value, k: &str) -> Result<Vec<String>, CmdError> {
|
||||
let Some(x) = v.get(k).filter(|x| !x.is_null()) else { return Ok(Vec::new()) };
|
||||
let bad = || CmdError::Params(format!("`{k}` must be a list of words"));
|
||||
let list = x.as_array().ok_or_else(bad)?;
|
||||
if list.len() > MAX_WORDS_PER_CALL {
|
||||
return Err(CmdError::Params(format!("`{k}` takes at most {MAX_WORDS_PER_CALL} words")));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for w in list {
|
||||
let w = w.as_str().ok_or_else(bad)?.trim();
|
||||
if w.is_empty() || w.chars().count() > MAX_WORD_CHARS || w.chars().any(|c| c.is_whitespace() || c.is_control()) {
|
||||
return Err(CmdError::Params(format!("`{k}`: {w:?} isn't a single word of at most {MAX_WORD_CHARS} characters")));
|
||||
}
|
||||
out.push(w.to_string());
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn options(s: &mut Session, v: &Value) -> CmdResult {
|
||||
let pane = match p::str(v, "pane") {
|
||||
Some(name) if PANES.contains(&name) => name,
|
||||
Some(name) => return Err(CmdError::Params(format!("unknown pane `{name}`; one of {}", PANES.join(", ")))),
|
||||
None => "general",
|
||||
};
|
||||
// Read every setting first, so a bad one changes nothing.
|
||||
let author = match v.get("author") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(Value::String(n)) => Some(n.clone()),
|
||||
Some(_) => return Err(CmdError::Params("`author` must be a string".into())),
|
||||
};
|
||||
let initials = match v.get("initials") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(Value::String(i)) => Some(i.trim().chars().filter(|c| !c.is_control()).take(MAX_INITIALS_CHARS).collect::<String>()),
|
||||
Some(_) => return Err(CmdError::Params("`initials` must be a string".into())),
|
||||
};
|
||||
let units = match v.get("units") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(u) => Some(
|
||||
serde_json::from_value::<wordcraft_geom::Unit>(u.clone())
|
||||
.map_err(|_| CmdError::Params("`units` is one of inches, centimeters, millimeters, points, picas".into()))?,
|
||||
),
|
||||
};
|
||||
let mut flag_values = Vec::new();
|
||||
for (k, f) in flags() {
|
||||
if let Some(b) = bool_param(v, k)? {
|
||||
flag_values.push((f, b));
|
||||
}
|
||||
}
|
||||
let mut mark_values = Vec::new();
|
||||
match v.get("marks") {
|
||||
None | Some(Value::Null) => {}
|
||||
Some(m @ Value::Object(_)) => {
|
||||
for (k, f) in mark_flags() {
|
||||
if let Some(b) = bool_param(m, k)? {
|
||||
mark_values.push((f, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => return Err(CmdError::Params("`marks` must be an object of true/false".into())),
|
||||
}
|
||||
let check_spelling = bool_param(v, "checkSpelling")?;
|
||||
let add = words_param(v, "addWords")?;
|
||||
let remove = words_param(v, "removeWords")?;
|
||||
|
||||
let setting = author.is_some()
|
||||
|| initials.is_some()
|
||||
|| units.is_some()
|
||||
|| !flag_values.is_empty()
|
||||
|| !mark_values.is_empty()
|
||||
|| check_spelling.is_some()
|
||||
|| !add.is_empty()
|
||||
|| !remove.is_empty();
|
||||
if let Some(name) = author {
|
||||
super::file::set_author(s, &json!({"name": name}))?;
|
||||
}
|
||||
if let Some(i) = initials {
|
||||
s.initials = i;
|
||||
}
|
||||
if let Some(u) = units {
|
||||
s.prefs.units = u;
|
||||
}
|
||||
for (f, b) in flag_values {
|
||||
*f(&mut s.prefs) = b;
|
||||
}
|
||||
for (f, b) in mark_values {
|
||||
*f(&mut s.prefs.marks) = b;
|
||||
}
|
||||
if let Some(b) = check_spelling {
|
||||
s.view.proofing = b;
|
||||
}
|
||||
for w in &add {
|
||||
wordcraft_proof::add_word(w);
|
||||
}
|
||||
for w in &remove {
|
||||
wordcraft_proof::remove_word(w);
|
||||
}
|
||||
if setting {
|
||||
// Marks, hidden text and proofing change what is laid out.
|
||||
s.relayout();
|
||||
} else {
|
||||
s.ui_requests.push(json!({"open": "options", "pane": pane}));
|
||||
}
|
||||
let mut r = current(s);
|
||||
if let Some(o) = r.as_object_mut() {
|
||||
o.insert("opened".into(), json!(!setting));
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// The settings `file.options` reads and sets.
|
||||
pub fn current(s: &Session) -> Value {
|
||||
let pr = &s.prefs;
|
||||
json!({
|
||||
"author": s.author,
|
||||
"initials": s.user_initials(),
|
||||
"units": pr.units,
|
||||
"typingReplacesSelection": pr.typing_replaces_selection,
|
||||
"insertKeyOvertype": pr.insert_key_overtype,
|
||||
"overtype": pr.overtype,
|
||||
"marks": pr.marks,
|
||||
"checkSpelling": s.view.proofing,
|
||||
"markGrammar": pr.mark_grammar,
|
||||
"ignoreUppercase": pr.ignore_uppercase,
|
||||
"ignoreNumbers": pr.ignore_numbers,
|
||||
"ignoreInternet": pr.ignore_internet,
|
||||
"dictionary": wordcraft_proof::user_dictionary(),
|
||||
})
|
||||
}
|
||||
|
|
@ -120,7 +120,7 @@ fn new_comment(s: &mut Session, v: &Value) -> CmdResult {
|
|||
};
|
||||
let part = s.doc.add_part(PartKind::Comment, vec![para_block(Paragraph::with_text(&text, Default::default()))]);
|
||||
let id = s.doc.comments.keys().next_back().map(|k| k + 1).unwrap_or(0);
|
||||
let initials: String = s.author.split_whitespace().filter_map(|w| w.chars().next()).collect();
|
||||
let initials = s.user_initials();
|
||||
s.doc.comments.insert(id, Comment { author: s.author.clone(), initials, date: now_iso(), parent: None, resolved: false, part });
|
||||
let props = Default::default();
|
||||
s.doc.insert_object(&b, InlineObject::CommentEnd { id }, &props)?;
|
||||
|
|
@ -137,7 +137,7 @@ fn reply(s: &mut Session, v: &Value) -> CmdResult {
|
|||
let text = p::req_str(v, "text")?;
|
||||
let part = s.doc.add_part(PartKind::Comment, vec![para_block(Paragraph::with_text(text, Default::default()))]);
|
||||
let id = s.doc.comments.keys().next_back().map(|k| k + 1).unwrap_or(0);
|
||||
let initials: String = s.author.split_whitespace().filter_map(|w| w.chars().next()).collect();
|
||||
let initials = s.user_initials();
|
||||
s.doc.comments.insert(id, Comment { author: s.author.clone(), initials, date: now_iso(), parent: Some(parent), resolved: false, part });
|
||||
Ok(json!({"id": id}))
|
||||
}
|
||||
|
|
@ -658,7 +658,7 @@ fn word_count(s: &mut Session, v: &Value) -> CmdResult {
|
|||
fn para_issues(s: &Session, story: StoryRef, path: &wordcraft_doc::Path) -> Vec<(Pos, Pos, wordcraft_proof::Issue)> {
|
||||
let Some(p) = s.doc.para(story, path) else { return Vec::new() };
|
||||
let text = wordcraft_layout::para::proof_text(p);
|
||||
let mut v: Vec<wordcraft_proof::Issue> = wordcraft_proof::check_spelling(&text);
|
||||
let mut v: Vec<wordcraft_proof::Issue> = wordcraft_proof::check_spelling_with(&text, &s.prefs.spelling());
|
||||
v.extend(wordcraft_proof::check_grammar(&text));
|
||||
v.sort_by_key(|i| i.start);
|
||||
v.into_iter()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Typing and deleting.
|
||||
|
||||
use serde_json::Value;
|
||||
use wordcraft_doc::para::{COLUMN_BREAK, NB_HYPHEN, NBSP, PAGE_BREAK, SOFT_HYPHEN};
|
||||
use wordcraft_doc::para::{COLUMN_BREAK, NB_HYPHEN, NBSP, OBJ, PAGE_BREAK, SOFT_HYPHEN};
|
||||
use wordcraft_doc::props::{Border, BorderStyle, NumRef};
|
||||
use wordcraft_doc::{Block, ListKind, Pos};
|
||||
|
||||
|
|
@ -60,9 +60,35 @@ fn autoformat(s: &mut Session, text: &str) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// File › Options › Advanced before typed text goes in: with "Typing replaces selected text"
|
||||
/// off the text goes in front of the selection and leaves it; in overtype the text replaces as
|
||||
/// many characters after the caret as it has, up to the paragraph's end or an object (picture,
|
||||
/// field), which stay.
|
||||
fn typing_options(s: &mut Session, text: &str) {
|
||||
if !s.sel.is_collapsed() {
|
||||
if !s.prefs.typing_replaces_selection {
|
||||
let (start, _) = s.sel.ordered();
|
||||
s.sel = Selection::caret(start);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if !s.prefs.overtype || text.contains(['\r', '\n', '\u{2029}', '\u{000B}']) {
|
||||
return;
|
||||
}
|
||||
let f = s.sel.focus.clone();
|
||||
let Some(rest) = s.doc.para_at(&f).and_then(|p| p.text.get(f.off..)) else { return };
|
||||
let n = text.chars().count();
|
||||
let len: usize = rest.chars().take(n).take_while(|c| *c != OBJ).map(char::len_utf8).sum();
|
||||
if len > 0 {
|
||||
// The caret (focus) stays where typing happens; the characters after it are selected.
|
||||
s.sel = Selection { anchor: Pos { off: f.off + len, ..f.clone() }, focus: f };
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(s: &mut Session, v: &Value) -> CmdResult {
|
||||
let text = p::req_str(v, "text")?;
|
||||
let raw = p::bool(v, "raw").unwrap_or(false);
|
||||
typing_options(s, text);
|
||||
let t = if raw { None } else { autoformat(s, text) };
|
||||
type_text(s, t.as_deref().unwrap_or(text))?;
|
||||
let trigger = text.chars().count() == 1 && text.chars().all(|c| c == ' ' || ",.;:!?".contains(c));
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub use ribbon::RibbonLayout;
|
|||
pub use session::{ColumnBlock, EditSnapshot, FindState, MathEdit, Prefs, Selection, Session, ViewState};
|
||||
pub use wordcraft_doc as doc;
|
||||
pub use wordcraft_layout as layout;
|
||||
pub use wordcraft_proof as proof;
|
||||
pub use wordcraft_render as render;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -225,6 +225,8 @@ pub struct Session {
|
|||
pub registry: Arc<Registry>,
|
||||
/// Author name for comments and tracked changes.
|
||||
pub author: String,
|
||||
/// Initials for comments (File › Options › General); empty = the name's initials.
|
||||
pub initials: String,
|
||||
/// Format painter: copied formatting waiting to be applied (and whether it stays on).
|
||||
pub painter: Option<(CharProps, wordcraft_doc::props::ParaProps, bool)>,
|
||||
/// Last message for the status bar / agents.
|
||||
|
|
@ -308,6 +310,7 @@ pub struct MathEdit {
|
|||
}
|
||||
|
||||
/// Editing preferences that persist between runs (the front end saves and restores them).
|
||||
/// File › Options sets most of them (`file.options`).
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct Prefs {
|
||||
|
|
@ -318,6 +321,21 @@ pub struct Prefs {
|
|||
pub markup: wordcraft_layout::display::MarkupOptions,
|
||||
/// Language › Detect language automatically (per user, as in Word).
|
||||
pub detect_language: bool,
|
||||
/// Typing over a selection replaces it; off, the typed text goes in front of it.
|
||||
pub typing_replaces_selection: bool,
|
||||
/// The Insert key switches overtype on and off.
|
||||
pub insert_key_overtype: bool,
|
||||
/// Overtype: typed characters replace the ones after the caret.
|
||||
pub overtype: bool,
|
||||
/// The unit rulers and dialogs show lengths in.
|
||||
pub units: wordcraft_geom::Unit,
|
||||
/// Formatting marks shown even while Show/Hide ¶ is off.
|
||||
pub marks: wordcraft_layout::display::FormattingMarks,
|
||||
/// Mark grammar errors as you type (with Check spelling as you type on).
|
||||
pub mark_grammar: bool,
|
||||
pub ignore_uppercase: bool,
|
||||
pub ignore_numbers: bool,
|
||||
pub ignore_internet: bool,
|
||||
/// AutoCorrect Options: replacements, capitalization and AutoFormat As You Type (per user, as
|
||||
/// in Word). A damaged saved value reads as the defaults.
|
||||
#[serde(deserialize_with = "crate::cmd::tools::lenient_autocorrect")]
|
||||
|
|
@ -326,7 +344,32 @@ pub struct Prefs {
|
|||
|
||||
impl Default for Prefs {
|
||||
fn default() -> Self {
|
||||
Prefs { count_notes: true, markup: Default::default(), detect_language: true, autocorrect: Default::default() }
|
||||
Prefs {
|
||||
count_notes: true,
|
||||
markup: Default::default(),
|
||||
typing_replaces_selection: true,
|
||||
insert_key_overtype: false,
|
||||
overtype: false,
|
||||
units: Default::default(),
|
||||
marks: Default::default(),
|
||||
mark_grammar: true,
|
||||
ignore_uppercase: true,
|
||||
ignore_numbers: true,
|
||||
ignore_internet: true,
|
||||
detect_language: true,
|
||||
autocorrect: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prefs {
|
||||
/// What the spelling check skips.
|
||||
pub fn spelling(&self) -> wordcraft_proof::Options {
|
||||
wordcraft_proof::Options {
|
||||
ignore_uppercase: self.ignore_uppercase,
|
||||
ignore_numbers: self.ignore_numbers,
|
||||
ignore_internet: self.ignore_internet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,6 +395,7 @@ impl Session {
|
|||
page_hint: 0,
|
||||
registry: Arc::new(crate::cmd::registry()),
|
||||
author: "WordCraft User".into(),
|
||||
initials: String::new(),
|
||||
painter: None,
|
||||
status: String::new(),
|
||||
history: Vec::new(),
|
||||
|
|
@ -452,6 +496,12 @@ impl Session {
|
|||
self.find.highlights.as_ref().map(|(_, v)| v.as_slice()).unwrap_or(&[])
|
||||
}
|
||||
|
||||
/// Initials for new comments: the ones set in File › Options, else the user name's.
|
||||
pub fn user_initials(&self) -> String {
|
||||
let set = self.initials.trim();
|
||||
if set.is_empty() { self.author.split_whitespace().filter_map(|w| w.chars().next()).collect() } else { set.to_string() }
|
||||
}
|
||||
|
||||
/// The current layout (recomputed when the document or view changed).
|
||||
pub fn layout(&mut self) -> Arc<DocLayout> {
|
||||
let ww = self.view.web_width;
|
||||
|
|
@ -466,10 +516,11 @@ impl Session {
|
|||
let opts = LayoutOptions {
|
||||
view: self.view.mode,
|
||||
web_width: ww,
|
||||
show_hidden: self.view.marks,
|
||||
show_hidden: self.view.marks || self.prefs.marks.hidden,
|
||||
hide_deleted: !self.view.show_markup || self.prefs.markup.hides_deletions(),
|
||||
proofing: self.view.proofing,
|
||||
field_codes: self.view.field_codes,
|
||||
proof: wordcraft_layout::ProofOptions { grammar: self.prefs.mark_grammar, spelling: self.prefs.spelling() },
|
||||
};
|
||||
let l = Arc::new(wordcraft_layout::layout(&self.doc, &mut self.cache, &opts));
|
||||
self.layout = Some((self.rev, ww, self.view.mode, l.clone(), self.view.proofing));
|
||||
|
|
@ -478,7 +529,7 @@ impl Session {
|
|||
/// A layout for output (PDF, images, print): no proofing marks, print view.
|
||||
pub fn export_layout(&self) -> Arc<DocLayout> {
|
||||
let opts =
|
||||
LayoutOptions { view: ViewMode::Print, web_width: 0.0, show_hidden: false, hide_deleted: false, proofing: false, field_codes: false };
|
||||
LayoutOptions { view: ViewMode::Print, web_width: 0.0, show_hidden: false, hide_deleted: false, proofing: false, ..Default::default() };
|
||||
Arc::new(wordcraft_layout::layout(&self.doc, &mut LayoutCache::new(), &opts))
|
||||
}
|
||||
|
||||
|
|
@ -643,6 +694,7 @@ impl Session {
|
|||
page_hint: _,
|
||||
registry: _,
|
||||
author: _,
|
||||
initials: _,
|
||||
painter: _,
|
||||
status: _,
|
||||
history,
|
||||
|
|
|
|||
|
|
@ -540,6 +540,40 @@ fn shift_f5_goes_back_through_the_last_edits() {
|
|||
assert!(Session::new(wordcraft_doc::Document::new()).run("edit.goBack", &json!({})).is_err(), "nothing edited yet");
|
||||
}
|
||||
|
||||
/// File › Options › Advanced, "Typing replaces selected text" (#485): on (Word's default) the
|
||||
/// typed text replaces the selection; off, it goes in front of the selection and keeps it.
|
||||
#[test]
|
||||
fn typing_over_a_selection_follows_the_replace_selection_option() {
|
||||
let mut s = typed("One two");
|
||||
run(&mut s, "select.text", json!({"text": "two"}));
|
||||
typ(&mut s, "X");
|
||||
assert_eq!(s.doc.plain_text(StoryRef::Body), "One X");
|
||||
let mut s = typed("One two");
|
||||
run(&mut s, "file.options", json!({"typingReplacesSelection": false}));
|
||||
run(&mut s, "select.text", json!({"text": "two"}));
|
||||
typ(&mut s, "XY");
|
||||
assert_eq!(s.doc.plain_text(StoryRef::Body), "One XYtwo");
|
||||
}
|
||||
|
||||
/// Overtype (#485): typed characters replace the ones after the caret, never past the end of the
|
||||
/// paragraph; the Insert key toggles it only with "Use the Insert key to control overtype" on
|
||||
/// (the UI checks that option; `text.overtype` is the switch).
|
||||
#[test]
|
||||
fn overtype_replaces_the_characters_after_the_caret() {
|
||||
let mut s = typed("Abcdef\nXy");
|
||||
run(&mut s, "caret.set", json!({"pos": Pos::body(0, 1)}));
|
||||
run(&mut s, "text.overtype", json!({"value": true}));
|
||||
typ(&mut s, "XY");
|
||||
assert_eq!(s.doc.plain_text(StoryRef::Body), "AXYdef\nXy");
|
||||
run(&mut s, "caret.set", json!({"pos": Pos::body(0, 5)}));
|
||||
typ(&mut s, "123");
|
||||
assert_eq!(s.doc.plain_text(StoryRef::Body), "AXYde123\nXy", "the paragraph mark isn't typed over");
|
||||
run(&mut s, "text.overtype", json!({}));
|
||||
run(&mut s, "caret.set", json!({"pos": Pos::body(0, 0)}));
|
||||
typ(&mut s, "Z");
|
||||
assert_eq!(s.doc.plain_text(StoryRef::Body), "ZAXYde123\nXy", "insert mode again");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_all_takes_out_the_paragraph_breaks_a_tracked_paste_inserted() {
|
||||
// Issue #417: Paste's paragraph marks weren't tracked, so Reject All left the split.
|
||||
|
|
|
|||
|
|
@ -152,10 +152,40 @@ impl Draw {
|
|||
}
|
||||
}
|
||||
|
||||
/// Formatting marks shown even while Show/Hide ¶ is off (File › Options › Display, "Always show
|
||||
/// these formatting marks"). Show/Hide ¶ on shows them all.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct FormattingMarks {
|
||||
/// Tab characters (→).
|
||||
pub tabs: bool,
|
||||
/// Spaces (·).
|
||||
pub spaces: bool,
|
||||
/// Paragraph marks (¶), line breaks, page, column and section breaks.
|
||||
pub paragraphs: bool,
|
||||
/// Hidden text (shown with its dotted underline instead of left out).
|
||||
pub hidden: bool,
|
||||
}
|
||||
|
||||
impl FormattingMarks {
|
||||
pub const ALL: FormattingMarks = FormattingMarks { tabs: true, spaces: true, paragraphs: true, hidden: true };
|
||||
|
||||
/// The marks on screen: all of them with Show/Hide ¶ on, else the chosen ones.
|
||||
pub fn shown(self, show_all: bool) -> FormattingMarks {
|
||||
if show_all { FormattingMarks::ALL } else { self }
|
||||
}
|
||||
|
||||
pub fn any(self) -> bool {
|
||||
self.tabs || self.spaces || self.paragraphs || self.hidden
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DisplayOptions {
|
||||
/// Show formatting marks (¶).
|
||||
/// Show formatting marks (¶): Show/Hide ¶, every mark.
|
||||
pub marks: bool,
|
||||
/// Marks shown while `marks` is off (File › Options › Display).
|
||||
pub always: FormattingMarks,
|
||||
/// Dim headers/footers (editing the body) or the body (editing a header/footer).
|
||||
pub dim_header: bool,
|
||||
pub dim_body: bool,
|
||||
|
|
@ -173,6 +203,7 @@ impl Default for DisplayOptions {
|
|||
fn default() -> Self {
|
||||
DisplayOptions {
|
||||
marks: false,
|
||||
always: FormattingMarks::default(),
|
||||
dim_header: true,
|
||||
dim_body: false,
|
||||
markup: true,
|
||||
|
|
@ -975,7 +1006,8 @@ fn lines(
|
|||
}
|
||||
}
|
||||
// Formatting marks.
|
||||
if opts.marks {
|
||||
let marks = opts.always.shown(opts.marks);
|
||||
if marks.any() {
|
||||
let msize = pl.styles.first().map(|s| s.size).unwrap_or(11.0);
|
||||
for k in line.c0..line.c1 {
|
||||
let Some(c) = pl.clusters.get(k) else { continue };
|
||||
|
|
@ -984,14 +1016,14 @@ fn lines(
|
|||
let size = pl.styles.get(c.style as usize).map(|s| s.size).unwrap_or(msize);
|
||||
let arrow = if line.rtl { '←' } else { '→' };
|
||||
match c.kind {
|
||||
ClKind::Space => {
|
||||
ClKind::Space if marks.spaces => {
|
||||
out.push(Draw::Mark { x: (cx + nx) / 2.0 - size * 0.12, baseline: base - size * 0.08, size, ch: '·', color: None })
|
||||
}
|
||||
ClKind::Tab => {
|
||||
ClKind::Tab if marks.tabs => {
|
||||
out.push(Draw::Mark { x: cx + ((nx - cx) / 2.0 - size * 0.3).max(0.0), baseline: base, size, ch: arrow, color: None })
|
||||
}
|
||||
ClKind::LineBreak => out.push(Draw::Mark { x: cx + 1.0, baseline: base, size, ch: '↵', color: None }),
|
||||
ClKind::PageBreak | ClKind::ColumnBreak => {
|
||||
ClKind::LineBreak if marks.paragraphs => out.push(Draw::Mark { x: cx + 1.0, baseline: base, size, ch: '↵', color: None }),
|
||||
ClKind::PageBreak | ClKind::ColumnBreak if marks.paragraphs => {
|
||||
let label = if c.kind == ClKind::PageBreak { '⤓' } else { '⇥' };
|
||||
out.push(Draw::Line {
|
||||
x0: cx + 2.0,
|
||||
|
|
@ -1008,7 +1040,7 @@ fn lines(
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
if line.end == LineEnd::Para {
|
||||
if line.end == LineEnd::Para && marks.paragraphs {
|
||||
// The mark follows the line's visual end, past every glyph, even where the
|
||||
// logically last text runs against the paragraph's direction.
|
||||
let ex = x + line.visual_end_x();
|
||||
|
|
|
|||
|
|
@ -55,11 +55,27 @@ pub struct LayoutOptions {
|
|||
pub show_hidden: bool,
|
||||
/// Final text ("No Markup"): tracked deletions take no space and draw nothing.
|
||||
pub hide_deleted: bool,
|
||||
/// Check spelling and grammar (squiggles).
|
||||
/// Check spelling as you type (squiggles).
|
||||
pub proofing: bool,
|
||||
/// Show field codes (`{ PAGE }`, shaded) instead of field results (View › Field Codes, Alt+F9).
|
||||
/// A field whose own `code` flag is set shows the other one.
|
||||
pub field_codes: bool,
|
||||
/// What the spelling check skips, and whether grammar is marked too (File › Options › Proofing).
|
||||
pub proof: ProofOptions,
|
||||
}
|
||||
|
||||
/// File › Options › Proofing choices that change the squiggles.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ProofOptions {
|
||||
/// Mark grammar errors as you type.
|
||||
pub grammar: bool,
|
||||
pub spelling: wordcraft_proof::Options,
|
||||
}
|
||||
|
||||
impl Default for ProofOptions {
|
||||
fn default() -> Self {
|
||||
ProofOptions { grammar: true, spelling: wordcraft_proof::Options::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Something placed on a page (page coordinates, points, y down).
|
||||
|
|
@ -362,7 +378,8 @@ fn env_hash(doc: &Document, opts: &LayoutOptions) -> u64 {
|
|||
opts.hide_deleted,
|
||||
opts.proofing,
|
||||
opts.field_codes,
|
||||
wordcraft_proof::user_dictionary().len(),
|
||||
opts.proof,
|
||||
wordcraft_proof::dictionary_generation(),
|
||||
hyphenation_key(doc),
|
||||
))
|
||||
}
|
||||
|
|
@ -495,7 +512,7 @@ impl Ctx<'_> {
|
|||
field_codes: false,
|
||||
hide_deleted: false,
|
||||
table: None,
|
||||
proofing: false,
|
||||
proofing: None,
|
||||
exclusions: &[],
|
||||
eq_number: 0,
|
||||
grid: para::Grid::default(),
|
||||
|
|
@ -563,7 +580,7 @@ impl Ctx<'_> {
|
|||
field_codes: self.opts.field_codes,
|
||||
hide_deleted: self.opts.hide_deleted,
|
||||
table,
|
||||
proofing: self.opts.proofing,
|
||||
proofing: self.opts.proofing.then_some(self.opts.proof),
|
||||
exclusions,
|
||||
eq_number,
|
||||
grid: self.grid,
|
||||
|
|
@ -2554,7 +2571,7 @@ fn next_first_line(ctx: &mut Ctx, block: usize, width: f32) -> f32 {
|
|||
field_codes: ctx.opts.field_codes,
|
||||
hide_deleted: ctx.opts.hide_deleted,
|
||||
table: None,
|
||||
proofing: false,
|
||||
proofing: None,
|
||||
exclusions: &[],
|
||||
eq_number: ctx.eq_count,
|
||||
grid: ctx.grid,
|
||||
|
|
|
|||
|
|
@ -316,7 +316,8 @@ pub struct ParaEnv<'a> {
|
|||
pub hide_deleted: bool,
|
||||
/// The table style's formatting for text in this paragraph's cell (`None` outside tables).
|
||||
pub table: Option<&'a CellText>,
|
||||
pub proofing: bool,
|
||||
/// Check spelling (and grammar, as the options say) for squiggles; `None` = no squiggles.
|
||||
pub proofing: Option<crate::ProofOptions>,
|
||||
/// Areas text must flow around (floating objects), relative to the paragraph: x from the
|
||||
/// column's left edge, y from the top of the first line.
|
||||
pub exclusions: &'a [Exclusion],
|
||||
|
|
@ -1258,7 +1259,7 @@ pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout {
|
|||
text_len: p.text.len(),
|
||||
has_page_fields,
|
||||
notes,
|
||||
issues: if env.proofing { proof_issues(p) } else { Vec::new() },
|
||||
issues: env.proofing.map(|o| proof_issues(p, &o)).unwrap_or_default(),
|
||||
drop_cap,
|
||||
hyph_after: Vec::new(),
|
||||
bidi_levels: b.levels,
|
||||
|
|
@ -2225,7 +2226,7 @@ impl ParaLayout {
|
|||
}
|
||||
|
||||
/// Spelling and grammar issues in a paragraph (skipping "do not check" and hidden runs).
|
||||
fn proof_issues(p: &Paragraph) -> Vec<(usize, usize, bool)> {
|
||||
fn proof_issues(p: &Paragraph, opts: &crate::ProofOptions) -> Vec<(usize, usize, bool)> {
|
||||
if p.text.trim().is_empty() || p.text.len() > 100_000 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
|
@ -2233,9 +2234,14 @@ fn proof_issues(p: &Paragraph) -> Vec<(usize, usize, bool)> {
|
|||
let skip = |a: usize, b: usize| {
|
||||
p.run_ranges().any(|(r, c)| r.start < b && a < r.end && (c.no_proof == Some(true) || c.hidden == Some(true) || c.link.is_some()))
|
||||
};
|
||||
let mut v: Vec<(usize, usize, bool)> =
|
||||
wordcraft_proof::check_spelling(&text).into_iter().filter(|i| !skip(i.start, i.end)).map(|i| (i.start, i.end, false)).collect();
|
||||
v.extend(wordcraft_proof::check_grammar(&text).into_iter().filter(|i| !skip(i.start, i.end)).map(|i| (i.start, i.end, true)));
|
||||
let mut v: Vec<(usize, usize, bool)> = wordcraft_proof::check_spelling_with(&text, &opts.spelling)
|
||||
.into_iter()
|
||||
.filter(|i| !skip(i.start, i.end))
|
||||
.map(|i| (i.start, i.end, false))
|
||||
.collect();
|
||||
if opts.grammar {
|
||||
v.extend(wordcraft_proof::check_grammar(&text).into_iter().filter(|i| !skip(i.start, i.end)).map(|i| (i.start, i.end, true)));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ pub fn measure_table_columns(doc: &wordcraft_doc::Document, t: &Table) -> Vec<(f
|
|||
field_codes: false,
|
||||
hide_deleted: false,
|
||||
table: text.as_ref(),
|
||||
proofing: false,
|
||||
proofing: None,
|
||||
exclusions: &[],
|
||||
eq_number: 0,
|
||||
grid: crate::para::Grid::default(),
|
||||
|
|
|
|||
|
|
@ -867,7 +867,7 @@ fn web_view_is_one_page() {
|
|||
let l = layout(
|
||||
&d,
|
||||
&mut c,
|
||||
&LayoutOptions { view: ViewMode::Web, web_width: 800.0, show_hidden: false, hide_deleted: false, proofing: false, field_codes: false },
|
||||
&LayoutOptions { view: ViewMode::Web, web_width: 800.0, show_hidden: false, hide_deleted: false, proofing: false, ..Default::default() },
|
||||
);
|
||||
assert_eq!(l.pages.len(), 1);
|
||||
assert!(l.pages[0].h > 800.0);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,26 @@ pub mod hyphen;
|
|||
pub mod patterns;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
|
||||
/// What the spelling check skips (File › Options › Proofing). Word's defaults: all on.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Options {
|
||||
/// Words in capitals (`NASA`, `WORDCRAFT`) are taken as correct.
|
||||
pub ignore_uppercase: bool,
|
||||
/// Words containing digits (`B2B`, `mp3`) are taken as correct.
|
||||
pub ignore_numbers: bool,
|
||||
/// Web and email addresses and file paths (`https://…`, `a@b.org`, `www.…`) are skipped.
|
||||
pub ignore_internet: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Options { ignore_uppercase: true, ignore_numbers: true, ignore_internet: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// A problem found in text: byte range, kind, message and suggestions.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Issue {
|
||||
|
|
@ -38,9 +56,28 @@ fn user_words() -> &'static RwLock<HashSet<String>> {
|
|||
U.get_or_init(|| RwLock::new(HashSet::new()))
|
||||
}
|
||||
|
||||
/// Bumped whenever the user dictionary changes, so cached proofing results can be dropped.
|
||||
static DICTIONARY_GENERATION: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Add a word to the user dictionary ("Add to Dictionary").
|
||||
pub fn add_word(w: &str) {
|
||||
user_words().write().unwrap_or_else(|e| e.into_inner()).insert(w.to_lowercase());
|
||||
if user_words().write().unwrap_or_else(|e| e.into_inner()).insert(w.to_lowercase()) {
|
||||
DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a word out of the user dictionary (File › Options › Proofing). Returns whether it was there.
|
||||
pub fn remove_word(w: &str) -> bool {
|
||||
let removed = user_words().write().unwrap_or_else(|e| e.into_inner()).remove(&w.to_lowercase());
|
||||
if removed {
|
||||
DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
/// A number that changes whenever a word is added to or removed from the user dictionary.
|
||||
pub fn dictionary_generation() -> u64 {
|
||||
DICTIONARY_GENERATION.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Words in the user dictionary.
|
||||
|
|
@ -124,15 +161,24 @@ fn known_core(w: &str) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
/// Is `word` spelled correctly? Numbers, single letters, ALL-CAPS acronyms, URLs and words with
|
||||
/// digits are accepted.
|
||||
/// Is `word` spelled correctly? Numbers, single letters, words in capitals and words with
|
||||
/// digits are accepted (Word's default [`Options`]).
|
||||
pub fn is_correct(word: &str) -> bool {
|
||||
is_correct_with(word, &Options::default())
|
||||
}
|
||||
|
||||
/// [`is_correct`] with the File › Options › Proofing choices.
|
||||
pub fn is_correct_with(word: &str, opts: &Options) -> bool {
|
||||
let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\'');
|
||||
let w = w.trim_matches('\'');
|
||||
if w.chars().count() <= 1 || w.chars().any(|c| c.is_ascii_digit()) || !w.chars().any(char::is_alphabetic) {
|
||||
if w.chars().count() <= 1 || !w.chars().any(char::is_alphabetic) {
|
||||
return true;
|
||||
}
|
||||
if w.chars().all(|c| !c.is_lowercase()) && w.chars().count() <= 6 {
|
||||
let digits = w.chars().any(|c| c.is_ascii_digit());
|
||||
if digits && opts.ignore_numbers {
|
||||
return true;
|
||||
}
|
||||
if opts.ignore_uppercase && w.chars().all(|c| !c.is_lowercase()) {
|
||||
return true;
|
||||
}
|
||||
if !w.is_ascii() && !w.chars().any(|c| c.is_ascii_alphabetic()) {
|
||||
|
|
@ -255,6 +301,11 @@ pub fn words(text: &str) -> Vec<(usize, usize)> {
|
|||
|
||||
/// Spelling issues in a paragraph of text (no suggestions; ask [`suggest`] on demand).
|
||||
pub fn check_spelling(text: &str) -> Vec<Issue> {
|
||||
check_spelling_with(text, &Options::default())
|
||||
}
|
||||
|
||||
/// [`check_spelling`] with the File › Options › Proofing choices.
|
||||
pub fn check_spelling_with(text: &str, opts: &Options) -> Vec<Issue> {
|
||||
let mut v = Vec::new();
|
||||
for (a, b) in words(text) {
|
||||
let Some(w) = text.get(a..b) else { continue };
|
||||
|
|
@ -262,10 +313,10 @@ pub fn check_spelling(text: &str) -> Vec<Issue> {
|
|||
let ts = text.get(..a).and_then(|t| t.rfind(char::is_whitespace)).map(|i| i + 1).unwrap_or(0);
|
||||
let te = text.get(b..).and_then(|t| t.find(char::is_whitespace)).map(|i| b + i).unwrap_or(text.len());
|
||||
let token = text.get(ts..te).unwrap_or("");
|
||||
if token.contains("://") || token.contains('@') || token.starts_with("www.") {
|
||||
if opts.ignore_internet && (token.contains("://") || token.contains('@') || token.starts_with("www.")) {
|
||||
continue;
|
||||
}
|
||||
if !is_correct(w) {
|
||||
if !is_correct_with(w, opts) {
|
||||
v.push(Issue { start: a, end: b, kind: IssueKind::Spelling, message: "Possible spelling mistake".into(), suggestions: Vec::new() });
|
||||
}
|
||||
}
|
||||
|
|
@ -472,6 +523,18 @@ mod tests {
|
|||
assert!(is_correct("zxqwordcrafty"));
|
||||
}
|
||||
|
||||
/// File › Options › Proofing (#485): each "Ignore" choice turned off makes those words count.
|
||||
#[test]
|
||||
fn ignore_options_decide_what_is_checked() {
|
||||
let t = "SENTENSE helo2 www.exampel.org";
|
||||
let bad = |o: Options| check_spelling_with(t, &o).iter().map(|i| t[i.start..i.end].to_string()).collect::<Vec<_>>();
|
||||
assert!(bad(Options::default()).is_empty(), "Word's defaults skip all three");
|
||||
let all = Options { ignore_uppercase: false, ignore_numbers: false, ignore_internet: false };
|
||||
assert_eq!(bad(all), ["SENTENSE", "helo2", "www", "exampel"]);
|
||||
assert_eq!(bad(Options { ignore_uppercase: false, ..Options::default() }), ["SENTENSE"]);
|
||||
assert!(is_correct_with("NASA", &all), "capitals that are a word stay correct");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hyphenation() {
|
||||
let l = hyphen::Limits::default();
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
|||
"open" => open_page(app, ui),
|
||||
"info" => info_page(app, ui),
|
||||
"print" | "export" => export_page(app, ui),
|
||||
"options" => options_page(app, ui),
|
||||
"options" => crate::options::page(app, ui),
|
||||
_ => home_page(app, ui),
|
||||
});
|
||||
});
|
||||
|
|
@ -240,7 +240,7 @@ fn open_list(app: &mut WordApp, ui: &mut Ui) {
|
|||
if app.ui.recent.is_empty() {
|
||||
ui.label(egui::RichText::new(tl!("Documents you open will show up here.")).color(t.text_dim));
|
||||
}
|
||||
for p in app.ui.recent.clone() {
|
||||
for p in app.ui.recent.iter().take(app.ui.recent_shown()).cloned().collect::<Vec<_>>() {
|
||||
let name = std::path::Path::new(&p).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or(p.clone());
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width().min(700.0), 40.0), Sense::click());
|
||||
if resp.hovered() {
|
||||
|
|
@ -365,129 +365,6 @@ fn export_page(app: &mut WordApp, ui: &mut Ui) {
|
|||
}
|
||||
}
|
||||
|
||||
fn options_page(app: &mut WordApp, ui: &mut Ui) {
|
||||
heading(ui, "Options");
|
||||
ui.label(egui::RichText::new(tl!("General")).font(semibold(15.0)));
|
||||
language_picker(app, ui);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("User name:"));
|
||||
let mut n = app.session.author.clone();
|
||||
if ui.text_edit_singleline(&mut n).changed() {
|
||||
app.session.author = n;
|
||||
}
|
||||
});
|
||||
theme_picker(app, ui);
|
||||
interface_size_picker(app, ui);
|
||||
// The browser can't write to the user's files, so AutoSave can't be turned on there (#176).
|
||||
let browser = app.autosave_block() == Some(crate::AutoSaveBlock::Browser);
|
||||
let mut autosave = app.autosave && !browser;
|
||||
let r = ui.add_enabled(!browser, egui::Checkbox::new(&mut autosave, tl!("AutoSave documents you have saved in WordCraft")));
|
||||
if browser {
|
||||
r.on_disabled_hover_text(crate::AutoSaveBlock::Browser.reason());
|
||||
} else if r.changed() {
|
||||
app.autosave = autosave;
|
||||
app.session.autosave = autosave;
|
||||
}
|
||||
let mut dark_page = app.session.view.dark_mode;
|
||||
if ui
|
||||
.checkbox(&mut dark_page, tl!("Dark page (white text on black)"))
|
||||
.on_hover_text(tl!("Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged."))
|
||||
.changed()
|
||||
{
|
||||
let _ = app.run("view.darkMode", json!({"value": dark_page}));
|
||||
}
|
||||
ui.checkbox(&mut app.ui.show_discord, tl!("Show the community button in the title bar"));
|
||||
ui.add_space(10.0);
|
||||
ui.label(egui::RichText::new(tl!("Display")).font(semibold(15.0)));
|
||||
let mut marks = app.session.view.marks;
|
||||
if ui.checkbox(&mut marks, tl!("Show all formatting marks")).changed() {
|
||||
let _ = app.run("view.marks", json!({"value": marks}));
|
||||
}
|
||||
let mut ruler = app.session.view.ruler;
|
||||
if ui.checkbox(&mut ruler, tl!("Show rulers")).changed() {
|
||||
let _ = app.run("view.ruler", json!({"value": ruler}));
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
ui.label(egui::RichText::new(tl!("Customize")).font(semibold(15.0)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Keyboard shortcuts:"));
|
||||
if ui.button(tl!("Customize…")).clicked() {
|
||||
let _ = app.run("tools.customizeKeyboard", json!({}));
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Ribbon and Quick Access Toolbar:"));
|
||||
if ui.button(tl!("Customize…")).clicked() {
|
||||
let _ = app.run("tools.customizeRibbon", json!({}));
|
||||
}
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
ui.label(egui::RichText::new(tl!("Agents")).font(semibold(15.0)));
|
||||
ui.label(tl!("Every command is available to scripts and AI agents: run `wordcraft-cli mcp` for an MCP server, or start the app with `--control <port>` for the JSON control channel."));
|
||||
}
|
||||
|
||||
/// File ▸ Options ▸ Interface theme: Light, Dark, or follow the system's appearance (#115).
|
||||
fn theme_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::theme::Appearance;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Interface theme:"));
|
||||
egui::ComboBox::from_id_salt("interface_theme").selected_text(tl!(app.ui.theme.label())).width(220.0).show_ui(ui, |ui| {
|
||||
for a in Appearance::ALL {
|
||||
if ui.selectable_label(app.ui.theme == a, tl!(a.label())).clicked() {
|
||||
let _ = app.run("ui.theme", json!({"value": a.code()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// File ▸ Options ▸ Interface size: 80–200 %, on top of the system's display scale (#475).
|
||||
fn interface_size_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::theme::InterfaceSize;
|
||||
let current = app.ui.interface_size.percent();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Interface size:"));
|
||||
egui::ComboBox::from_id_salt("interface_size").selected_text(format!("{current}%")).width(220.0).show_ui(ui, |ui| {
|
||||
for pct in InterfaceSize::PRESETS {
|
||||
if ui.selectable_label(current == pct, format!("{pct}%")).clicked() {
|
||||
let _ = app.run("ui.interfaceSize", json!({"percent": pct}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.label(
|
||||
egui::RichText::new(tl!(
|
||||
"Makes the ribbon, menus and dialogs larger or smaller, on top of your system's display scaling. Document zoom is separate."
|
||||
))
|
||||
.small()
|
||||
.weak(),
|
||||
);
|
||||
}
|
||||
|
||||
/// File ▸ Options ▸ Interface language: follow the system (the default) or pick one (#8).
|
||||
fn language_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::i18n::{AUTO, Lang};
|
||||
// The language names are in their own scripts: fonts for them load from the next frame.
|
||||
app.want_system_cjk = true;
|
||||
let system = crate::i18n::system_lang();
|
||||
let auto_label = crate::i18n::fmt(tl!("Automatic ({language})"), &[("language", system.name())]);
|
||||
let current = if app.ui.language == AUTO { auto_label.clone() } else { Lang::from_pref(&app.ui.language).name().to_string() };
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Interface language:"));
|
||||
egui::ComboBox::from_id_salt("interface_language").selected_text(current).width(220.0).show_ui(ui, |ui| {
|
||||
if ui.selectable_label(app.ui.language == AUTO, auto_label.as_str()).clicked() {
|
||||
let _ = app.run("ui.language", json!({"value": AUTO}));
|
||||
}
|
||||
for lang in Lang::all() {
|
||||
if ui.selectable_label(app.ui.language == lang.code(), lang.name()).clicked() {
|
||||
let _ = app.run("ui.language", json!({"value": lang.code()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Automatic follows your system's language. Menus and commands change; your documents don't.")).small().weak());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ fn page_key(app: &WordApp, page: &Page, scale_px: f32, dim_body: bool) -> u64 {
|
|||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
scale_px.to_bits().hash(&mut h);
|
||||
let v = &app.session.view;
|
||||
(v.marks, v.show_markup, v.dark_mode, wordcraft_render::DARK_PAPER, v.hide_ink, v.draw.replay).hash(&mut h);
|
||||
(v.marks, app.session.prefs.marks, v.show_markup, v.dark_mode, wordcraft_render::DARK_PAPER, v.hide_ink, v.draw.replay).hash(&mut h);
|
||||
app.session.prefs.markup.hash(&mut h);
|
||||
dim_body.hash(&mut h);
|
||||
format!("{:?}{:?}", app.session.doc.settings.page_color, app.session.doc.settings.watermark).hash(&mut h);
|
||||
|
|
@ -465,6 +465,7 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
|||
if !fresh && (rendered < 2 || !app.canvas.textures.contains_key(&i) && rendered < 4) {
|
||||
let mut opts = screen_render_options();
|
||||
opts.display.marks = app.session.view.marks;
|
||||
opts.display.always = app.session.prefs.marks;
|
||||
opts.display.placeholders = true;
|
||||
opts.display.markup = app.session.view.show_markup;
|
||||
// Ink Replay draws the ink itself, stroke by stroke, over pages without it.
|
||||
|
|
@ -765,7 +766,9 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
|||
if let (Some(h), Some(v)) = (hruler, vruler) {
|
||||
rulers(app, ui, h, v, &rects, &layout, geo.scale);
|
||||
}
|
||||
if let Some(sel) = app.canvas.mini_anchor {
|
||||
if let Some(sel) = app.canvas.mini_anchor
|
||||
&& app.ui.mini_toolbar
|
||||
{
|
||||
crate::mini_toolbar::show(app, ui.ctx(), sel, area);
|
||||
}
|
||||
}
|
||||
|
|
@ -1267,21 +1270,22 @@ fn rulers(app: &mut WordApp, ui: &mut Ui, h: Rect, v: Rect, rects: &[Rect], layo
|
|||
hp.rect_filled(bar, 0.0, t.ruler_margin);
|
||||
let text = Rect::from_min_max(pos2(x0 + page.body.x * scale, bar.min.y), pos2(x0 + page.body.right() * scale, bar.max.y));
|
||||
hp.rect_filled(text, 0.0, t.ruler);
|
||||
let unit = 72.0;
|
||||
// Graduations in the File › Options unit.
|
||||
let (unit, div, step) = ruler_scale(app.session.prefs.units);
|
||||
let origin = page.body.x;
|
||||
let mut k = -((origin / unit).ceil() as i32) * 8;
|
||||
let mut k = -((origin / unit).ceil() as i32) * div;
|
||||
loop {
|
||||
let xpt = origin + k as f32 * unit / 8.0;
|
||||
let xpt = origin + k as f32 * unit / div as f32;
|
||||
if xpt > page.w {
|
||||
break;
|
||||
}
|
||||
if xpt >= 0.0 {
|
||||
let sx = x0 + xpt * scale;
|
||||
let (len, label) = if k % 8 == 0 {
|
||||
(0.0, Some(k / 8))
|
||||
} else if k % 4 == 0 {
|
||||
let (len, label) = if k % div == 0 {
|
||||
(0.0, Some(k / div * step))
|
||||
} else if k % (div / 2) == 0 {
|
||||
(5.0, None)
|
||||
} else if k % 2 == 0 {
|
||||
} else if div >= 8 && k % (div / 4) == 0 {
|
||||
(3.0, None)
|
||||
} else {
|
||||
(1.5, None)
|
||||
|
|
@ -1292,7 +1296,7 @@ fn rulers(app: &mut WordApp, ui: &mut Ui, h: Rect, v: Rect, rects: &[Rect], layo
|
|||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
if scale * unit / 8.0 > 4.0 || k % 2 == 0 {
|
||||
if scale * unit / div as f32 > 4.0 || k % 2 == 0 {
|
||||
hp.line_segment([pos2(sx, bar.center().y - len / 2.0), pos2(sx, bar.center().y + len / 2.0)], Stroke::new(1.0, t.ruler_tick));
|
||||
}
|
||||
}
|
||||
|
|
@ -1410,15 +1414,21 @@ fn rulers(app: &mut WordApp, ui: &mut Ui, h: Rect, v: Rect, rects: &[Rect], layo
|
|||
margin_handle(app, ui, edge, zone, |p| (p.y - y0) / scale, §);
|
||||
}
|
||||
let origin = page.body.y;
|
||||
let mut k = -((origin / unit).ceil() as i32) * 8;
|
||||
let mut k = -((origin / unit).ceil() as i32) * div;
|
||||
loop {
|
||||
let ypt = origin + k as f32 * unit / 8.0;
|
||||
let ypt = origin + k as f32 * unit / div as f32;
|
||||
if ypt > page.h.min(20_000.0) {
|
||||
break;
|
||||
}
|
||||
if ypt >= 0.0 && k % 8 == 0 && k != 0 {
|
||||
vp.text(pos2(vbar.center().x, y0 + ypt * scale), egui::Align2::CENTER_CENTER, (k / 8).abs().to_string(), regular(9.5), t.ruler_tick);
|
||||
} else if ypt >= 0.0 && k % 4 == 0 {
|
||||
if ypt >= 0.0 && k % div == 0 && k != 0 {
|
||||
vp.text(
|
||||
pos2(vbar.center().x, y0 + ypt * scale),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
(k / div * step).abs().to_string(),
|
||||
regular(9.5),
|
||||
t.ruler_tick,
|
||||
);
|
||||
} else if ypt >= 0.0 && k % (div / 2) == 0 {
|
||||
let sy = y0 + ypt * scale;
|
||||
vp.line_segment([pos2(vbar.center().x - 2.5, sy), pos2(vbar.center().x + 2.5, sy)], Stroke::new(1.0, t.ruler_tick));
|
||||
}
|
||||
|
|
@ -1429,6 +1439,19 @@ fn rulers(app: &mut WordApp, ui: &mut Ui, h: Rect, v: Rect, rects: &[Rect], layo
|
|||
}
|
||||
}
|
||||
|
||||
/// A ruler's graduation for a unit: points per numbered step, divisions per step, and how much
|
||||
/// each step counts (millimetre rulers number every centimetre as 10, 20…).
|
||||
fn ruler_scale(u: wordcraft_geom::Unit) -> (f32, i32, i32) {
|
||||
use wordcraft_geom::Unit;
|
||||
match u {
|
||||
Unit::Centimeters => (wordcraft_geom::PT_PER_CM, 4, 1),
|
||||
Unit::Millimeters => (wordcraft_geom::PT_PER_CM, 4, 10),
|
||||
Unit::Points => (72.0, 8, 72),
|
||||
Unit::Picas => (72.0, 8, 6),
|
||||
Unit::Inches => (72.0, 8, 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// One edge of a page's text area on a ruler.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum MarginEdge {
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ pub fn title_bar(app: &mut WordApp, ui: &mut Ui) {
|
|||
// Account / community.
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(control_h, control_h), Sense::click());
|
||||
ui.painter().circle_filled(r.center(), control_h / 2.0 - 1.0, t.accent);
|
||||
let initials: String = app.session.author.split_whitespace().filter_map(|w| w.chars().next()).take(2).collect();
|
||||
let initials: String = app.session.user_initials().chars().take(2).collect();
|
||||
ui.painter().text(r.center(), Align2::CENTER_CENTER, initials, semibold(10.5), egui::Color32::WHITE);
|
||||
resp.on_hover_text(format!("{} — set your name in File › Options", app.session.author));
|
||||
ui.add_space(8.0);
|
||||
|
|
@ -295,6 +295,9 @@ pub fn status_bar(app: &mut WordApp, ui: &mut Ui) {
|
|||
if app.session.doc.settings.track_changes {
|
||||
st(ui, tl!("Track Changes: On"));
|
||||
}
|
||||
if app.session.prefs.overtype && st(ui, tl!("Overtype")).on_hover_text(tl!("Click to switch back to inserting")).clicked() {
|
||||
let _ = app.run("text.overtype", json!({"value": false}));
|
||||
}
|
||||
if let Some((msg, at)) = &app.status_msg
|
||||
&& crate::now_ms() - at < 6000.0
|
||||
{
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ impl TableForm {
|
|||
let s = &app.session;
|
||||
let (tp, r, c) = s.sel.focus.path.cell()?;
|
||||
let t = s.doc.table(s.sel.focus.story, &tp)?;
|
||||
let k = wordcraft_geom::Unit::default().pt_per_unit();
|
||||
let k = crate::options::unit().pt_per_unit();
|
||||
let row = t.rows.get(r).map(|x| x.props.clone()).unwrap_or_default();
|
||||
let cell = t.rows.get(r).and_then(|x| x.cells.get(c)).map(|x| x.props.clone()).unwrap_or_default();
|
||||
let tw = wordcraft_engine::cmd::page::sect(s).text_width();
|
||||
|
|
@ -402,7 +402,7 @@ impl TableForm {
|
|||
|
||||
/// `table.properties` parameters for what differs from `basis` (empty when nothing does).
|
||||
pub fn changes(&self, basis: &TableForm) -> Value {
|
||||
let k = wordcraft_geom::Unit::default().pt_per_unit();
|
||||
let k = crate::options::unit().pt_per_unit();
|
||||
let moved = |a: f32, b: f32| (a - b).abs() > 1e-4;
|
||||
let mut v = serde_json::Map::new();
|
||||
if self.width_on != basis.width_on || (self.width_on && moved(self.width, basis.width)) {
|
||||
|
|
@ -827,9 +827,9 @@ impl Dialog {
|
|||
Dialog::Paragraph {
|
||||
rtl: rp.bidi,
|
||||
align: format!("{:?}", rp.align.visual(rp.bidi)).to_lowercase(),
|
||||
left: vl / 72.0,
|
||||
right: vr / 72.0,
|
||||
first: rp.indent_first / 72.0,
|
||||
left: vl / crate::options::unit().pt_per_unit(),
|
||||
right: vr / crate::options::unit().pt_per_unit(),
|
||||
first: rp.indent_first / crate::options::unit().pt_per_unit(),
|
||||
before: rp.space_before,
|
||||
after: rp.space_after,
|
||||
line,
|
||||
|
|
@ -876,10 +876,10 @@ impl Dialog {
|
|||
let size = app.session.doc.styles.resolve_char(Some("Normal"), &Default::default()).size;
|
||||
let grid = (kind, sp.grid_lines_per_page().min(1000), sp.grid_chars_per_line(size).min(1000));
|
||||
Dialog::PageSetup {
|
||||
top: sp.margin_top / 72.0,
|
||||
bottom: sp.margin_bottom / 72.0,
|
||||
left: sp.margin_left / 72.0,
|
||||
right: sp.margin_right / 72.0,
|
||||
top: sp.margin_top / crate::options::unit().pt_per_unit(),
|
||||
bottom: sp.margin_bottom / crate::options::unit().pt_per_unit(),
|
||||
left: sp.margin_left / crate::options::unit().pt_per_unit(),
|
||||
right: sp.margin_right / crate::options::unit().pt_per_unit(),
|
||||
landscape: sp.landscape,
|
||||
grid,
|
||||
grid_was: grid,
|
||||
|
|
@ -1358,12 +1358,27 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
|||
let (l_label, r_label) = if opts[0] { ("Inside:", "Outside:") } else { ("Left:", "Right:") };
|
||||
egui::Grid::new("ind").num_columns(4).show(ui, |ui| {
|
||||
ui.label(tl!(l_label));
|
||||
ui.add(egui::DragValue::new(left).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(left)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.label(tl!(r_label));
|
||||
ui.add(egui::DragValue::new(right).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(right)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.end_row();
|
||||
ui.label(tl!("First line:"));
|
||||
ui.add(egui::DragValue::new(first).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(first)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.end_row();
|
||||
});
|
||||
ui.checkbox(&mut opts[0], tl!("Mirror indents"));
|
||||
|
|
@ -1572,14 +1587,34 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
|||
ui.label(egui::RichText::new(tl!("Margins")).font(semibold(12.5)));
|
||||
egui::Grid::new("ps").num_columns(4).show(ui, |ui| {
|
||||
ui.label(tl!("Top:"));
|
||||
ui.add(egui::DragValue::new(top).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(top)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.label(tl!("Bottom:"));
|
||||
ui.add(egui::DragValue::new(bottom).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(bottom)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.end_row();
|
||||
ui.label(tl!("Left:"));
|
||||
ui.add(egui::DragValue::new(left).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(left)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.label(tl!("Right:"));
|
||||
ui.add(egui::DragValue::new(right).speed(0.05).suffix("\"").max_decimals(2));
|
||||
ui.add(
|
||||
egui::DragValue::new(right)
|
||||
.speed(3.6 / crate::options::unit().pt_per_unit())
|
||||
.suffix(crate::options::unit().suffix())
|
||||
.max_decimals(2),
|
||||
);
|
||||
ui.end_row();
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("Orientation")).font(semibold(12.5)));
|
||||
|
|
@ -1610,7 +1645,7 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
|||
if ok {
|
||||
let _ = app.run("layout.orientation", json!({"value": if *landscape { "landscape" } else { "portrait" }}));
|
||||
let _ =
|
||||
app.run("layout.margins", json!({"top": *top * 72.0, "bottom": *bottom * 72.0, "left": *left * 72.0, "right": *right * 72.0}));
|
||||
app.run("layout.margins", json!({"top": *top * crate::options::unit().pt_per_unit(), "bottom": *bottom * crate::options::unit().pt_per_unit(), "left": *left * crate::options::unit().pt_per_unit(), "right": *right * crate::options::unit().pt_per_unit()}));
|
||||
if grid != grid_was {
|
||||
let kind = ["none", "lines", "linesAndChars", "snapToChars"].get(grid.0 as usize).copied().unwrap_or("none");
|
||||
let mut v = json!({"type": kind});
|
||||
|
|
@ -2419,7 +2454,7 @@ fn recipient_list(app: &mut WordApp, ui: &mut Ui, fields: &mut Vec<String>, rows
|
|||
|
||||
/// The Table Properties dialog's fields: Table, Row, Column and Cell sections.
|
||||
fn table_properties(ui: &mut Ui, f: &mut TableForm) {
|
||||
let unit = wordcraft_geom::Unit::default();
|
||||
let unit = crate::options::unit();
|
||||
fn len(v: &mut f32, lo: f32, unit: wordcraft_geom::Unit) -> egui::DragValue<'_> {
|
||||
egui::DragValue::new(v).speed(0.01).range(lo..=22.0).suffix(unit.suffix()).max_decimals(2)
|
||||
}
|
||||
|
|
@ -2511,7 +2546,7 @@ fn apply_paragraph(
|
|||
let _ = app.run("para.align", json!({"value": align}));
|
||||
let (start, end) = if rtl { (right, left) } else { (left, right) };
|
||||
let mut props = json!({
|
||||
"indentLeft": start * 72.0, "indentRight": end * 72.0, "indentFirst": first * 72.0,
|
||||
"indentLeft": start * crate::options::unit().pt_per_unit(), "indentRight": end * crate::options::unit().pt_per_unit(), "indentFirst": first * crate::options::unit().pt_per_unit(),
|
||||
"spaceBefore": before, "spaceAfter": after, "lineSpacing": {"rule": "multiple", "value": line},
|
||||
"keepNext": keep_next, "keepLines": keep_lines, "pageBreakBefore": page_break, "widowControl": widow,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ pub struct ColumnsForm {
|
|||
|
||||
impl ColumnsForm {
|
||||
fn k() -> f32 {
|
||||
wordcraft_geom::Unit::default().pt_per_unit()
|
||||
crate::options::unit().pt_per_unit()
|
||||
}
|
||||
|
||||
pub fn read(app: &WordApp) -> ColumnsForm {
|
||||
|
|
@ -346,7 +346,7 @@ fn columns_preview(ui: &mut Ui, f: &ColumnsForm, t: &Tokens) {
|
|||
|
||||
fn columns_ui(app: &mut WordApp, ui: &mut Ui, f: &mut ColumnsForm) -> bool {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let unit = wordcraft_geom::Unit::default();
|
||||
let unit = crate::options::unit();
|
||||
ui.set_width(420.0);
|
||||
ui.label(egui::RichText::new(tl!("Presets")).font(semibold(12.5)));
|
||||
let current = f.current_preset();
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ pub struct LevelForm {
|
|||
|
||||
impl LevelForm {
|
||||
fn read(i: usize, v: &Value) -> LevelForm {
|
||||
let k = wordcraft_geom::Unit::default().pt_per_unit();
|
||||
let k = crate::options::unit().pt_per_unit();
|
||||
let s = |key: &str| v.get(key).and_then(Value::as_str).unwrap_or("").to_string();
|
||||
let f = |key: &str| v.get(key).and_then(Value::as_f64).map(|x| x as f32 / k).filter(|x| x.is_finite());
|
||||
let restart_after = v.get("restartAfter").and_then(Value::as_u64);
|
||||
|
|
@ -78,7 +78,7 @@ impl LevelForm {
|
|||
|
||||
/// The level as `list.define` takes it.
|
||||
pub fn params(&self, i: usize) -> Value {
|
||||
let k = wordcraft_geom::Unit::default().pt_per_unit();
|
||||
let k = crate::options::unit().pt_per_unit();
|
||||
json!({
|
||||
"format": self.format,
|
||||
"text": self.text,
|
||||
|
|
@ -162,7 +162,7 @@ impl ListForm {
|
|||
|
||||
/// Define New Multilevel List. Returns true to close.
|
||||
pub fn define_list(app: &mut WordApp, ui: &mut Ui, f: &mut ListForm) -> bool {
|
||||
let unit = wordcraft_geom::Unit::default();
|
||||
let unit = crate::options::unit();
|
||||
fn len(v: &mut f32, unit: wordcraft_geom::Unit) -> egui::DragValue<'_> {
|
||||
egui::DragValue::new(v).speed(0.01).range(-10.0..=22.0).suffix(unit.suffix()).max_decimals(2)
|
||||
}
|
||||
|
|
@ -310,7 +310,7 @@ fn preview(ui: &mut Ui, f: &ListForm) {
|
|||
let p = ui.painter_at(rect);
|
||||
p.rect_filled(rect, 2.0, egui::Color32::WHITE);
|
||||
p.rect_stroke(rect, 2.0, egui::Stroke::new(1.0, t.border_strong), egui::StrokeKind::Inside);
|
||||
let k = wordcraft_geom::Unit::default().pt_per_unit();
|
||||
let k = crate::options::unit().pt_per_unit();
|
||||
// Positions in points, scaled so 4.5 inches fit.
|
||||
let scale = (rect.width() - 16.0) / 324.0;
|
||||
let x_of = |v: f32| rect.left() + 8.0 + (v * k * scale).clamp(0.0, rect.width() - 40.0);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use crate::theme::{Tokens, semibold};
|
|||
const SAME_POS: f32 = 0.5;
|
||||
|
||||
fn unit() -> wordcraft_geom::Unit {
|
||||
wordcraft_geom::Unit::default()
|
||||
crate::options::unit()
|
||||
}
|
||||
|
||||
/// The Tabs dialog's fields. Positions in `stops` and `cleared` are points; `pos` and
|
||||
|
|
|
|||
|
|
@ -183,6 +183,11 @@ fn untranslated_interface_text() {
|
|||
let drawn = frame(&ctx, &mut app);
|
||||
record(&app, &format!("File › {page}"), drawn);
|
||||
}
|
||||
for pane in wordcraft_engine::cmd::options::PANES {
|
||||
let _ = app.run("ui.backstage", json!({"value": true, "page": "options", "pane": pane}));
|
||||
let drawn = frame(&ctx, &mut app);
|
||||
record(&app, &format!("File › Options › {pane}"), drawn);
|
||||
}
|
||||
let _ = app.run("ui.backstage", json!({"value": false}));
|
||||
let dialogs = [
|
||||
"font",
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@
|
|||
Arrange\nAll Organizar\ntodo
|
||||
Arrow Flecha
|
||||
Artistic Effects Efectos artísticos
|
||||
As you type Mientras escribes
|
||||
Asian Typography Tipografía asiática
|
||||
Author Autor
|
||||
AutoCorrect Options Opciones de autocorrección
|
||||
|
|
@ -72,39 +73,72 @@
|
|||
AutoSave saves every change to the file (needs a saved document) El autoguardado guarda cada cambio en el archivo (requiere un documento guardado)
|
||||
AutoSave isn't available in the browser: it can't write to your files. Save downloads a copy. El autoguardado no está disponible en el navegador: no puede escribir en tus archivos. Guardar descarga una copia.
|
||||
AutoSave only keeps .docx, .odt and .rtf files; {name} can't keep all formatting. Save as .docx to turn on AutoSave. El autoguardado solo admite archivos .docx, .odt y .rtf; {name} no puede conservar todo el formato. Guarda como .docx para activar el autoguardado.
|
||||
AutoSave writes your changes to the file a moment after you stop typing. El autoguardado escribe tus cambios en el archivo poco después de que dejes de escribir.
|
||||
Balloons Globos
|
||||
Bullet Viñeta
|
||||
By author Por autor
|
||||
Centimeters Centímetros
|
||||
Changed lines: Líneas cambiadas:
|
||||
Click level to modify: Haz clic en el nivel que quieras modificar:
|
||||
Check spelling as you type Revisar la ortografía mientras escribes
|
||||
Click to switch back to inserting Haz clic para volver a insertar
|
||||
Color only Solo color
|
||||
Custom dictionary Diccionario personalizado
|
||||
Define New Multilevel List Definir nueva lista multinivel
|
||||
Define New Multilevel List… Definir nueva lista multinivel…
|
||||
Deletions: Eliminaciones:
|
||||
Enter formatting for number: Formato del número:
|
||||
Follow number with: Después del número:
|
||||
Formatting Formato
|
||||
Formatting marks Marcas de formato
|
||||
Hidden text Texto oculto
|
||||
Ignore web and file addresses Omitir direcciones web y de archivo
|
||||
Ignore words in capitals Omitir palabras en mayúsculas
|
||||
Ignore words with numbers Omitir palabras con números
|
||||
Inches Pulgadas
|
||||
Formatting exceptions Excepciones de formato
|
||||
Include level number from: Incluir número de nivel de:
|
||||
Initials: Iniciales:
|
||||
Ink Entrada de lápiz
|
||||
Insertions and Deletions Inserciones y eliminaciones
|
||||
Insertions: Inserciones:
|
||||
Interface Interfaz
|
||||
Interface language Idioma de la interfaz
|
||||
Inside: Interior:
|
||||
Left border Borde izquierdo
|
||||
Legal style numbering Numeración de estilo legal
|
||||
Link level to style: Vincular nivel con estilo:
|
||||
List Definition Definición de lista
|
||||
Mark grammar errors as you type Marcar errores gramaticales mientras escribes
|
||||
Measurement units: Unidades de medida:
|
||||
Millimeters Milímetros
|
||||
New comments and tracked changes are recorded under this name and these initials. Los comentarios y cambios con seguimiento nuevos se registran con este nombre y estas iniciales.
|
||||
New word Palabra nueva
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Aún no hay palabras. Añadir al diccionario sobre una palabra mal escrita la pone aquí.
|
||||
Mirror indents Sangrías simétricas
|
||||
Nothing Nada
|
||||
Number alignment: Alineación del número:
|
||||
Number format Formato de número
|
||||
Number style for this level: Estilo de número para este nivel:
|
||||
Outside border Borde exterior
|
||||
Overtype Sobrescribir
|
||||
Overtype (typing replaces the characters after the caret) Sobrescribir (lo que escribes reemplaza los caracteres tras el cursor)
|
||||
Page display Visualización de página
|
||||
Paragraph marks Marcas de párrafo
|
||||
Personalize Personalizar
|
||||
Picas Picas
|
||||
Points Puntos
|
||||
Print the page color Imprimir el color de página
|
||||
Printing Impresión
|
||||
Proofing language Idioma de revisión
|
||||
Recent documents to show: Documentos recientes que mostrar:
|
||||
Remove Quitar
|
||||
Outside: Exterior:
|
||||
Restart list after: Reiniciar lista después de:
|
||||
Right border Borde derecho
|
||||
Save the document to turn on AutoSave Guarda el documento para activar el autoguardado
|
||||
Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Guarda el documento para activar el autoguardado (un archivo que solo has abierto no se reescribe hasta que lo guardes)
|
||||
Save documents Guardar documentos
|
||||
AutoText Texto automático
|
||||
Automatic Automático
|
||||
Automatic ({language}) Automático ({language})
|
||||
|
|
@ -130,6 +164,11 @@
|
|||
Border Painter Pincel de bordes
|
||||
Border\nPainter Pincel de\nbordes
|
||||
Borders Bordes
|
||||
Show the mini toolbar when text is selected Mostrar la minibarra de herramientas al seleccionar texto
|
||||
Spaces Espacios
|
||||
Spelling Ortografía
|
||||
Spelling and grammar are checked in English (United States). La ortografía y la gramática se revisan en inglés (Estados Unidos).
|
||||
Tab characters Caracteres de tabulación
|
||||
Tab stop position: Posición de la tabulación:
|
||||
Default tab stops: Tabulaciones predeterminadas:
|
||||
Tab stops to be cleared: Tabulaciones que se borrarán:
|
||||
|
|
@ -140,6 +179,10 @@
|
|||
Set Fijar
|
||||
Page Border Borde de página
|
||||
Apply to: Aplicar a:
|
||||
These marks show even when Show/Hide ¶ is off. Estas marcas se muestran aunque Mostrar u ocultar ¶ esté desactivado.
|
||||
Typing replaces selected text Lo que escribes reemplaza el texto seleccionado
|
||||
Update fields before printing Actualizar campos antes de imprimir
|
||||
Use the Insert key to switch overtype on and off Usar la tecla Insert para activar y desactivar la sobrescritura
|
||||
Whole document Todo el documento
|
||||
This section Esta sección
|
||||
Fill Relleno de color
|
||||
|
|
@ -658,7 +701,6 @@
|
|||
Reject All Changes Rechazar todos los cambios
|
||||
Reject Change Rechazar cambio
|
||||
Relaxed Relajado
|
||||
Remove Quitar
|
||||
Remove Background Quitar fondo
|
||||
Remove Footer Quitar pie de página
|
||||
Remove Header Quitar encabezado
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
|
|
@ -49,6 +49,7 @@
|
|||
Arrange\nAll Paiguta\nkõik
|
||||
Arrow Nool
|
||||
Artistic Effects Kunstilised efektid
|
||||
As you type Kirjutamise ajal
|
||||
Author Autor
|
||||
AutoCorrect Options Automaatparanduse valikud
|
||||
AutoFit Automaatne sobitus
|
||||
|
|
@ -56,6 +57,7 @@
|
|||
AutoFit Window Sobita aknaga
|
||||
AutoSave Automaatsalvestus
|
||||
AutoSave saves every change to the file (needs a saved document) Automaatsalvestus salvestab iga muudatuse faili (dokument peab olema salvestatud)
|
||||
AutoSave writes your changes to the file a moment after you stop typing. Automaatsalvestus kirjutab muudatused faili veidi pärast seda, kui lõpetad kirjutamise.
|
||||
AutoText Automaattekst
|
||||
Automatic Automaatne
|
||||
Automatic ({language}) Automaatne ({language})
|
||||
|
|
@ -96,6 +98,7 @@
|
|||
Cell Size Lahtri suurus
|
||||
Cell\nMargins Lahtri\nveerised
|
||||
Center Keskele
|
||||
Centimeters Sentimeetrid
|
||||
Change Muuda
|
||||
Change Case Muuda tähesuurust
|
||||
Change List Level Muuda loendi taset
|
||||
|
|
@ -114,6 +117,7 @@
|
|||
Check Compatibility Kontrolli ühilduvust
|
||||
Check Spelling as You Type Kontrolli õigekirja kirjutamise ajal
|
||||
Check for Errors Kontrolli vigu
|
||||
Check spelling as you type Kontrolli õigekirja kirjutamise ajal
|
||||
Check\nAccessibility Kontrolli\nligipääsetavust
|
||||
Citation Style Viitamisstiil
|
||||
Citations & Bibliography Viited ja allikaloend
|
||||
|
|
@ -124,6 +128,7 @@
|
|||
Clear Character Style Eemalda märgistiil
|
||||
Clear Formatting Level Eemalda vormindustase
|
||||
Clear Paragraph Formatting Eemalda lõiguvormindus
|
||||
Click to switch back to inserting Klõpsa, et naasta lisamisrežiimi
|
||||
Clipboard Lõikelaud
|
||||
Close Sulge
|
||||
Close Header and Footer Sulge päis ja jalus
|
||||
|
|
@ -164,6 +169,7 @@
|
|||
Custom Margins… Kohandatud veerised…
|
||||
Custom Watermark Kohandatud vesimärk
|
||||
Custom Watermark… Kohandatud vesimärk…
|
||||
Custom dictionary Kohandatud sõnastik
|
||||
Customize Quick Access Toolbar Kohanda kiirpääsuriba
|
||||
Cut Lõika
|
||||
Dark Mode Tume režiim
|
||||
|
|
@ -276,6 +282,7 @@
|
|||
Format Painter Vormingupintsel
|
||||
Formatted Text Vormindatud tekst
|
||||
Formatting at Selection Valiku vormindus
|
||||
Formatting marks Vormindusmärgid
|
||||
Formatting exceptions Vormindamise erandid
|
||||
Formula Valem
|
||||
General Üldine
|
||||
|
|
@ -302,6 +309,7 @@
|
|||
Heart Süda
|
||||
Help Abi
|
||||
Hidden Peidetud
|
||||
Hidden text Peidetud tekst
|
||||
Highlight Merge Fields Tõsta ühendamisväljad esile
|
||||
Highlight\nMerge Fields Tõsta ühendamis-\nväljad esile
|
||||
Highlighter Marker
|
||||
|
|
@ -311,10 +319,14 @@
|
|||
Hyphenation Poolitus
|
||||
Icons Ikoonid
|
||||
Ignore All Eira kõiki
|
||||
Ignore web and file addresses Eira veebi- ja failiaadresse
|
||||
Ignore words in capitals Eira suurtähtedega sõnu
|
||||
Ignore words with numbers Eira numbritega sõnu
|
||||
Illustrations Illustratsioonid
|
||||
Immersive Süvenemine
|
||||
Immersive Reader Süvaluger
|
||||
Immersive\nReader Süva-\nluger
|
||||
Inches Tollid
|
||||
Increase Font Size Suurenda fondi suurust
|
||||
Increase Indent Suurenda taanet
|
||||
Indent Taane
|
||||
|
|
@ -322,6 +334,7 @@
|
|||
Indents Taanded
|
||||
Index Register
|
||||
Info Teave
|
||||
Initials: Initsiaalid:
|
||||
Ink to\nMath Käsikiri\nvalemiks
|
||||
Ink to\nShape Käsikiri\nkujundiks
|
||||
Ink\nReplay Esita\nkäsikiri
|
||||
|
|
@ -351,6 +364,8 @@
|
|||
Inside: Sisemine:
|
||||
Inspect Document Kontrolli dokumenti
|
||||
Inspect Styles at Selection Kontrolli valiku stiile
|
||||
Interface Kasutajaliides
|
||||
Interface language Kasutajaliidese keel
|
||||
Interface language: Kasutajaliidese keel:
|
||||
Italic Kaldkiri
|
||||
Join the ArtCraft community on Discord Liitu ArtCrafti kogukonnaga Discordis
|
||||
|
|
@ -401,16 +416,19 @@
|
|||
Margins Veerised
|
||||
Mark Citation Märgi õigusallikas
|
||||
Mark Entry Märgi registrikirje
|
||||
Mark grammar errors as you type Märgi grammatikavead kirjutamise ajal
|
||||
Mark\nCitation Märgi\nõigusallikas
|
||||
Mark\nEntry Märgi\nregistrikirje
|
||||
Markdown (*.md) Markdown (*.md)
|
||||
Match Fields Vastenda väljad
|
||||
Match case Erista suur- ja väiketähti
|
||||
Matrix Maatriks
|
||||
Measurement units: Mõõtühikud:
|
||||
Merge Ühenda
|
||||
Merge Cells Ühenda lahtrid
|
||||
Merge Formatting Ühenda vormindus
|
||||
Merged PRs Ühendatud PR-id
|
||||
Millimeters Millimeetrid
|
||||
Mirrored Peegeldatud
|
||||
Moderate Mõõdukas
|
||||
Modify Style Muuda stiili
|
||||
|
|
@ -429,7 +447,9 @@
|
|||
New Paragraph Uus lõik
|
||||
New Style… Uus stiil…
|
||||
New Window Uus aken
|
||||
New comments and tracked changes are recorded under this name and these initials. Uued kommentaarid ja jälitatud muudatused salvestatakse selle nime ja nende initsiaalidega.
|
||||
New from Template Uus mallist
|
||||
New word Uus sõna
|
||||
New\nComment Uus\nkommentaar
|
||||
New\nWindow Uus\naken
|
||||
Next Järgmine
|
||||
|
|
@ -446,6 +466,7 @@
|
|||
No contributor data was built into this copy. Selles versioonis pole kaasautorite andmeid.
|
||||
No model credits were built into this copy. Selles versioonis pole mudelite autoriteavet.
|
||||
No templates match your search. Otsingule vastavaid malle pole.
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Sõnu veel pole. Valik „Lisa sõnastikku“ valesti kirjutatud sõnal lisab selle siia.
|
||||
Nonbreaking Hyphen Poolitamatu sidekriips
|
||||
Nonbreaking Space Poolitamatu tühik
|
||||
None Puudub
|
||||
|
|
@ -474,6 +495,8 @@
|
|||
Outside Borders Välisäärised
|
||||
Outside: Välimine:
|
||||
Oval Ovaal
|
||||
Overtype Ülekirjutus
|
||||
Overtype (typing replaces the characters after the caret) Ülekirjutus (kirjutatu asendab kursori järel olevad märgid)
|
||||
PDF document (*.pdf) PDF-dokument (*.pdf)
|
||||
Page Leht
|
||||
Page Background Lehe taust
|
||||
|
|
@ -490,6 +513,7 @@
|
|||
Page Width Lehe laius
|
||||
Page X of Y Leht X / Y
|
||||
Page break before Lehepiir enne
|
||||
Page display Lehe kuvamine
|
||||
Page image (*.png) Lehe pilt (*.png)
|
||||
Page width Lehe laius
|
||||
Page {page} Leht {page}
|
||||
|
|
@ -504,6 +528,7 @@
|
|||
Paragraph Settings Lõigu sätted
|
||||
Paragraph Spacing Lõiguvahe
|
||||
Paragraph Up Lõigu võrra üles
|
||||
Paragraph marks Lõigumärgid
|
||||
Paragraphs Lõigud
|
||||
Paste Kleebi
|
||||
Paste (⌘V) Kleebi (⌘V)
|
||||
|
|
@ -515,11 +540,14 @@
|
|||
Paste: Merge Formatting Kleebi: ühenda vormindus
|
||||
Pen Pliiats
|
||||
Pencil Grafiitpliiats
|
||||
Personalize Isikupärastamine
|
||||
Picas Piikad
|
||||
Picture Border Pildi ääris
|
||||
Picture Format Pildi vorming
|
||||
Picture Styles Pildistiilid
|
||||
Pictures Pildid
|
||||
Plain text (*.txt) Lihttekst (*.txt)
|
||||
Points Punktid
|
||||
Portrait Püstpaigutus
|
||||
Position Asukoht
|
||||
Preview Results Tulemuste eelvaade
|
||||
|
|
@ -531,9 +559,12 @@
|
|||
Previous Section Eelmine jaotis
|
||||
Print Prindi
|
||||
Print Layout Prindipaigutus
|
||||
Print the page color Prindi lehe värv
|
||||
Print\nLayout Prindi-\npaigutus
|
||||
Printing Printimine
|
||||
Proofing Keelekontroll
|
||||
Proofing Issues Keelekontrolli probleemid
|
||||
Proofing language Keelekontrolli keel
|
||||
Properties Atribuudid
|
||||
Protect Kaitse
|
||||
Protect Document Kaitse dokumenti
|
||||
|
|
@ -548,6 +579,7 @@
|
|||
Reads the clipboard's web content and keeps its formatting. Loeb lõikelaualt veebisisu ja säilitab vorminduse.
|
||||
Reading Highlight Otsingutulemuste esiletõst
|
||||
Recent Hiljutised
|
||||
Recent documents to show: Kuvatavad hiljutised dokumendid:
|
||||
Record Macro Salvesta makro
|
||||
Recover Unsaved Documents Taasta salvestamata dokumendid
|
||||
Rectangle Ristkülik
|
||||
|
|
@ -558,6 +590,7 @@
|
|||
Reject All Changes Lükka kõik muudatused tagasi
|
||||
Reject Change Lükka muudatus tagasi
|
||||
Relaxed Hõre
|
||||
Remove Eemalda
|
||||
Remove Background Eemalda taust
|
||||
Remove Footer Eemalda jalus
|
||||
Remove Header Eemalda päis
|
||||
|
|
@ -608,6 +641,7 @@
|
|||
Save As Salvesta nimega
|
||||
Save a copy in another format. Printing goes through a PDF you can print from any viewer. Salvesta koopia teises vormingus. Printimiseks luuakse PDF, mille saad printida PDF-vaaturist.
|
||||
Save as Template Salvesta mallina
|
||||
Save documents Dokumentide salvestamine
|
||||
Saved Salvestatud
|
||||
Search Commands Otsi käske
|
||||
Search commands and help Otsi käske ja abi
|
||||
|
|
@ -656,6 +690,7 @@
|
|||
Show all formatting marks Kuva kõik vormindusmärgid
|
||||
Show rulers Kuva joonlauad
|
||||
Show the community button in the title bar Kuva tiitliribal kogukonna nupp
|
||||
Show the mini toolbar when text is selected Kuva teksti valimisel minitööriistariba
|
||||
Show/Hide ¶ Kuva/peida ¶
|
||||
Show\nComments Kuva\nkommentaarid
|
||||
Shrink Font 1 Point Vähenda fonti 1 punkti võrra
|
||||
|
|
@ -672,12 +707,15 @@
|
|||
Sort Sordi
|
||||
Source code: github.com/storytold/wordcraft Lähtekood: github.com/storytold/wordcraft
|
||||
Space before/after: Vahe enne/pärast:
|
||||
Spaces Tühikud
|
||||
Spacing Vahe
|
||||
Spacing (pt): Vahe (pt):
|
||||
Speech Kõne
|
||||
Spelling Õigekiri
|
||||
Spelling & Grammar Õigekiri ja grammatika
|
||||
Spelling &\nGrammar Õigekiri ja\ngrammatika
|
||||
Spelling Suggestions Õigekirjasoovitused
|
||||
Spelling and grammar are checked in English (United States). Õigekirja ja grammatikat kontrollitakse inglise keeles (Ameerika Ühendriigid).
|
||||
Split Tükelda
|
||||
Split Cells Tükelda lahtrid
|
||||
Split Table Tükelda tabel
|
||||
|
|
@ -706,6 +744,7 @@
|
|||
Symbols Sümbolid
|
||||
Synonyms Sünonüümid
|
||||
Tab Tabeldus
|
||||
Tab characters Tabeldusmärgid
|
||||
Table Tabel
|
||||
Table Column Width Tabeli veerulaius
|
||||
Table Design Tabeli kujundus
|
||||
|
|
@ -739,6 +778,7 @@
|
|||
Theme List Kujunduste loend
|
||||
Themes Kujundused
|
||||
Thesaurus Sünonüümisõnastik
|
||||
These marks show even when Show/Hide ¶ is off. Need märgid on nähtavad ka siis, kui Kuva/peida ¶ on välja lülitatud.
|
||||
Thick Paks
|
||||
Three Kolm
|
||||
Tight Tihe
|
||||
|
|
@ -755,6 +795,7 @@
|
|||
Two Kaks
|
||||
Type Text Sisesta tekst
|
||||
Type a command, e.g. "insert table" Sisesta käsk, nt "lisa tabel"
|
||||
Typing replaces selected text Kirjutatu asendab valitud teksti
|
||||
UPPERCASE SUURTÄHED
|
||||
Underline Allajoon
|
||||
Underline Words Only Jooni alla ainult sõnad
|
||||
|
|
@ -767,8 +808,10 @@
|
|||
Update Style to Match Selection Uuenda stiil valiku järgi
|
||||
Update Table Uuenda tabelit
|
||||
Update Table of Figures Uuenda illustratsioonide loendit
|
||||
Update fields before printing Värskenda välju enne printimist
|
||||
Update to Match Uuenda vastavaks
|
||||
Update to Match Selection Uuenda valiku järgi
|
||||
Use the Insert key to switch overtype on and off Kasuta Insert-klahvi ülekirjutuse sisse- ja väljalülitamiseks
|
||||
User Name Kasutajanimi
|
||||
User name: Kasutajanimi:
|
||||
Version History Versiooniajalugu
|
||||
|
|
@ -1454,7 +1497,6 @@
|
|||
Not used by any command Ükski käsk ei kasuta seda
|
||||
Now used by {command} Praegu kasutab: {command}
|
||||
Press a key combination Vajuta klahvikombinatsiooni
|
||||
Remove Eemalda
|
||||
Remove every custom shortcut and restore the built-in keys? Kas eemaldada kõik kohandatud kiirklahvid ja taastada sisseehitatud klahvid?
|
||||
Reset All… Lähtesta kõik…
|
||||
(Custom) (Kohandatud)
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 12.
|
|
|
@ -48,12 +48,14 @@
|
|||
Arrange\nAll 整列
|
||||
Arrow 矢印
|
||||
Artistic Effects アート効果
|
||||
As you type 入力中
|
||||
Asian Typography アジア文字の組版
|
||||
Author 作成者
|
||||
AutoCorrect Options オートコレクトのオプション
|
||||
AutoFit 自動調整
|
||||
AutoFit Contents 文字列の幅に合わせる
|
||||
AutoFit Window ウィンドウ幅に合わせる
|
||||
AutoSave writes your changes to the file a moment after you stop typing. 自動保存は、入力を止めて少したつと変更をファイルに書き込みます。
|
||||
Automatic 自動
|
||||
Automatic ({language}) 自動({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. 「自動」はシステムの言語に従います。メニューとコマンドの表示が変わり、文書の内容は変わりません。
|
||||
|
|
@ -64,13 +66,17 @@
|
|||
AutoSave failed: {error}. Save the document to turn AutoSave back on. 自動保存できませんでした: {error}。文書を保存し直すと、自動保存が再開します。
|
||||
Balloons 吹き出し
|
||||
Bottom Margin 下余白
|
||||
Centimeters センチメートル
|
||||
Check spelling as you type 入力中にスペル チェックを行う
|
||||
Click a border between two cells to merge them. Esc stops. 2 つのセルの間の境界線をクリックすると結合します。Esc で終了します。
|
||||
Bullet 行頭文字
|
||||
By author 校閲者別
|
||||
Changed lines: 変更された行:
|
||||
Click level to modify: 変更するレベルをクリック:
|
||||
Click to switch back to inserting クリックすると挿入モードに戻ります
|
||||
Color only 色のみ
|
||||
Compress punctuation at the start of a line 行頭の約物を詰める
|
||||
Custom dictionary ユーザー辞書
|
||||
Dark page (white text on black) ダーク ページ (黒地に白い文字)
|
||||
Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. 四角形をドラッグして表を描くか、セルを横切る線を引いて分割します。Esc で終了します。
|
||||
Draw Table 表を描画
|
||||
|
|
@ -81,11 +87,20 @@
|
|||
Enter formatting for number: 番号の書式:
|
||||
Follow number with: 番号に続く文字:
|
||||
Formatting 書式
|
||||
Formatting marks 編集記号
|
||||
Hidden text 隠し文字
|
||||
Ignore web and file addresses Web アドレスとファイル パスを無視する
|
||||
Ignore words in capitals すべて大文字の単語を無視する
|
||||
Ignore words with numbers 数字を含む単語を無視する
|
||||
Inches インチ
|
||||
Formatting exceptions 書式の例外
|
||||
Include level number from: 含めるレベルの番号:
|
||||
Initials: イニシャル:
|
||||
Ink インク
|
||||
Insertions and Deletions 挿入と削除
|
||||
Insertions: 挿入された箇所:
|
||||
Interface インターフェイス
|
||||
Interface language 表示言語
|
||||
Inside: 内側:
|
||||
Left border 左罫線
|
||||
Left Margin 左余白
|
||||
|
|
@ -94,14 +109,32 @@
|
|||
Line breaking 行の分割
|
||||
Link level to style: レベルに対応付けるスタイル:
|
||||
List Definition リストの定義
|
||||
Mark grammar errors as you type 入力中に文法の誤りに印を付ける
|
||||
Measurement units: 単位:
|
||||
Millimeters ミリメートル
|
||||
Mirror indents 左右対称のインデント
|
||||
More templates → その他のテンプレート →
|
||||
New comments and tracked changes are recorded under this name and these initials. 新しいコメントと変更履歴は、この名前とイニシャルで記録されます。
|
||||
New word 新しい単語
|
||||
No templates match your search. 検索に一致するテンプレートはありません。
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. まだ単語はありません。スペルミスの単語で [辞書に追加] を選ぶと、ここに追加されます。
|
||||
Nothing なし
|
||||
Number alignment: 番号の配置:
|
||||
Number format 番号書式
|
||||
Number style for this level: このレベルの番号の種類:
|
||||
Outside border 外側の罫線
|
||||
Overtype 上書き
|
||||
Overtype (typing replaces the characters after the caret) 上書き (入力した文字がカーソルの後ろの文字を置き換えます)
|
||||
Page display ページの表示
|
||||
Paragraph marks 段落記号
|
||||
Personalize 個人設定
|
||||
Picas パイカ
|
||||
Points ポイント
|
||||
Print the page color ページの色を印刷する
|
||||
Printing 印刷
|
||||
Proofing language 文章校正の言語
|
||||
Recent documents to show: 表示する最近使ったドキュメントの数:
|
||||
Remove 削除
|
||||
Outside: 外側:
|
||||
Reopen 再度開く
|
||||
Restart list after: リストを振り直すレベル:
|
||||
|
|
@ -112,9 +145,9 @@
|
|||
Distribute spacing 間隔を均等に配置
|
||||
Offset (pt): オフセット (pt):
|
||||
Phonetic Guide ルビ
|
||||
Remove 削除
|
||||
Right (vertical) 右 (縦書き)
|
||||
Ruby text: ルビ文字:
|
||||
Save documents ドキュメントの保存
|
||||
Search for templates テンプレートを検索
|
||||
Show all revisions inline すべての変更履歴を本文中に表示
|
||||
Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. 表示 › モードの切り替えと同様に、文書の色を反転して表示します。保存、印刷、PDF は変わりません。
|
||||
|
|
@ -142,6 +175,11 @@
|
|||
Border Painter 罫線の書式設定
|
||||
Border\nPainter 罫線の書式設定
|
||||
Borders 罫線
|
||||
Show the mini toolbar when text is selected テキストを選択したときにミニ ツール バーを表示する
|
||||
Spaces スペース
|
||||
Spelling スペル チェック
|
||||
Spelling and grammar are checked in English (United States). スペルと文法は英語 (米国) でチェックされます。
|
||||
Tab characters タブ
|
||||
Tab stop position: タブ位置:
|
||||
Default tab stops: 既定のタブ間隔:
|
||||
Tab stops to be cleared: 削除するタブ:
|
||||
|
|
@ -152,6 +190,10 @@
|
|||
Set 設定
|
||||
Page Border ページ罫線の設定
|
||||
Apply to: 適用先:
|
||||
These marks show even when Show/Hide ¶ is off. これらの記号は、[編集記号の表示/非表示 ¶] がオフでも表示されます。
|
||||
Typing replaces selected text 入力時に選択文字列を置き換える
|
||||
Update fields before printing 印刷前にフィールドを更新する
|
||||
Use the Insert key to switch overtype on and off Insert キーで上書きのオンとオフを切り替える
|
||||
Whole document 文書全体
|
||||
This section このセクション
|
||||
Fill 塗りつぶし
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 3.
|
|
|
@ -58,12 +58,14 @@
|
|||
Arrange\nAll Organizar\ntudo
|
||||
Arrow Seta
|
||||
Artistic Effects Efeitos artísticos
|
||||
As you type Enquanto você digita
|
||||
Asian Typography Tipografia asiática
|
||||
Author Autor
|
||||
AutoCorrect Options Opções de autocorreção
|
||||
AutoFit Autoajuste
|
||||
AutoFit Contents Autoajustar ao conteúdo
|
||||
AutoFit Window Autoajustar à janela
|
||||
AutoSave writes your changes to the file a moment after you stop typing. O salvamento automático grava suas alterações no arquivo pouco depois que você para de digitar.
|
||||
Automatic Automático
|
||||
Automatic ({language}) Automático ({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. Automático segue o idioma do seu sistema. Menus e comandos mudam; seus documentos, não.
|
||||
|
|
@ -76,34 +78,66 @@
|
|||
Balloons Balões
|
||||
Bullet Marcador
|
||||
By author Por autor
|
||||
Centimeters Centímetros
|
||||
Changed lines: Linhas alteradas:
|
||||
Check spelling as you type Verificar a ortografia enquanto você digita
|
||||
Click level to modify: Clique no nível a modificar:
|
||||
Click to switch back to inserting Clique para voltar a inserir
|
||||
Color only Somente cor
|
||||
Custom dictionary Dicionário personalizado
|
||||
Define New Multilevel List Definir nova lista de vários níveis
|
||||
Define New Multilevel List… Definir nova lista de vários níveis…
|
||||
Deletions: Exclusões:
|
||||
Enter formatting for number: Formatação do número:
|
||||
Follow number with: Após o número:
|
||||
Formatting Formatação
|
||||
Formatting marks Marcas de formatação
|
||||
Hidden text Texto oculto
|
||||
Ignore web and file addresses Ignorar endereços da Web e de arquivos
|
||||
Ignore words in capitals Ignorar palavras em maiúsculas
|
||||
Ignore words with numbers Ignorar palavras com números
|
||||
Inches Polegadas
|
||||
Formatting exceptions Exceções de formatação
|
||||
Include level number from: Incluir número do nível:
|
||||
Initials: Iniciais:
|
||||
Ink Tinta
|
||||
Insertions and Deletions Inserções e exclusões
|
||||
Insertions: Inserções:
|
||||
Interface Interface
|
||||
Interface language Idioma da interface
|
||||
Inside: Interno:
|
||||
Left border Borda esquerda
|
||||
Legal style numbering Numeração no estilo legal
|
||||
Link level to style: Vincular nível ao estilo:
|
||||
List Definition Definição da lista
|
||||
Mark grammar errors as you type Marcar erros gramaticais enquanto você digita
|
||||
Measurement units: Unidades de medida:
|
||||
Millimeters Milímetros
|
||||
New comments and tracked changes are recorded under this name and these initials. Novos comentários e alterações controladas são registrados com este nome e estas iniciais.
|
||||
New word Nova palavra
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Ainda não há palavras. Adicionar ao dicionário em uma palavra com erro a coloca aqui.
|
||||
Mirror indents Recuos espelhados
|
||||
Nothing Nada
|
||||
Number alignment: Alinhamento do número:
|
||||
Number format Formato do número
|
||||
Number style for this level: Estilo de número deste nível:
|
||||
Outside border Borda externa
|
||||
Overtype Sobrescrever
|
||||
Overtype (typing replaces the characters after the caret) Sobrescrever (o que você digita substitui os caracteres após o cursor)
|
||||
Page display Exibição da página
|
||||
Paragraph marks Marcas de parágrafo
|
||||
Personalize Personalizar
|
||||
Picas Paicas
|
||||
Points Pontos
|
||||
Print the page color Imprimir a cor da página
|
||||
Printing Impressão
|
||||
Proofing language Idioma de revisão
|
||||
Recent documents to show: Documentos recentes a mostrar:
|
||||
Remove Remover
|
||||
Outside: Externo:
|
||||
Restart list after: Reiniciar lista após:
|
||||
Right border Borda direita
|
||||
Save documents Salvar documentos
|
||||
Save the document to turn on AutoSave Salve o documento para ativar o salvamento automático
|
||||
Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Salve o documento para ativar o salvamento automático (um arquivo que você apenas abriu não é regravado até você salvá-lo)
|
||||
AutoText Autotexto
|
||||
|
|
@ -126,6 +160,11 @@
|
|||
Border Painter Pincel de bordas
|
||||
Border\nPainter Pincel\nde bordas
|
||||
Borders Bordas
|
||||
Show the mini toolbar when text is selected Mostrar a minibarra de ferramentas ao selecionar texto
|
||||
Spaces Espaços
|
||||
Spelling Ortografia
|
||||
Spelling and grammar are checked in English (United States). A ortografia e a gramática são verificadas em inglês (Estados Unidos).
|
||||
Tab characters Caracteres de tabulação
|
||||
Tab stop position: Posição da tabulação:
|
||||
Default tab stops: Tabulações padrão:
|
||||
Tab stops to be cleared: Tabulações a remover:
|
||||
|
|
@ -136,6 +175,10 @@
|
|||
Set Definir
|
||||
Page Border Borda da página
|
||||
Apply to: Aplicar a:
|
||||
These marks show even when Show/Hide ¶ is off. Estas marcas aparecem mesmo com Mostrar/ocultar ¶ desativado.
|
||||
Typing replaces selected text O que você digita substitui o texto selecionado
|
||||
Update fields before printing Atualizar campos antes de imprimir
|
||||
Use the Insert key to switch overtype on and off Usar a tecla Insert para ativar e desativar a sobrescrita
|
||||
Whole document Documento inteiro
|
||||
This section Esta seção
|
||||
Fill Cor de fundo
|
||||
|
|
@ -274,7 +317,6 @@
|
|||
Distribute spacing Distribuir espaçamento
|
||||
Offset (pt): Deslocamento (pt):
|
||||
Phonetic Guide Guia fonético
|
||||
Remove Remover
|
||||
Right (vertical) Direita (vertical)
|
||||
Ruby text: Texto ruby:
|
||||
Search for templates Pesquisar modelos
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
|
|
@ -2206,3 +2206,47 @@ preview Title Пример
|
|||
Turkish Турецкий
|
||||
Ukrainian Украинский
|
||||
{language} is now the document's default language. {language} теперь язык документа по умолчанию.
|
||||
|
||||
# File > Options panes (#485).
|
||||
As you type По мере ввода
|
||||
AutoSave writes your changes to the file a moment after you stop typing. Автосохранение записывает изменения в файл вскоре после того, как вы перестанете печатать.
|
||||
Centimeters Сантиметры
|
||||
Check spelling as you type Проверять орфографию при вводе
|
||||
Click to switch back to inserting Нажмите, чтобы вернуться к вставке
|
||||
Custom dictionary Пользовательский словарь
|
||||
Formatting marks Знаки форматирования
|
||||
Hidden text Скрытый текст
|
||||
Ignore web and file addresses Пропускать веб-адреса и пути к файлам
|
||||
Ignore words in capitals Пропускать слова из прописных букв
|
||||
Ignore words with numbers Пропускать слова с цифрами
|
||||
Inches Дюймы
|
||||
Initials: Инициалы:
|
||||
Interface Интерфейс
|
||||
Interface language Язык интерфейса
|
||||
Mark grammar errors as you type Отмечать грамматические ошибки при вводе
|
||||
Measurement units: Единицы измерения:
|
||||
Millimeters Миллиметры
|
||||
New comments and tracked changes are recorded under this name and these initials. Новые примечания и исправления записываются под этим именем и инициалами.
|
||||
New word Новое слово
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Слов пока нет. Команда «Добавить в словарь» для слова с ошибкой помещает его сюда.
|
||||
Overtype Замена
|
||||
Overtype (typing replaces the characters after the caret) Замена (вводимый текст заменяет символы после курсора)
|
||||
Page display Отображение страницы
|
||||
Paragraph marks Знаки абзаца
|
||||
Personalize Персонализация
|
||||
Picas Пики
|
||||
Points Пункты
|
||||
Print the page color Печатать цвет страницы
|
||||
Printing Печать
|
||||
Proofing language Язык проверки правописания
|
||||
Recent documents to show: Число недавних документов:
|
||||
Save documents Сохранение документов
|
||||
Show the mini toolbar when text is selected Показывать мини-панель при выделении текста
|
||||
Spaces Пробелы
|
||||
Spelling Орфография
|
||||
Spelling and grammar are checked in English (United States). Орфография и грамматика проверяются для английского языка (США).
|
||||
Tab characters Знаки табуляции
|
||||
These marks show even when Show/Hide ¶ is off. Эти знаки видны, даже когда «Показать все знаки» выключено.
|
||||
Typing replaces selected text Ввод заменяет выделенный текст
|
||||
Update fields before printing Обновлять поля перед печатью
|
||||
Use the Insert key to switch overtype on and off Клавиша Insert включает и выключает режим замены
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
|
|
@ -65,12 +65,14 @@
|
|||
Arrow Strelica
|
||||
Artistic Effects Umetnički efekti
|
||||
As Recommended Kao što je preporučeno
|
||||
As you type Tokom kucanja
|
||||
Asian Typography Azijska tipografija
|
||||
Author Autor
|
||||
AutoCorrect Options Opcije automatske ispravke
|
||||
AutoFit Automatsko uklapanje
|
||||
AutoFit Contents Automatski uklopi prema sadržaju
|
||||
AutoFit Window Automatski uklopi prema prozoru
|
||||
AutoSave writes your changes to the file a moment after you stop typing. Automatsko čuvanje upisuje izmene u datoteku čim prestanete da kucate.
|
||||
Automatic Automatski
|
||||
Automatic ({language}) Automatski ({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. Automatski prati jezik sistema. Meniji i naredbe se menjaju; dokumenti ne.
|
||||
|
|
@ -81,6 +83,8 @@
|
|||
AutoSave failed: {error}. Save the document to turn AutoSave back on. Automatsko čuvanje neuspelo: {error}. Sačuvajte dokument da biste ga ponovo uključili.
|
||||
Balloons Oblačići
|
||||
Bottom Margin Donja margina
|
||||
Centimeters Centimetri
|
||||
Check spelling as you type Proveravaj pravopis tokom kucanja
|
||||
Click a border between two cells to merge them. Esc stops. Kliknite ivicu između dve ćelije da ih spojite. Esc završava.
|
||||
Based on: {style} Zasnovan na: {style}
|
||||
Built-in style Ugrađeni stil
|
||||
|
|
@ -92,12 +96,14 @@
|
|||
Character style Stil znakova
|
||||
Click an item to paste it. Kliknite na stavku da biste je nalepili.
|
||||
Click level to modify: Kliknite nivo koji menjate:
|
||||
Click to switch back to inserting Kliknite da biste se vratili na umetanje
|
||||
Clipboard Items Stavke ostave
|
||||
Clipboard Pane Okno ostave
|
||||
Color only Samo boja
|
||||
Color: {color} Boja: {color}
|
||||
Compress punctuation at the start of a line Sažmi interpunkciju na početku reda
|
||||
Confirm password: Potvrdite lozinku:
|
||||
Custom dictionary Prilagođeni rečnik
|
||||
Dark page (white text on black) Tamna stranica (beo tekst na crnom)
|
||||
Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. Prevucite pravougaonik da nacrtate tabelu ili liniju preko ćelija da ih podelite. Esc završava.
|
||||
Draw Table Nacrtaj tabelu
|
||||
|
|
@ -108,23 +114,50 @@
|
|||
Enter formatting for number: Oblik broja:
|
||||
Follow number with: Posle broja:
|
||||
Formatting Oblikovanje
|
||||
Formatting marks Oznake oblikovanja
|
||||
Hidden text Skriveni tekst
|
||||
Ignore web and file addresses Zanemari veb-adrese i putanje datoteka
|
||||
Ignore words in capitals Zanemari reči pisane velikim slovima
|
||||
Ignore words with numbers Zanemari reči sa brojevima
|
||||
Inches Inči
|
||||
Formatting exceptions Izuzeci formatiranja
|
||||
Include level number from: Uključi broj nivoa:
|
||||
Initials: Inicijali:
|
||||
Ink Mastilo
|
||||
Insertions and Deletions Umetanja i brisanja
|
||||
Insertions: Umetanja:
|
||||
Interface Interfejs
|
||||
Interface language Jezik interfejsa
|
||||
Inside: Unutra:
|
||||
Left border Leva ivica
|
||||
Left Margin Leva margina
|
||||
Legal style numbering Pravno numerisanje
|
||||
Link level to style: Poveži nivo sa stilom:
|
||||
List Definition Definicija liste
|
||||
Mark grammar errors as you type Označavaj gramatičke greške tokom kucanja
|
||||
Measurement units: Jedinice mere:
|
||||
Millimeters Milimetri
|
||||
New comments and tracked changes are recorded under this name and these initials. Novi komentari i praćene izmene beleže se pod ovim imenom i inicijalima.
|
||||
New word Nova reč
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Još nema reči. Dodaj u rečnik na pogrešno napisanoj reči stavlja je ovde.
|
||||
Mirror indents Preslikaj uvlačenja
|
||||
Nothing Ništa
|
||||
Number alignment: Poravnanje broja:
|
||||
Number format Format broja
|
||||
Number style for this level: Stil broja za ovaj nivo:
|
||||
Outside border Spoljna ivica
|
||||
Overtype Prepisivanje
|
||||
Overtype (typing replaces the characters after the caret) Prepisivanje (otkucano zamenjuje znakove posle kursora)
|
||||
Page display Prikaz strane
|
||||
Paragraph marks Oznake pasusa
|
||||
Personalize Personalizacija
|
||||
Picas Pike
|
||||
Points Tačke
|
||||
Print the page color Štampaj boju strane
|
||||
Printing Štampanje
|
||||
Proofing language Jezik provere pisanja
|
||||
Recent documents to show: Broj nedavnih dokumenata za prikaz:
|
||||
Remove Ukloni
|
||||
Outside: Spolja:
|
||||
Reopen Ponovo otvori
|
||||
Restart list after: Ponovo počni listu posle:
|
||||
|
|
@ -163,6 +196,7 @@
|
|||
Paste Clipboard Item Nalepi stavku iz ostave
|
||||
Preview Pregled
|
||||
Remove Password Ukloni lozinku
|
||||
Save documents Čuvanje dokumenata
|
||||
Shadow, glow and soft edges Senka, sjaj i meke ivice
|
||||
Shape Effects Efekti oblika
|
||||
Show all revisions inline Prikaži sve izmene u tekstu
|
||||
|
|
@ -171,7 +205,6 @@
|
|||
Distribute spacing Rasporedi razmak
|
||||
Offset (pt): Pomak (pt):
|
||||
Phonetic Guide Fonetski vodič
|
||||
Remove Ukloni
|
||||
Right (vertical) Desno (vertikalno)
|
||||
Ruby text: Rubi tekst:
|
||||
Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Prikaži dokumente sa invertovanim bojama, kao kod Prikaz › Promeni režim. Čuvanje, štampanje i PDF nisu pogođeni.
|
||||
|
|
@ -199,6 +232,11 @@
|
|||
Border Painter Kopiranje ivica
|
||||
Border\nPainter Kopiranje\nivica
|
||||
Borders Ivice
|
||||
Show the mini toolbar when text is selected Prikaži mini traku sa alatkama pri izboru teksta
|
||||
Spaces Razmaci
|
||||
Spelling Pravopis
|
||||
Spelling and grammar are checked in English (United States). Pravopis i gramatika se proveravaju na engleskom (Sjedinjene Države).
|
||||
Tab characters Znakovi tabulatora
|
||||
Tab stop position: Položaj tabulatora:
|
||||
Default tab stops: Podrazumevani tabulatori:
|
||||
Tab stops to be cleared: Tabulatori za brisanje:
|
||||
|
|
@ -209,6 +247,10 @@
|
|||
Set Postavi
|
||||
Page Border Ivica strane
|
||||
Apply to: Primeni na:
|
||||
These marks show even when Show/Hide ¶ is off. Ove oznake se prikazuju i kada je Prikaži/sakrij ¶ isključeno.
|
||||
Typing replaces selected text Otkucano zamenjuje izabrani tekst
|
||||
Update fields before printing Ažuriraj polja pre štampanja
|
||||
Use the Insert key to switch overtype on and off Taster Insert uključuje i isključuje prepisivanje
|
||||
Whole document Ceo dokument
|
||||
This section Ovaj odeljak
|
||||
Fill Ispuna
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
|
|
@ -69,12 +69,14 @@
|
|||
Arrow Стрелица
|
||||
Artistic Effects Уметнички ефекти
|
||||
As Recommended Као што је препоручено
|
||||
As you type Током куцања
|
||||
Asian Typography Азијска типографија
|
||||
Author Аутор
|
||||
AutoCorrect Options Опције аутоматске исправке
|
||||
AutoFit Аутоматско уклапање
|
||||
AutoFit Contents Аутоматски уклопи према садржају
|
||||
AutoFit Window Аутоматски уклопи према прозору
|
||||
AutoSave writes your changes to the file a moment after you stop typing. Аутоматско чување уписује измене у датотеку чим престанете да куцате.
|
||||
Automatic Аутоматски
|
||||
Automatic ({language}) Аутоматски ({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. Аутоматски прати језик система. Менији и наредбе се мењају; документи не.
|
||||
|
|
@ -85,6 +87,8 @@
|
|||
AutoSave failed: {error}. Save the document to turn AutoSave back on. Аутоматско чување неуспело: {error}. Сачувајте документ да бисте га поново укључили.
|
||||
Balloons Облачићи
|
||||
Bottom Margin Доња маргина
|
||||
Centimeters Центиметри
|
||||
Check spelling as you type Проверавај правопис током куцања
|
||||
Click a border between two cells to merge them. Esc stops. Кликните ивицу између две ћелије да их спојите. Esc завршава.
|
||||
Based on: {style} Заснован на: {style}
|
||||
Built-in style Уграђени стил
|
||||
|
|
@ -96,12 +100,14 @@
|
|||
Character style Стил знакова
|
||||
Click an item to paste it. Кликните на ставку да бисте је налепили.
|
||||
Click level to modify: Кликните ниво који мењате:
|
||||
Click to switch back to inserting Кликните да бисте се вратили на уметање
|
||||
Clipboard Items Ставке оставе
|
||||
Clipboard Pane Окно оставе
|
||||
Color only Само боја
|
||||
Color: {color} Боја: {color}
|
||||
Compress punctuation at the start of a line Сажми интерпункцију на почетку реда
|
||||
Confirm password: Потврдите лозинку:
|
||||
Custom dictionary Прилагођени речник
|
||||
Dark page (white text on black) Тамна страница (бео текст на црном)
|
||||
Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. Превуците правоугаоник да нацртате табелу или линију преко ћелија да их поделите. Esc завршава.
|
||||
Draw Table Нацртај табелу
|
||||
|
|
@ -112,23 +118,50 @@
|
|||
Enter formatting for number: Облик броја:
|
||||
Follow number with: После броја:
|
||||
Formatting Обликовање
|
||||
Formatting marks Ознаке обликовања
|
||||
Hidden text Скривени текст
|
||||
Ignore web and file addresses Занемари веб-адресе и путање датотека
|
||||
Ignore words in capitals Занемари речи писане великим словима
|
||||
Ignore words with numbers Занемари речи са бројевима
|
||||
Inches Инчи
|
||||
Formatting exceptions Изузеци форматирања
|
||||
Include level number from: Укључи број нивоа:
|
||||
Initials: Иницијали:
|
||||
Ink Мастило
|
||||
Insertions and Deletions Уметања и брисања
|
||||
Insertions: Уметања:
|
||||
Interface Интерфејс
|
||||
Interface language Језик интерфејса
|
||||
Inside: Унутра:
|
||||
Left border Лева ивица
|
||||
Left Margin Лева маргина
|
||||
Legal style numbering Правно нумерисање
|
||||
Link level to style: Повежи ниво са стилом:
|
||||
List Definition Дефиниција листе
|
||||
Mark grammar errors as you type Означавај граматичке грешке током куцања
|
||||
Measurement units: Јединице мере:
|
||||
Millimeters Милиметри
|
||||
New comments and tracked changes are recorded under this name and these initials. Нови коментари и праћене измене бележе се под овим именом и иницијалима.
|
||||
New word Нова реч
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Још нема речи. Додај у речник на погрешно написаној речи ставља је овде.
|
||||
Mirror indents Пресликај увлачења
|
||||
Nothing Ништа
|
||||
Number alignment: Поравнање броја:
|
||||
Number format Формат броја
|
||||
Number style for this level: Стил броја за овај ниво:
|
||||
Outside border Спољна ивица
|
||||
Overtype Преписивање
|
||||
Overtype (typing replaces the characters after the caret) Преписивање (откуцано замењује знакове после курсора)
|
||||
Page display Приказ стране
|
||||
Paragraph marks Ознаке пасуса
|
||||
Personalize Персонализација
|
||||
Picas Пике
|
||||
Points Тачке
|
||||
Print the page color Штампај боју стране
|
||||
Printing Штампање
|
||||
Proofing language Језик провере писања
|
||||
Recent documents to show: Број недавних докумената за приказ:
|
||||
Remove Уклони
|
||||
Outside: Споља:
|
||||
Reopen Поново отвори
|
||||
Restart list after: Поново почни листу после:
|
||||
|
|
@ -167,6 +200,7 @@
|
|||
Paste Clipboard Item Налепи ставку из оставе
|
||||
Preview Преглед
|
||||
Remove Password Уклони лозинку
|
||||
Save documents Чување докумената
|
||||
Shadow, glow and soft edges Сенка, сјај и меке ивице
|
||||
Shape Effects Ефекти облика
|
||||
Show all revisions inline Прикажи све измене у тексту
|
||||
|
|
@ -175,7 +209,6 @@
|
|||
Distribute spacing Распореди размак
|
||||
Offset (pt): Помак (пт):
|
||||
Phonetic Guide Фонетски водич
|
||||
Remove Уклони
|
||||
Right (vertical) Десно (вертикално)
|
||||
Ruby text: Руби текст:
|
||||
Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Прикажи документе са инвертованим бојама, као код Приказ › Промени режим. Чување, штампање и PDF нису погођени.
|
||||
|
|
@ -203,6 +236,11 @@
|
|||
Border Painter Копирање ивица
|
||||
Border\nPainter Копирање\nивица
|
||||
Borders Ивице
|
||||
Show the mini toolbar when text is selected Прикажи мини траку са алаткама при избору текста
|
||||
Spaces Размаци
|
||||
Spelling Правопис
|
||||
Spelling and grammar are checked in English (United States). Правопис и граматика се проверавају на енглеском (Сједињене Државе).
|
||||
Tab characters Знакови табулатора
|
||||
Tab stop position: Положај табулатора:
|
||||
Default tab stops: Подразумевани табулатори:
|
||||
Tab stops to be cleared: Табулатори за брисање:
|
||||
|
|
@ -213,6 +251,10 @@
|
|||
Set Постави
|
||||
Page Border Ивица стране
|
||||
Apply to: Примени на:
|
||||
These marks show even when Show/Hide ¶ is off. Ове ознаке се приказују и када је Прикажи/сакриј ¶ искључено.
|
||||
Typing replaces selected text Откуцано замењује изабрани текст
|
||||
Update fields before printing Ажурирај поља пре штампања
|
||||
Use the Insert key to switch overtype on and off Тастер Insert укључује и искључује преписивање
|
||||
Whole document Цео документ
|
||||
This section Овај одељак
|
||||
Fill Испуна
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 11 and column 25.
|
|
|
@ -1828,3 +1828,47 @@
|
|||
Turkish Türkçe
|
||||
Ukrainian Ukraynaca
|
||||
{language} is now the document's default language. {language} artık belgenin varsayılan dili.
|
||||
|
||||
# File > Options panes (#485).
|
||||
As you type Yazarken
|
||||
AutoSave writes your changes to the file a moment after you stop typing. Otomatik kaydetme, yazmayı bıraktıktan kısa süre sonra değişikliklerinizi dosyaya yazar.
|
||||
Centimeters Santimetre
|
||||
Check spelling as you type Yazarken yazımı denetle
|
||||
Click to switch back to inserting Eklemeye geri dönmek için tıklayın
|
||||
Custom dictionary Özel sözlük
|
||||
Formatting marks Biçimlendirme işaretleri
|
||||
Hidden text Gizli metin
|
||||
Ignore web and file addresses Web ve dosya adreslerini yoksay
|
||||
Ignore words in capitals Büyük harfli sözcükleri yoksay
|
||||
Ignore words with numbers Rakam içeren sözcükleri yoksay
|
||||
Inches İnç
|
||||
Initials: Baş harfler:
|
||||
Interface Arayüz
|
||||
Interface language Arayüz dili
|
||||
Mark grammar errors as you type Yazarken dil bilgisi hatalarını işaretle
|
||||
Measurement units: Ölçü birimleri:
|
||||
Millimeters Milimetre
|
||||
New comments and tracked changes are recorded under this name and these initials. Yeni açıklamalar ve izlenen değişiklikler bu ad ve baş harflerle kaydedilir.
|
||||
New word Yeni sözcük
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Henüz sözcük yok. Yanlış yazılmış bir sözcükte «Sözlüğe ekle» onu buraya koyar.
|
||||
Overtype Üzerine yaz
|
||||
Overtype (typing replaces the characters after the caret) Üzerine yazma (yazılan metin imleçten sonraki karakterlerin yerine geçer)
|
||||
Page display Sayfa görünümü
|
||||
Paragraph marks Paragraf işaretleri
|
||||
Personalize Kişiselleştir
|
||||
Picas Pika
|
||||
Points Punto
|
||||
Print the page color Sayfa rengini yazdır
|
||||
Printing Yazdırma
|
||||
Proofing language Yazım denetimi dili
|
||||
Recent documents to show: Gösterilecek son belge sayısı:
|
||||
Save documents Belgeleri kaydet
|
||||
Show the mini toolbar when text is selected Metin seçildiğinde mini araç çubuğunu göster
|
||||
Spaces Boşluklar
|
||||
Spelling Yazım
|
||||
Spelling and grammar are checked in English (United States). Yazım ve dil bilgisi İngilizce (Amerika Birleşik Devletleri) için denetlenir.
|
||||
Tab characters Sekme karakterleri
|
||||
These marks show even when Show/Hide ¶ is off. Bu işaretler, Göster/Gizle ¶ kapalıyken de görünür.
|
||||
Typing replaces selected text Yazmak seçili metnin yerine geçer
|
||||
Update fields before printing Yazdırmadan önce alanları güncelle
|
||||
Use the Insert key to switch overtype on and off Üzerine yazmayı açıp kapatmak için Insert tuşunu kullan
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 18.
|
|
|
@ -60,12 +60,14 @@
|
|||
Arrange\nAll Упорядкувати\nвсі
|
||||
Arrow Стрілка
|
||||
Artistic Effects Художні ефекти
|
||||
As you type Під час введення
|
||||
Asian Typography Азійська типографіка
|
||||
Author Автор
|
||||
AutoCorrect Options Параметри автовиправлення
|
||||
AutoFit Добір розміру
|
||||
AutoFit Contents Добір розміру за вмістом
|
||||
AutoFit Window Добір розміру за шириною вікна
|
||||
AutoSave writes your changes to the file a moment after you stop typing. Автозбереження записує зміни у файл невдовзі після того, як ви припините вводити текст.
|
||||
Automatic Автоматично
|
||||
Automatic ({language}) Автоматично ({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. Автоматичний режим використовує мову вашої системи. Змінюються меню й команди, а вміст документів залишається незмінним.
|
||||
|
|
@ -78,34 +80,66 @@
|
|||
Balloons Виноски
|
||||
Bullet Маркер
|
||||
By author За автором
|
||||
Centimeters Сантиметри
|
||||
Changed lines: Змінені рядки:
|
||||
Check spelling as you type Перевіряти правопис під час введення
|
||||
Click level to modify: Виберіть рівень для змінення:
|
||||
Click to switch back to inserting Клацніть, щоб повернутися до вставлення
|
||||
Color only Лише колір
|
||||
Custom dictionary Власний словник
|
||||
Define New Multilevel List Визначити новий багаторівневий список
|
||||
Define New Multilevel List… Визначити новий багаторівневий список…
|
||||
Deletions: Видалення:
|
||||
Enter formatting for number: Вигляд номера:
|
||||
Follow number with: Після номера:
|
||||
Formatting Форматування
|
||||
Formatting marks Знаки форматування
|
||||
Hidden text Прихований текст
|
||||
Ignore web and file addresses Пропускати веб-адреси та шляхи до файлів
|
||||
Ignore words in capitals Пропускати слова з великих літер
|
||||
Ignore words with numbers Пропускати слова з цифрами
|
||||
Inches Дюйми
|
||||
Formatting exceptions Винятки форматування
|
||||
Include level number from: Додати номер рівня:
|
||||
Initials: Ініціали:
|
||||
Ink Рукописні примітки
|
||||
Insertions and Deletions Вставлення й видалення
|
||||
Insertions: Вставлення:
|
||||
Interface Інтерфейс
|
||||
Interface language Мова інтерфейсу
|
||||
Inside: Усередині:
|
||||
Left border Ліва межа
|
||||
Legal style numbering Юридична нумерація
|
||||
Link level to style: Зв'язати рівень зі стилем:
|
||||
List Definition Визначення списку
|
||||
Mark grammar errors as you type Позначати граматичні помилки під час введення
|
||||
Measurement units: Одиниці виміру:
|
||||
Millimeters Міліметри
|
||||
New comments and tracked changes are recorded under this name and these initials. Нові коментарі та відстежувані зміни записуються під цим іменем і цими ініціалами.
|
||||
New word Нове слово
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. Слів ще немає. «Додати до словника» для слова з помилкою додає його сюди.
|
||||
Mirror indents Дзеркальні відступи
|
||||
Nothing Нічого
|
||||
Number alignment: Вирівнювання номера:
|
||||
Number format Формат номера
|
||||
Number style for this level: Стиль номера для цього рівня:
|
||||
Outside border Зовнішня межа
|
||||
Overtype Заміна
|
||||
Overtype (typing replaces the characters after the caret) Заміна (введений текст замінює символи після курсора)
|
||||
Page display Відображення сторінки
|
||||
Paragraph marks Знаки абзацу
|
||||
Personalize Персоналізація
|
||||
Picas Піки
|
||||
Points Пункти
|
||||
Print the page color Друкувати колір сторінки
|
||||
Printing Друк
|
||||
Proofing language Мова перевірки
|
||||
Recent documents to show: Кількість останніх документів:
|
||||
Remove Вилучити
|
||||
Outside: Зовні:
|
||||
Restart list after: Почати список знову після:
|
||||
Right border Права межа
|
||||
Save documents Збереження документів
|
||||
Save the document to turn on AutoSave Збережіть документ, щоб увімкнути автозбереження
|
||||
Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Збережіть документ, щоб увімкнути автозбереження (файл, який ви лише відкрили, не перезаписується, доки ви його не збережете)
|
||||
AutoText Автоматичний текст
|
||||
|
|
@ -128,6 +162,11 @@
|
|||
Border Painter Пензель меж
|
||||
Border\nPainter Пензель\nмеж
|
||||
Borders Межі
|
||||
Show the mini toolbar when text is selected Показувати міні-панель інструментів під час виділення тексту
|
||||
Spaces Пробіли
|
||||
Spelling Правопис
|
||||
Spelling and grammar are checked in English (United States). Правопис і граматика перевіряються англійською (США).
|
||||
Tab characters Символи табуляції
|
||||
Tab stop position: Позиція табуляції:
|
||||
Default tab stops: Типові позиції табуляції:
|
||||
Tab stops to be cleared: Позиції, які буде видалено:
|
||||
|
|
@ -138,6 +177,10 @@
|
|||
Set Встановити
|
||||
Page Border Межа сторінки
|
||||
Apply to: Застосувати до:
|
||||
These marks show even when Show/Hide ¶ is off. Ці знаки відображаються, навіть коли «Показати/приховати ¶» вимкнено.
|
||||
Typing replaces selected text Введення замінює виділений текст
|
||||
Update fields before printing Оновлювати поля перед друком
|
||||
Use the Insert key to switch overtype on and off Клавіша Insert вмикає та вимикає режим заміни
|
||||
Whole document Увесь документ
|
||||
This section Цей розділ
|
||||
Fill Колір заливки
|
||||
|
|
@ -281,7 +324,6 @@
|
|||
Distribute spacing Розподілити інтервали
|
||||
Offset (pt): Зсув (пункти):
|
||||
Phonetic Guide Фонетична підказка
|
||||
Remove Прибрати
|
||||
Right (vertical) Праворуч (вертикально)
|
||||
Ruby text: Текст рубі:
|
||||
Search for templates Пошук шаблонів
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 31.
|
|
|
@ -60,12 +60,14 @@
|
|||
Arrange\nAll 全部重排
|
||||
Arrow 箭头
|
||||
Artistic Effects 艺术效果
|
||||
As you type 键入时
|
||||
Asian Typography 亚洲版式
|
||||
Author 作者
|
||||
AutoCorrect Options 自动更正选项
|
||||
AutoFit 自动调整
|
||||
AutoFit Contents 根据内容自动调整
|
||||
AutoFit Window 根据窗口自动调整
|
||||
AutoSave writes your changes to the file a moment after you stop typing. 停止键入片刻后,自动保存会将更改写入文件。
|
||||
Automatic 自动
|
||||
Automatic ({language}) 自动({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. “自动”跟随系统语言。菜单和命令会改变,文档内容不会改变。
|
||||
|
|
@ -76,13 +78,17 @@
|
|||
AutoSave failed: {error}. Save the document to turn AutoSave back on. 自动保存失败:{error}。重新保存文档后将恢复自动保存。
|
||||
Balloons 批注框
|
||||
Bottom Margin 下边距
|
||||
Centimeters 厘米
|
||||
Check spelling as you type 键入时检查拼写
|
||||
Click a border between two cells to merge them. Esc stops. 单击两个单元格之间的边框以合并它们。按 Esc 结束。
|
||||
Bullet 项目符号
|
||||
By author 按作者
|
||||
Changed lines: 修订行:
|
||||
Click level to modify: 单击要修改的级别:
|
||||
Click to switch back to inserting 单击以切换回插入模式
|
||||
Color only 仅颜色
|
||||
Compress punctuation at the start of a line 压缩行首标点
|
||||
Custom dictionary 自定义词典
|
||||
Dark page (white text on black) 深色页面(黑底白字)
|
||||
Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. 拖出矩形以绘制表格,或拖出穿过单元格的线条以拆分单元格。按 Esc 结束。
|
||||
Draw Table 绘制表格
|
||||
|
|
@ -93,11 +99,20 @@
|
|||
Enter formatting for number: 输入编号的格式:
|
||||
Follow number with: 编号之后:
|
||||
Formatting 格式
|
||||
Formatting marks 格式标记
|
||||
Hidden text 隐藏文字
|
||||
Ignore web and file addresses 忽略网址和文件路径
|
||||
Ignore words in capitals 忽略全部大写的单词
|
||||
Ignore words with numbers 忽略包含数字的单词
|
||||
Inches 英寸
|
||||
Formatting exceptions 格式例外
|
||||
Include level number from: 包含的级别编号来自:
|
||||
Initials: 缩写:
|
||||
Ink 墨迹
|
||||
Insertions and Deletions 插入和删除
|
||||
Insertions: 插入内容:
|
||||
Interface 界面
|
||||
Interface language 界面语言
|
||||
Inside: 内侧:
|
||||
Left border 左框线
|
||||
Left Margin 左边距
|
||||
|
|
@ -106,14 +121,32 @@
|
|||
Line breaking 断行
|
||||
Link level to style: 将级别链接到样式:
|
||||
List Definition 列表定义
|
||||
Mark grammar errors as you type 键入时标记语法错误
|
||||
Measurement units: 度量单位:
|
||||
Millimeters 毫米
|
||||
Mirror indents 对称缩进
|
||||
More templates → 更多模板 →
|
||||
New comments and tracked changes are recorded under this name and these initials. 新批注和修订将以此名称和缩写记录。
|
||||
New word 新单词
|
||||
No templates match your search. 没有与搜索匹配的模板。
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. 还没有单词。对拼写错误的单词选择“添加到词典”会将其放在这里。
|
||||
Nothing 无
|
||||
Number alignment: 编号对齐方式:
|
||||
Number format 编号格式
|
||||
Number style for this level: 此级别的编号样式:
|
||||
Outside border 外侧框线
|
||||
Overtype 改写
|
||||
Overtype (typing replaces the characters after the caret) 改写(键入的内容替换插入点后的字符)
|
||||
Page display 页面显示
|
||||
Paragraph marks 段落标记
|
||||
Personalize 个性化
|
||||
Picas 派卡
|
||||
Points 磅
|
||||
Print the page color 打印页面颜色
|
||||
Printing 打印
|
||||
Proofing language 校对语言
|
||||
Recent documents to show: 显示的最近文档数:
|
||||
Remove 移除
|
||||
Outside: 外侧:
|
||||
Reopen 重新打开
|
||||
Restart list after: 重新开始列表的级别:
|
||||
|
|
@ -124,9 +157,9 @@
|
|||
Distribute spacing 间距均匀分布
|
||||
Offset (pt): 偏移量(磅):
|
||||
Phonetic Guide 拼音指南
|
||||
Remove 删除
|
||||
Right (vertical) 右对齐(竖排)
|
||||
Ruby text: 拼音文字:
|
||||
Save documents 保存文档
|
||||
Search for templates 搜索模板
|
||||
Show all revisions inline 以嵌入方式显示所有修订
|
||||
Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. 像“视图 › 切换模式”一样以反转的颜色显示文档。保存、打印和 PDF 不受影响。
|
||||
|
|
@ -154,6 +187,11 @@
|
|||
Border Painter 边框刷
|
||||
Border\nPainter 边框刷
|
||||
Borders 边框
|
||||
Show the mini toolbar when text is selected 选择文本时显示浮动工具栏
|
||||
Spaces 空格
|
||||
Spelling 拼写
|
||||
Spelling and grammar are checked in English (United States). 拼写和语法按英语(美国)检查。
|
||||
Tab characters 制表符
|
||||
Tab stop position: 制表位位置:
|
||||
Default tab stops: 默认制表位:
|
||||
Tab stops to be cleared: 要清除的制表位:
|
||||
|
|
@ -164,6 +202,10 @@
|
|||
Set 设置
|
||||
Page Border 页面边框线
|
||||
Apply to: 应用于:
|
||||
These marks show even when Show/Hide ¶ is off. 即使关闭“显示/隐藏 ¶”,这些标记也会显示。
|
||||
Typing replaces selected text 键入内容替换所选文字
|
||||
Update fields before printing 打印前更新域
|
||||
Use the Insert key to switch overtype on and off 用 Insert 键切换改写模式
|
||||
Whole document 整篇文档
|
||||
This section 本节
|
||||
Fill 填充
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
|
|
@ -49,12 +49,14 @@
|
|||
Arrange\nAll 全部排列
|
||||
Arrow 箭號
|
||||
Artistic Effects 美術效果
|
||||
As you type 輸入時
|
||||
Asian Typography 亞洲版式
|
||||
Author 作者
|
||||
AutoCorrect Options 自動校正選項
|
||||
AutoFit 自動調整
|
||||
AutoFit Contents 依內容自動調整
|
||||
AutoFit Window 依視窗自動調整
|
||||
AutoSave writes your changes to the file a moment after you stop typing. 停止輸入片刻後,自動儲存會將變更寫入檔案。
|
||||
Automatic 自動
|
||||
Automatic ({language}) 自動({language})
|
||||
Automatic follows your system's language. Menus and commands change; your documents don't. 「自動」會跟隨系統語言。功能表與命令會改變,文件內容不會改變。
|
||||
|
|
@ -65,13 +67,17 @@
|
|||
AutoSave failed: {error}. Save the document to turn AutoSave back on. 自動儲存失敗:{error}。重新儲存文件後將恢復自動儲存。
|
||||
Balloons 註解方塊
|
||||
Bottom Margin 下邊界
|
||||
Centimeters 公分
|
||||
Check spelling as you type 輸入時檢查拼字
|
||||
Click a border between two cells to merge them. Esc stops. 按一下兩個儲存格之間的框線以合併它們。按 Esc 結束。
|
||||
Bullet 項目符號
|
||||
By author 依作者
|
||||
Changed lines: 修訂行:
|
||||
Click level to modify: 按一下要修改的層級:
|
||||
Click to switch back to inserting 按一下以切換回插入模式
|
||||
Color only 僅色彩
|
||||
Compress punctuation at the start of a line 壓縮行首標點
|
||||
Custom dictionary 自訂字典
|
||||
Dark page (white text on black) 深色頁面(黑底白字)
|
||||
Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. 拖出矩形以繪製表格,或拖出穿過儲存格的線條以分割儲存格。按 Esc 結束。
|
||||
Draw Table 繪製表格
|
||||
|
|
@ -82,11 +88,20 @@
|
|||
Enter formatting for number: 輸入編號的格式:
|
||||
Follow number with: 編號之後:
|
||||
Formatting 格式設定
|
||||
Formatting marks 格式化標記
|
||||
Hidden text 隱藏文字
|
||||
Ignore web and file addresses 忽略網址和檔案路徑
|
||||
Ignore words in capitals 忽略全部大寫的單字
|
||||
Ignore words with numbers 忽略包含數字的單字
|
||||
Inches 英寸
|
||||
Formatting exceptions 格式例外
|
||||
Include level number from: 包含的層級編號來自:
|
||||
Initials: 縮寫:
|
||||
Ink 筆跡
|
||||
Insertions and Deletions 插入和刪除
|
||||
Insertions: 插入內容:
|
||||
Interface 介面
|
||||
Interface language 介面語言
|
||||
Inside: 內側:
|
||||
Left border 左框線
|
||||
Left Margin 左邊界
|
||||
|
|
@ -95,14 +110,32 @@
|
|||
Line breaking 斷行
|
||||
Link level to style: 將層級連結到樣式:
|
||||
List Definition 清單定義
|
||||
Mark grammar errors as you type 輸入時標示文法錯誤
|
||||
Measurement units: 度量單位:
|
||||
Millimeters 公釐
|
||||
Mirror indents 對稱縮排
|
||||
More templates → 更多範本 →
|
||||
New comments and tracked changes are recorded under this name and these initials. 新的註解和追蹤修訂會以此名稱和縮寫記錄。
|
||||
New word 新單字
|
||||
No templates match your search. 沒有符合搜尋的範本。
|
||||
No words yet. Add to Dictionary on a misspelled word puts it here. 還沒有單字。對拼字錯誤的單字選擇「新增至字典」會將它放在這裡。
|
||||
Nothing 無
|
||||
Number alignment: 編號對齊方式:
|
||||
Number format 編號格式
|
||||
Number style for this level: 此層級的編號樣式:
|
||||
Outside border 外側框線
|
||||
Overtype 取代
|
||||
Overtype (typing replaces the characters after the caret) 取代(輸入的內容會取代插入點後的字元)
|
||||
Page display 頁面顯示
|
||||
Paragraph marks 段落標記
|
||||
Personalize 個人化
|
||||
Picas 派卡
|
||||
Points 點
|
||||
Print the page color 列印頁面色彩
|
||||
Printing 列印
|
||||
Proofing language 校訂語言
|
||||
Recent documents to show: 顯示的最近文件數:
|
||||
Remove 移除
|
||||
Outside: 外側:
|
||||
Reopen 重新開啟
|
||||
Restart list after: 重新開始清單的層級:
|
||||
|
|
@ -113,9 +146,9 @@
|
|||
Distribute spacing 間距平均分散
|
||||
Offset (pt): 位移(點):
|
||||
Phonetic Guide 注音標示
|
||||
Remove 移除
|
||||
Right (vertical) 靠右(直書)
|
||||
Ruby text: 注音文字:
|
||||
Save documents 儲存文件
|
||||
Search for templates 搜尋範本
|
||||
Show all revisions inline 以內嵌方式顯示所有修訂
|
||||
Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. 像「檢視 › 切換模式」一樣以反轉的色彩顯示文件。儲存、列印和 PDF 不受影響。
|
||||
|
|
@ -143,6 +176,11 @@
|
|||
Border Painter 框線複製
|
||||
Border\nPainter 框線複製
|
||||
Borders 框線
|
||||
Show the mini toolbar when text is selected 選取文字時顯示迷你工具列
|
||||
Spaces 空格
|
||||
Spelling 拼字
|
||||
Spelling and grammar are checked in English (United States). 拼字和文法依英文(美國)檢查。
|
||||
Tab characters 定位字元
|
||||
Tab stop position: 定位點位置:
|
||||
Default tab stops: 預設定位點間距:
|
||||
Tab stops to be cleared: 要清除的定位點:
|
||||
|
|
@ -153,6 +191,10 @@
|
|||
Set 設定
|
||||
Page Border 頁面框線設定
|
||||
Apply to: 套用至:
|
||||
These marks show even when Show/Hide ¶ is off. 即使關閉「顯示/隱藏 ¶」,這些標記也會顯示。
|
||||
Typing replaces selected text 輸入的內容取代選取的文字
|
||||
Update fields before printing 列印前更新功能變數
|
||||
Use the Insert key to switch overtype on and off 使用 Insert 鍵切換取代模式
|
||||
Whole document 整份文件
|
||||
This section 本節
|
||||
Fill 填滿
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 4.
|
|
|
@ -271,6 +271,13 @@ pub fn canvas_events(app: &mut WordApp, ctx: &egui::Context) {
|
|||
if key == Key::Escape && crate::table_pen::stop(app) {
|
||||
continue;
|
||||
}
|
||||
// File › Options › Advanced: the Insert key switches overtype only when asked to.
|
||||
if key == Key::Insert && modifiers.is_none() {
|
||||
if app.session.prefs.insert_key_overtype {
|
||||
let _ = app.run("text.overtype", json!({}));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if crate::objects::key(app, key, modifiers) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub mod keys;
|
|||
pub mod keytips;
|
||||
pub mod mini_toolbar;
|
||||
pub mod objects;
|
||||
pub mod options;
|
||||
pub mod panes;
|
||||
pub mod paste_picture;
|
||||
pub mod previews;
|
||||
|
|
@ -151,8 +152,28 @@ pub struct UiState {
|
|||
/// Entries that aren't pens are dropped on reading.
|
||||
#[serde(deserialize_with = "wordcraft_engine::cmd::draw::lenient_pens")]
|
||||
pub ink_pens: Vec<wordcraft_engine::cmd::draw::CustomPen>,
|
||||
/// File › Options › General: initials for comments; empty = the user name's. The session
|
||||
/// owns them while running, like `author`.
|
||||
pub initials: String,
|
||||
/// The File › Options pane last shown ([`wordcraft_engine::cmd::options::PANES`]).
|
||||
pub options_pane: String,
|
||||
/// File › Options › Advanced: how many recent documents File › Home and Open list (0–50).
|
||||
pub recent_count: usize,
|
||||
/// File › Options › General: show the mini toolbar over a selection.
|
||||
pub mini_toolbar: bool,
|
||||
/// File › Options › Display: print the page colour (off, as in Word).
|
||||
pub print_backgrounds: bool,
|
||||
/// File › Options › Display: update fields (dates, page numbers, SEQ…) before printing.
|
||||
pub update_fields_before_print: bool,
|
||||
/// The custom dictionary (File › Options › Proofing, Add to Dictionary), kept between runs.
|
||||
pub dictionary: Vec<String>,
|
||||
}
|
||||
|
||||
/// Most recent documents kept (File › Options › Advanced offers 0–50, as Word does).
|
||||
pub const MAX_RECENT: usize = 50;
|
||||
/// Most custom dictionary words read back from `ui.json`.
|
||||
pub const MAX_DICTIONARY: usize = 10_000;
|
||||
|
||||
impl Default for UiState {
|
||||
fn default() -> Self {
|
||||
UiState {
|
||||
|
|
@ -180,10 +201,32 @@ impl Default for UiState {
|
|||
ribbon: wordcraft_engine::RibbonLayout::default(),
|
||||
caption_labels: Vec::new(),
|
||||
ink_pens: Vec::new(),
|
||||
initials: String::new(),
|
||||
options_pane: "general".into(),
|
||||
recent_count: 12,
|
||||
mini_toolbar: true,
|
||||
print_backgrounds: false,
|
||||
update_fields_before_print: false,
|
||||
dictionary: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UiState {
|
||||
/// Read `ui.json` leniently: a setting with a value of the wrong type (or a newer or older
|
||||
/// version's idea of it) falls back to its default instead of discarding every setting.
|
||||
/// `None` only when the file isn't a JSON object.
|
||||
pub fn from_json(bytes: &[u8]) -> Option<UiState> {
|
||||
let v: Value = serde_json::from_slice(bytes).ok()?;
|
||||
v.is_object().then(|| options::lenient(&v))
|
||||
}
|
||||
|
||||
/// How many recent documents to list.
|
||||
pub fn recent_shown(&self) -> usize {
|
||||
self.recent_count.min(MAX_RECENT)
|
||||
}
|
||||
}
|
||||
|
||||
/// The application.
|
||||
pub struct WordApp {
|
||||
pub session: Session,
|
||||
|
|
@ -323,6 +366,8 @@ impl WordApp {
|
|||
pub fn prefs(&self) -> UiState {
|
||||
let mut ui = self.ui.clone();
|
||||
ui.author = self.session.author.clone();
|
||||
ui.initials = self.session.initials.clone();
|
||||
ui.dictionary = wordcraft_engine::proof::user_dictionary();
|
||||
ui.editing = self.session.prefs.clone();
|
||||
ui.read_aloud_rate = self.session.read_aloud.rate();
|
||||
ui.read_aloud_skip_citations = self.session.read_aloud.skip_citations;
|
||||
|
|
@ -346,6 +391,13 @@ impl WordApp {
|
|||
if !author.trim().is_empty() {
|
||||
self.session.author = author;
|
||||
}
|
||||
self.session.initials = std::mem::take(&mut self.ui.initials).chars().take(wordcraft_engine::cmd::options::MAX_INITIALS_CHARS).collect();
|
||||
for w in std::mem::take(&mut self.ui.dictionary).iter().take(MAX_DICTIONARY) {
|
||||
let w = w.trim();
|
||||
if !w.is_empty() && w.chars().count() <= wordcraft_engine::cmd::options::MAX_WORD_CHARS {
|
||||
wordcraft_engine::proof::add_word(w);
|
||||
}
|
||||
}
|
||||
self.session.prefs = std::mem::take(&mut self.ui.editing);
|
||||
self.session.read_aloud.set_rate(self.ui.read_aloud_rate);
|
||||
self.session.read_aloud.skip_citations = self.ui.read_aloud_skip_citations;
|
||||
|
|
@ -563,6 +615,7 @@ impl WordApp {
|
|||
}
|
||||
|
||||
fn after_command(&mut self, id: &str) {
|
||||
options::sync(self);
|
||||
self.canvas.caret_visible_since = now_ms();
|
||||
if !id.starts_with("view.") && !id.starts_with("document.") && !id.starts_with("format.state") {
|
||||
self.canvas.scroll_to_caret = true;
|
||||
|
|
@ -575,7 +628,7 @@ impl WordApp {
|
|||
{
|
||||
self.ui.recent.retain(|r| *r != p);
|
||||
self.ui.recent.insert(0, p);
|
||||
self.ui.recent.truncate(12);
|
||||
self.ui.recent.truncate(MAX_RECENT);
|
||||
}
|
||||
if !self.session.status.is_empty() {
|
||||
let s = std::mem::take(&mut self.session.status);
|
||||
|
|
@ -599,6 +652,9 @@ impl WordApp {
|
|||
"options" => {
|
||||
self.ui.backstage = true;
|
||||
self.ui.backstage_page = "options".into();
|
||||
if let Some(pane) = req.get("pane").and_then(Value::as_str) {
|
||||
self.ui.options_pane = pane.into();
|
||||
}
|
||||
}
|
||||
"pasteSpecial" => self.dialog = Some(dialogs::Dialog::paste_special(self, req)),
|
||||
other => self.dialog = dialogs::Dialog::open(other, self),
|
||||
|
|
@ -633,6 +689,9 @@ impl WordApp {
|
|||
if let Some(pg) = s("page") {
|
||||
self.ui.backstage_page = pg.into();
|
||||
}
|
||||
if let Some(pane) = s("pane") {
|
||||
self.ui.options_pane = pane.into();
|
||||
}
|
||||
json!({"backstage": self.ui.backstage})
|
||||
}
|
||||
"ui.dialog" => {
|
||||
|
|
@ -745,7 +804,18 @@ impl WordApp {
|
|||
if self.services.print.is_none() {
|
||||
return Err("printing to the system print dialog isn't available here; export a PDF instead".into());
|
||||
}
|
||||
let result = wordcraft_engine::io::save_bytes("document.pdf", &self.session.doc)
|
||||
// File › Options › Display: fields first, and the page colour only when asked for.
|
||||
if self.ui.update_fields_before_print
|
||||
&& let Err(e) = self.session.run("references.updateFields", &json!({}))
|
||||
{
|
||||
log::warn!("updating fields before printing: {e}");
|
||||
}
|
||||
let without_colour = (!self.ui.print_backgrounds && self.session.doc.settings.page_color.is_some()).then(|| {
|
||||
let mut d = self.session.doc.clone();
|
||||
d.settings.page_color = None;
|
||||
d
|
||||
});
|
||||
let result = wordcraft_engine::io::save_bytes("document.pdf", without_colour.as_ref().unwrap_or(&self.session.doc))
|
||||
.and_then(|bytes| self.services.print.as_ref().map_or(Ok(()), |print| print(&bytes)).map(|()| bytes.len()));
|
||||
match result {
|
||||
Ok(len) => {
|
||||
|
|
@ -854,6 +924,7 @@ impl WordApp {
|
|||
pub fn logic(&mut self, ctx: &egui::Context) {
|
||||
let lang = i18n::Lang::from_pref(&self.ui.language);
|
||||
i18n::set_current(lang);
|
||||
options::sync(self);
|
||||
// Chinese text wants the Chinese face before the Japanese one (one glyph style per line).
|
||||
// An installed CJK font is read only once CJK text is shown (#241): never for an English
|
||||
// interface that doesn't open the language list.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,521 @@
|
|||
//! File › Options (#485): a list of panes on the left (General, Display, Proofing, Save,
|
||||
//! Language, Accessibility, Advanced, Agents), the chosen pane's settings on the right. Only
|
||||
//! settings WordCraft carries out are shown. Each pane is one function below, so a new setting
|
||||
//! goes into its pane's function. Settings agents can change too go through `file.options`; the
|
||||
//! rest live in [`crate::UiState`] (saved in `ui.json`).
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use egui::{RichText, Ui, vec2};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Map, Value, json};
|
||||
use wordcraft_engine::cmd::options::{MAX_WORD_CHARS, PANES};
|
||||
use wordcraft_geom::Unit;
|
||||
|
||||
use crate::WordApp;
|
||||
use crate::theme::{Tokens, semibold};
|
||||
|
||||
thread_local! {
|
||||
// The unit dialogs and the ribbon show lengths in: File › Options › Advanced, copied from the
|
||||
// session each frame so dialog code without the app can read it.
|
||||
static UNIT: Cell<Unit> = const { Cell::new(Unit::Inches) };
|
||||
}
|
||||
|
||||
/// The unit lengths are shown in (File › Options › Advanced › Measurement units).
|
||||
pub fn unit() -> Unit {
|
||||
UNIT.get()
|
||||
}
|
||||
|
||||
/// Copy the session's settings the UI reads without the app.
|
||||
pub(crate) fn sync(app: &WordApp) {
|
||||
UNIT.set(app.session.prefs.units);
|
||||
}
|
||||
|
||||
/// The units offered, with their names.
|
||||
pub const UNITS: [(Unit, &str); 5] = [
|
||||
(Unit::Inches, "Inches"),
|
||||
(Unit::Centimeters, "Centimeters"),
|
||||
(Unit::Millimeters, "Millimeters"),
|
||||
(Unit::Points, "Points"),
|
||||
(Unit::Picas, "Picas"),
|
||||
];
|
||||
|
||||
/// The pane names, in [`PANES`] order.
|
||||
fn pane_label(id: &str) -> &'static str {
|
||||
match id {
|
||||
"display" => "Display",
|
||||
"proofing" => "Proofing",
|
||||
"save" => "Save",
|
||||
"language" => "Language",
|
||||
"accessibility" => "Accessibility",
|
||||
"advanced" => "Advanced",
|
||||
"agents" => "Agents",
|
||||
_ => "General",
|
||||
}
|
||||
}
|
||||
|
||||
/// The Options page.
|
||||
pub fn page(app: &mut WordApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.label(RichText::new(tl!("Options")).font(semibold(26.0)));
|
||||
ui.add_space(16.0);
|
||||
let current = if PANES.contains(&app.ui.options_pane.as_str()) { app.ui.options_pane.clone() } else { "general".to_string() };
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(170.0);
|
||||
for id in PANES {
|
||||
let label = tl!(pane_label(id));
|
||||
let active = current == id;
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(170.0, 30.0), egui::Sense::click());
|
||||
resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::SelectableLabel, true, active, label));
|
||||
if active {
|
||||
ui.painter().rect_filled(r, 4.0, t.checked);
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(r, 4.0, t.hover);
|
||||
}
|
||||
let font = if active { semibold(14.0) } else { crate::theme::regular(14.0) };
|
||||
ui.painter().text(egui::pos2(r.min.x + 12.0, r.center().y), egui::Align2::LEFT_CENTER, label, font, t.text);
|
||||
if resp.clicked() {
|
||||
app.ui.options_pane = id.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
let (line, _) = ui.allocate_exact_size(vec2(1.0, 420.0), egui::Sense::hover());
|
||||
ui.painter().rect_filled(line, 0.0, t.border);
|
||||
ui.add_space(20.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.set_max_width(640.0);
|
||||
match current.as_str() {
|
||||
"display" => display(app, ui),
|
||||
"proofing" => proofing(app, ui),
|
||||
"save" => save(app, ui),
|
||||
"language" => language(app, ui),
|
||||
"accessibility" => accessibility(app, ui),
|
||||
"advanced" => advanced(app, ui),
|
||||
"agents" => agents(ui),
|
||||
_ => general(app, ui),
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn section(ui: &mut Ui, title: &str) {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(tl!(title)).font(semibold(15.0)));
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
fn note(ui: &mut Ui, text: &str) {
|
||||
ui.label(RichText::new(tl!(text)).small().weak());
|
||||
}
|
||||
|
||||
/// Change settings through `file.options` (the command agents use).
|
||||
fn set(app: &mut WordApp, params: Value) {
|
||||
if let Err(e) = app.run("file.options", params) {
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// A checkbox for a `file.options` on/off setting.
|
||||
fn flag(app: &mut WordApp, ui: &mut Ui, label: &str, key: &str, value: bool) {
|
||||
let mut v = value;
|
||||
if ui.checkbox(&mut v, tl!(label)).changed() {
|
||||
set(app, json!({ key: v }));
|
||||
}
|
||||
}
|
||||
|
||||
fn general(app: &mut WordApp, ui: &mut Ui) {
|
||||
section(ui, "Interface");
|
||||
theme_picker(app, ui);
|
||||
interface_size_picker(app, ui);
|
||||
ui.checkbox(&mut app.ui.mini_toolbar, tl!("Show the mini toolbar when text is selected"));
|
||||
let mut dark_page = app.session.view.dark_mode;
|
||||
if ui
|
||||
.checkbox(&mut dark_page, tl!("Dark page (white text on black)"))
|
||||
.on_hover_text(tl!("Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged."))
|
||||
.changed()
|
||||
{
|
||||
let _ = app.run("view.darkMode", json!({"value": dark_page}));
|
||||
}
|
||||
ui.checkbox(&mut app.ui.show_discord, tl!("Show the community button in the title bar"));
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Personalize");
|
||||
egui::Grid::new("options_user").num_columns(2).spacing(vec2(8.0, 6.0)).show(ui, |ui| {
|
||||
ui.label(tl!("User name:"));
|
||||
let mut n = app.session.author.clone();
|
||||
if ui.add(egui::TextEdit::singleline(&mut n).desired_width(260.0)).changed() {
|
||||
app.session.author = n;
|
||||
}
|
||||
ui.end_row();
|
||||
ui.label(tl!("Initials:"));
|
||||
let mut i = app.session.initials.clone();
|
||||
let hint = app.session.user_initials();
|
||||
if ui
|
||||
.add(
|
||||
egui::TextEdit::singleline(&mut i).desired_width(80.0).char_limit(wordcraft_engine::cmd::options::MAX_INITIALS_CHARS).hint_text(hint),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
set(app, json!({"initials": i}));
|
||||
}
|
||||
ui.end_row();
|
||||
});
|
||||
note(ui, "New comments and tracked changes are recorded under this name and these initials.");
|
||||
}
|
||||
|
||||
fn display(app: &mut WordApp, ui: &mut Ui) {
|
||||
section(ui, "Formatting marks");
|
||||
note(ui, "These marks show even when Show/Hide ¶ is off.");
|
||||
let m = app.session.prefs.marks;
|
||||
for (label, key, on) in [
|
||||
("Tab characters", "tabs", m.tabs),
|
||||
("Spaces", "spaces", m.spaces),
|
||||
("Paragraph marks", "paragraphs", m.paragraphs),
|
||||
("Hidden text", "hidden", m.hidden),
|
||||
] {
|
||||
let mut v = on;
|
||||
if ui.checkbox(&mut v, tl!(label)).changed() {
|
||||
set(app, json!({"marks": { key: v }}));
|
||||
}
|
||||
}
|
||||
let mut marks = app.session.view.marks;
|
||||
if ui.checkbox(&mut marks, tl!("Show all formatting marks")).changed() {
|
||||
let _ = app.run("view.marks", json!({"value": marks}));
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Page display");
|
||||
let mut ruler = app.session.view.ruler;
|
||||
if ui.checkbox(&mut ruler, tl!("Show rulers")).changed() {
|
||||
let _ = app.run("view.ruler", json!({"value": ruler}));
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Printing");
|
||||
ui.checkbox(&mut app.ui.print_backgrounds, tl!("Print the page color"));
|
||||
ui.checkbox(&mut app.ui.update_fields_before_print, tl!("Update fields before printing"));
|
||||
}
|
||||
|
||||
fn proofing(app: &mut WordApp, ui: &mut Ui) {
|
||||
// #476 (AutoCorrect Options…) goes here, at the top.
|
||||
section(ui, "Spelling");
|
||||
let pr = app.session.prefs.clone();
|
||||
flag(app, ui, "Ignore words in capitals", "ignoreUppercase", pr.ignore_uppercase);
|
||||
flag(app, ui, "Ignore words with numbers", "ignoreNumbers", pr.ignore_numbers);
|
||||
flag(app, ui, "Ignore web and file addresses", "ignoreInternet", pr.ignore_internet);
|
||||
ui.add_space(8.0);
|
||||
section(ui, "As you type");
|
||||
let spelling = app.session.view.proofing;
|
||||
flag(app, ui, "Check spelling as you type", "checkSpelling", spelling);
|
||||
ui.add_enabled_ui(app.session.view.proofing, |ui| flag(app, ui, "Mark grammar errors as you type", "markGrammar", pr.mark_grammar));
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Custom dictionary");
|
||||
custom_dictionary(app, ui);
|
||||
}
|
||||
|
||||
/// The custom dictionary: the words Add to Dictionary kept, each with a Remove button, and a
|
||||
/// field to add one.
|
||||
fn custom_dictionary(app: &mut WordApp, ui: &mut Ui) {
|
||||
let words = wordcraft_engine::proof::user_dictionary();
|
||||
if words.is_empty() {
|
||||
note(ui, "No words yet. Add to Dictionary on a misspelled word puts it here.");
|
||||
}
|
||||
egui::ScrollArea::vertical().id_salt("options_dictionary").max_height(180.0).show(ui, |ui| {
|
||||
for w in &words {
|
||||
ui.horizontal(|ui| {
|
||||
ui.allocate_ui_with_layout(vec2(220.0, 20.0), egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
||||
ui.set_min_width(220.0);
|
||||
ui.add(egui::Label::new(w.as_str()).truncate());
|
||||
});
|
||||
if ui.small_button(tl!("Remove")).clicked() {
|
||||
set(app, json!({"removeWords": [w]}));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let id = egui::Id::new("options_dictionary_new");
|
||||
let mut word = ui.data(|d| d.get_temp::<String>(id)).unwrap_or_default();
|
||||
ui.horizontal(|ui| {
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut word).desired_width(220.0).char_limit(MAX_WORD_CHARS).hint_text(tl!("New word")));
|
||||
let enter = r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
let w = word.trim().to_string();
|
||||
let ok = !w.is_empty() && !w.chars().any(char::is_whitespace);
|
||||
if (ui.add_enabled(ok, egui::Button::new(tl!("Add"))).clicked() || (enter && ok)) && app.run("file.options", json!({"addWords": [w]})).is_ok()
|
||||
{
|
||||
word.clear();
|
||||
}
|
||||
});
|
||||
ui.data_mut(|d| d.insert_temp(id, word));
|
||||
}
|
||||
|
||||
fn save(app: &mut WordApp, ui: &mut Ui) {
|
||||
section(ui, "Save documents");
|
||||
// The browser can't write to the user's files, so AutoSave can't be turned on there (#176).
|
||||
let browser = app.autosave_block() == Some(crate::AutoSaveBlock::Browser);
|
||||
let mut autosave = app.autosave && !browser;
|
||||
let r = ui.add_enabled(!browser, egui::Checkbox::new(&mut autosave, tl!("AutoSave documents you have saved in WordCraft")));
|
||||
if browser {
|
||||
r.on_disabled_hover_text(crate::AutoSaveBlock::Browser.reason());
|
||||
} else if r.changed() {
|
||||
app.autosave = autosave;
|
||||
app.session.autosave = autosave;
|
||||
}
|
||||
note(ui, "AutoSave writes your changes to the file a moment after you stop typing.");
|
||||
}
|
||||
|
||||
fn language(app: &mut WordApp, ui: &mut Ui) {
|
||||
section(ui, "Interface language");
|
||||
language_picker(app, ui);
|
||||
// #414 (detect the document's language) goes here.
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Proofing language");
|
||||
note(ui, "Spelling and grammar are checked in English (United States).");
|
||||
}
|
||||
|
||||
fn accessibility(app: &mut WordApp, ui: &mut Ui) {
|
||||
use wordcraft_engine::speech::{MAX_RATE, MIN_RATE};
|
||||
section(ui, "Read Aloud");
|
||||
let mut rate = app.session.read_aloud.rate();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Speed"));
|
||||
let r = ui.add(egui::Slider::new(&mut rate, MIN_RATE..=MAX_RATE).step_by(0.05).custom_formatter(|v, _| format!("{v:.2}×")));
|
||||
if r.changed() {
|
||||
let _ = app.run("readAloud.speed", json!({"value": rate}));
|
||||
}
|
||||
});
|
||||
let mut skip = app.session.read_aloud.skip_citations;
|
||||
if ui.checkbox(&mut skip, tl!("Skip citations & bibliography")).changed() {
|
||||
let _ = app.run("readAloud.skipCitations", json!({"value": skip}));
|
||||
}
|
||||
}
|
||||
|
||||
fn advanced(app: &mut WordApp, ui: &mut Ui) {
|
||||
section(ui, "Editing");
|
||||
let pr = app.session.prefs.clone();
|
||||
flag(app, ui, "Typing replaces selected text", "typingReplacesSelection", pr.typing_replaces_selection);
|
||||
flag(app, ui, "Use the Insert key to switch overtype on and off", "insertKeyOvertype", pr.insert_key_overtype);
|
||||
flag(app, ui, "Overtype (typing replaces the characters after the caret)", "overtype", pr.overtype);
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Display");
|
||||
egui::Grid::new("options_display").num_columns(2).spacing(vec2(8.0, 6.0)).show(ui, |ui| {
|
||||
ui.label(tl!("Recent documents to show:"));
|
||||
let mut n = app.ui.recent_shown();
|
||||
if ui.add(egui::DragValue::new(&mut n).range(0..=crate::MAX_RECENT).speed(0.2)).changed() {
|
||||
app.ui.recent_count = n;
|
||||
}
|
||||
ui.end_row();
|
||||
ui.label(tl!("Measurement units:"));
|
||||
let name = UNITS.iter().find(|(u, _)| *u == pr.units).map_or("Inches", |(_, n)| n);
|
||||
egui::ComboBox::from_id_salt("options_units").selected_text(tl!(name)).width(160.0).show_ui(ui, |ui| {
|
||||
for (u, label) in UNITS {
|
||||
if ui.selectable_label(pr.units == u, tl!(label)).clicked() {
|
||||
set(app, json!({"units": u}));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
section(ui, "Customize");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Keyboard shortcuts:"));
|
||||
if ui.button(tl!("Customize…")).clicked() {
|
||||
let _ = app.run("tools.customizeKeyboard", json!({}));
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Ribbon and Quick Access Toolbar:"));
|
||||
if ui.button(tl!("Customize…")).clicked() {
|
||||
let _ = app.run("tools.customizeRibbon", json!({}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn agents(ui: &mut Ui) {
|
||||
section(ui, "Agents");
|
||||
ui.label(tl!("Every command is available to scripts and AI agents: run `wordcraft-cli mcp` for an MCP server, or start the app with `--control <port>` for the JSON control channel."));
|
||||
}
|
||||
|
||||
/// Interface theme: Light, Dark, or follow the system's appearance (#115).
|
||||
fn theme_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::theme::Appearance;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Interface theme:"));
|
||||
egui::ComboBox::from_id_salt("interface_theme").selected_text(tl!(app.ui.theme.label())).width(220.0).show_ui(ui, |ui| {
|
||||
for a in Appearance::ALL {
|
||||
if ui.selectable_label(app.ui.theme == a, tl!(a.label())).clicked() {
|
||||
let _ = app.run("ui.theme", json!({"value": a.code()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Interface size: 80–200 %, on top of the system's display scale (#475).
|
||||
fn interface_size_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::theme::InterfaceSize;
|
||||
let current = app.ui.interface_size.percent();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Interface size:"));
|
||||
egui::ComboBox::from_id_salt("interface_size").selected_text(format!("{current}%")).width(220.0).show_ui(ui, |ui| {
|
||||
for pct in InterfaceSize::PRESETS {
|
||||
if ui.selectable_label(current == pct, format!("{pct}%")).clicked() {
|
||||
let _ = app.run("ui.interfaceSize", json!({"percent": pct}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.label(
|
||||
RichText::new(tl!(
|
||||
"Makes the ribbon, menus and dialogs larger or smaller, on top of your system's display scaling. Document zoom is separate."
|
||||
))
|
||||
.small()
|
||||
.weak(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Interface language: follow the system (the default) or pick one (#8).
|
||||
fn language_picker(app: &mut WordApp, ui: &mut Ui) {
|
||||
use crate::i18n::{AUTO, Lang};
|
||||
// The language names are in their own scripts: fonts for them load from the next frame.
|
||||
app.want_system_cjk = true;
|
||||
let system = crate::i18n::system_lang();
|
||||
let auto_label = crate::i18n::fmt(tl!("Automatic ({language})"), &[("language", system.name())]);
|
||||
let current = if app.ui.language == AUTO { auto_label.clone() } else { Lang::from_pref(&app.ui.language).name().to_string() };
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Interface language:"));
|
||||
egui::ComboBox::from_id_salt("interface_language").selected_text(current).width(220.0).show_ui(ui, |ui| {
|
||||
if ui.selectable_label(app.ui.language == AUTO, auto_label.as_str()).clicked() {
|
||||
let _ = app.run("ui.language", json!({"value": AUTO}));
|
||||
}
|
||||
for lang in Lang::all() {
|
||||
if ui.selectable_label(app.ui.language == lang.code(), lang.name()).clicked() {
|
||||
let _ = app.run("ui.language", json!({"value": lang.code()}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.label(RichText::new(tl!("Automatic follows your system's language. Menus and commands change; your documents don't.")).small().weak());
|
||||
}
|
||||
|
||||
/// Deserialize `input` over `T`'s defaults one setting at a time: a value of the wrong type is
|
||||
/// dropped (its default stays) instead of failing the whole read. Objects nest (`editing`).
|
||||
pub fn lenient<T: Serialize + DeserializeOwned + Default>(input: &Value) -> T {
|
||||
let mut base = serde_json::to_value(T::default()).unwrap_or(Value::Null);
|
||||
if base.is_object() {
|
||||
overlay::<T>(&mut base, &mut Vec::new(), input, 0);
|
||||
}
|
||||
serde_json::from_value(base).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The object at `path` inside `root`.
|
||||
fn object_at<'a>(root: &'a mut Value, path: &[String]) -> Option<&'a mut Map<String, Value>> {
|
||||
let mut v = root;
|
||||
for k in path {
|
||||
v = v.as_object_mut()?.get_mut(k)?;
|
||||
}
|
||||
v.as_object_mut()
|
||||
}
|
||||
|
||||
fn overlay<T: DeserializeOwned>(root: &mut Value, path: &mut Vec<String>, input: &Value, depth: usize) {
|
||||
let Some(map) = input.as_object() else { return };
|
||||
for (k, v) in map {
|
||||
let Some(parent) = object_at(root, path) else { return };
|
||||
let old = parent.get(k).cloned();
|
||||
if depth < 4 && v.is_object() && old.as_ref().is_some_and(Value::is_object) {
|
||||
path.push(k.clone());
|
||||
overlay::<T>(root, path, v, depth + 1);
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
parent.insert(k.clone(), v.clone());
|
||||
if serde_json::from_value::<T>(root.clone()).is_err()
|
||||
&& let Some(parent) = object_at(root, path)
|
||||
{
|
||||
match old {
|
||||
Some(o) => parent.insert(k.clone(), o),
|
||||
None => parent.remove(k),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
use wordcraft_geom::Unit;
|
||||
|
||||
use crate::theme::Appearance;
|
||||
use crate::{Services, UiState, WordApp};
|
||||
|
||||
fn app() -> WordApp {
|
||||
WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Services::default())
|
||||
}
|
||||
|
||||
/// File › Options settings come back after a restart, and a `ui.json` with a wrong value in
|
||||
/// one setting keeps every other setting (#485).
|
||||
#[test]
|
||||
fn options_survive_a_restart_and_junk_in_ui_json() {
|
||||
let mut a = app();
|
||||
a.run(
|
||||
"file.options",
|
||||
json!({"initials": "VK", "units": "centimeters", "typingReplacesSelection": false, "marks": {"tabs": true}, "ignoreNumbers": false, "addWords": ["zqxoptionsword"]}),
|
||||
)
|
||||
.unwrap();
|
||||
a.ui.recent_count = 30;
|
||||
a.ui.print_backgrounds = true;
|
||||
let saved = serde_json::to_vec(&a.prefs()).unwrap();
|
||||
let mut b = app();
|
||||
b.apply_prefs(UiState::from_json(&saved).unwrap());
|
||||
assert_eq!(b.session.prefs, a.session.prefs);
|
||||
assert_eq!(b.session.prefs.units, Unit::Centimeters);
|
||||
assert_eq!(b.session.initials, "VK");
|
||||
assert_eq!((b.ui.recent_count, b.ui.print_backgrounds), (30, true));
|
||||
assert!(wordcraft_engine::proof::user_dictionary().contains(&"zqxoptionsword".to_string()));
|
||||
|
||||
let junk = br#"{"theme": "dark", "recentCount": "lots", "printBackgrounds": 7, "optionsPane": ["x"], "dark": true,
|
||||
"editing": {"units": "furlongs", "typingReplacesSelection": false, "marks": {"tabs": "yes", "spaces": true}}}"#;
|
||||
let ui = UiState::from_json(junk).unwrap();
|
||||
assert_eq!(ui.theme, Appearance::Dark);
|
||||
assert_eq!((ui.recent_count, ui.print_backgrounds, ui.options_pane.as_str()), (12, false, "general"));
|
||||
assert_eq!(ui.legacy_dark, Some(true));
|
||||
assert_eq!(ui.editing.units, Unit::Inches);
|
||||
assert!(!ui.editing.typing_replaces_selection);
|
||||
assert!(ui.editing.marks.spaces && !ui.editing.marks.tabs);
|
||||
assert!(UiState::from_json(b"[1, 2]").is_none() && UiState::from_json(b"not json").is_none());
|
||||
}
|
||||
|
||||
/// `file.options` opens the page at a pane; the pane list switches panes, and their
|
||||
/// checkboxes change the settings (#485).
|
||||
#[test]
|
||||
fn options_panes_open_and_change_settings() {
|
||||
let mut a = app();
|
||||
a.run("file.options", json!({"pane": "display"})).unwrap();
|
||||
assert!(a.ui.backstage);
|
||||
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(4);
|
||||
h.get_by_label("Spaces").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().session.prefs.marks.spaces, "Display › Spaces");
|
||||
h.get_by_label("Proofing").click();
|
||||
h.run_steps(4);
|
||||
assert_eq!(h.state().ui.options_pane, "proofing");
|
||||
h.get_by_label("Ignore words in capitals").click();
|
||||
h.run_steps(2);
|
||||
assert!(!h.state().session.prefs.ignore_uppercase);
|
||||
h.get_by_label("Advanced").click();
|
||||
h.run_steps(4);
|
||||
h.get_by_label("Typing replaces selected text").click();
|
||||
h.run_steps(2);
|
||||
assert!(!h.state().session.prefs.typing_replaces_selection);
|
||||
assert!(h.query_by_label("Measurement units:").is_some());
|
||||
}
|
||||
}
|
||||
|
|
@ -1038,12 +1038,15 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
|
|||
ui.label("");
|
||||
ui.label(egui::RichText::new(tl!("Spacing")).small().strong());
|
||||
ui.end_row();
|
||||
let mut l = rp.indent_left / 72.0;
|
||||
// Indents in the File › Options unit.
|
||||
let u = crate::options::unit();
|
||||
let k = u.pt_per_unit();
|
||||
let mut l = rp.indent_left / k;
|
||||
let mut b = rp.space_before;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(tl!("Left:")).small());
|
||||
if ui.add(egui::DragValue::new(&mut l).speed(0.05).range(-11.0..=22.0).suffix("\"").max_decimals(2)).changed() {
|
||||
let _ = app.run("para.indents", json!({"left": l * 72.0}));
|
||||
if ui.add(egui::DragValue::new(&mut l).speed(3.6 / k).range(-792.0 / k..=1584.0 / k).suffix(u.suffix()).max_decimals(2)).changed() {
|
||||
let _ = app.run("para.indents", json!({"left": l * k}));
|
||||
}
|
||||
});
|
||||
ui.label("");
|
||||
|
|
@ -1054,12 +1057,12 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
|
|||
}
|
||||
});
|
||||
ui.end_row();
|
||||
let mut r = rp.indent_right / 72.0;
|
||||
let mut r = rp.indent_right / k;
|
||||
let mut a = rp.space_after;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(tl!("Right:")).small());
|
||||
if ui.add(egui::DragValue::new(&mut r).speed(0.05).range(-11.0..=22.0).suffix("\"").max_decimals(2)).changed() {
|
||||
let _ = app.run("para.indents", json!({"right": r * 72.0}));
|
||||
if ui.add(egui::DragValue::new(&mut r).speed(3.6 / k).range(-792.0 / k..=1584.0 / k).suffix(u.suffix()).max_decimals(2)).changed() {
|
||||
let _ = app.run("para.indents", json!({"right": r * k}));
|
||||
}
|
||||
});
|
||||
ui.label("");
|
||||
|
|
@ -1981,7 +1984,7 @@ fn cell_size_boxes(ui: &mut Ui, app: &mut WordApp) {
|
|||
}
|
||||
_ => None,
|
||||
});
|
||||
let unit = wordcraft_geom::Unit::default();
|
||||
let unit = crate::options::unit();
|
||||
let k = unit.pt_per_unit();
|
||||
let h0 = row.height.or(laid.map(|x| x.h)).unwrap_or(0.0);
|
||||
let w0 = laid.map(|x| x.w).or(stored_w).unwrap_or(0.0);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Where WordCraft falls short of Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (#18: the document canvas is exposed to screen readers, #488; previously trivial: #24 narrowed: Customize Keyboard and Customize Ribbon landed; 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-11 · **Change:** trivial (File › Options panes landed, #485; previously trivial: #24 narrowed: Customize Keyboard and Customize Ribbon landed; 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
|
||||
|
||||
The work list. One entry per shortfall, ranked by how much it stops a Word user from switching
|
||||
(**B** = blocks beta). Each says what's missing, the evidence, who it hurts, an Opus 5.5 agent
|
||||
|
|
@ -17,7 +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; 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 | **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; still missing: drag-and-drop moves (no text drag yet) and move options in Track Changes Options. 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 | B | 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 | **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**: 52 modal dialogs vs Word's ~100 (Options panes…) | 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) |
|
||||
| 7 | **Dialog depth**: 52 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) |
|
||||
| 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) |
|
||||
| 10 | **Objects**: no group (#267 open), 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 6/6, Layout › Group) | Newsletters, flyers, reports with diagrams | 20–30 | [target-app-parity.md](target-app-parity.md) |
|
||||
|
|
@ -64,6 +64,7 @@ The same gaps grouped the way the parity documents are, for agents working in on
|
|||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | trivial | File › Options panes landed (#485); removed from the dialog-depth list |
|
||||
| 2026-10-11 | trivial | #18: the document canvas describes its text, structure and caret to screen readers through AccessKit (#488); real screen-reader testing remains |
|
||||
| 2026-10-11 | trivial | Compare / Combine Documents and AutoCorrect Options dialogs and the Restrict Editing pane landed (#412); removed from the dialog-depth list; duplicate #7 rows merged |
|
||||
| 2026-10-11 | trivial | #10: WordArt text effects and Transform warps, connectors glued to shapes (#416) |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Typing parity with Word
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (pasted paragraph breaks: tracked under Track Changes, kept as text in lists, #417, #422; previously minor: 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-11 · **Change:** minor (editing options: typing replaces selection, overtype, #485; previously minor: 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
|
||||
|
|
@ -76,6 +76,13 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
|
|||
and "don't capitalize after" exceptions extend the abbreviation list. A replacement of several
|
||||
words takes a sentence capital on its first (`autocorrect_options_change_what_typing_does`).
|
||||
|
||||
## Editing options (File › Options › Advanced)
|
||||
|
||||
| Keys / setting | Result | Test |
|
||||
|---|---|---|
|
||||
| Typing over a selection | replaces it (Word's default); with "Typing replaces selected text" off, the text goes in front of the selection, which stays | `typing_over_a_selection_follows_the_replace_selection_option` |
|
||||
| Overtype on, typing | each character replaces the one after the caret, never the paragraph mark or an object; Insert switches overtype only with the Insert-key option on (off by default, as in Word) | `overtype_replaces_the_characters_after_the_caret` |
|
||||
|
||||
## Layout
|
||||
|
||||
- No line break right after a slash (`and/or`, URLs) unless the word can't fit a line
|
||||
|
|
@ -106,6 +113,7 @@ Applied when a word is finished by a space, punctuation **or Enter** (`enter_fin
|
|||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Editing options: typing replaces selection, overtype and the Insert key (#485) |
|
||||
| 2026-10-11 | minor | Pasted paragraph breaks: tracked under Track Changes (#417), kept in a list by Keep Text Only (#422) |
|
||||
| 2026-10-11 | minor | Undo rows: a selection ends the typing group (#419); Undo of Bold at the caret restores unbolded typing (#424) |
|
||||
| 2026-10-11 | minor | Names of days capitalised; AutoCorrect Options (user entries, deleted built-ins, exceptions, every AutoCorrect/AutoFormat switch) change typing (#412) |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# UI parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (screen readers can read the document canvas, #488; previously trivial: Customize Ribbon and the Quick Access Toolbar, #379; 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-11 · **Change:** trivial (File › Options panes, #485; previously trivial: Customize Ribbon and the Quick Access Toolbar, #379; 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
|
||||
|
|
@ -17,9 +17,9 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
|
|||
| Customize Ribbon / Customize Keyboard | Customize Keyboard (#368): categories, a command's keys, press a combination to see which command uses it, Assign / Remove / Reset All; custom keys override built-ins, removed built-ins do nothing, saved per user; `tools.customizeKeyboard` for agents. Customize Ribbon (#379): show, hide and reorder tabs, custom tabs and custom groups (on any tab) holding any commands, rename, remove, Reset; right-click on the ribbon; saved per user; `tools.customizeRibbon` for agents. Built-in tabs can't be renamed; no import/export file | ✅ | 85% | 1–2 |
|
||||
| Keyboard shortcuts | **125** commands bound in `CommandSpec::key`, 144 key combinations (measured on Linux/Windows); styles, spacing, symbols, fields, views, mail merge (#330); field keys Alt+F9, Shift+F9, Ctrl+Shift+F9, Ctrl+F11, Ctrl+Shift+F11, F11, Shift+F11 (#339); Alt+Shift+arrows (move paragraphs, promote/demote), Shift+F5 Go Back and Alt+X (#343). Missing: en dash on the numeric keypad | ~250 default shortcuts | ~70% | 2–4 |
|
||||
| Modal dialogs | **52** (`Dialog` enum: Font, Enclose Characters, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Icons, Compare and Combine, AutoCorrect Options, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); Field code (#339); mail merge Recipients, Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors; Caption with New Label and Numbering, Index, Mark Index Entry, Custom Table of Contents with Options and TOC styles, Source Manager, Create/Edit Source (#402); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
|
||||
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339); right-click selects a picture, shape or text box and offers Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables partial | rich per context | 60% | 3–5 |
|
||||
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation, Styles, Comments, Thesaurus, Restrict Editing, Accessibility (grouped findings, jump-to, fixes) and Alt Text (#386), Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
|
||||
| Backstage (Home, New, Open, Info, Save As, Print, Share, Export, Options) | Home and New separate (#254), Info properties kept (#268) | | 68% | 4–6 |
|
||||
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339); right-click selects a picture, shape or text box and offers Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables partial | rich per context | 60% | 3–5 |
|
||||
| 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) | | 68% | 4–6 |
|
||||
| Rulers: indents, tabs, margins, table columns | indents draggable (one undo, #62); tabs; table column drag pending (#49) | ✅ | 70% | 2–3 |
|
||||
| On-canvas objects: select, drag, resize handles, nudge | ✅ tested (#228) | ✅ | 75% | — |
|
||||
| Rotation handle, crop handles on canvas, alignment guides, smart guides | rotation handle (Shift: 15° steps), turned frames resize along their axes (#332); crop handles and guides missing | ✅ | 25% | 3–5 |
|
||||
|
|
@ -37,6 +37,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
|
|||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | trivial | File › Options as panes (General, Display, Proofing, Save, Language, Accessibility, Advanced, Agents), every setting wired; `file.options` headless (#485) |
|
||||
| 2026-10-11 | trivial | Right-click on a picture, shape or text box selects it and offers Wrap Text: the seven wrapping styles (current one checked), Move with Text and Fix Position on Page (`arrange.moveWithText`) (#513) |
|
||||
| 2026-10-11 | trivial | Paragraph dialog: Mirror indents (Inside/Outside labels), Don't add space between paragraphs of the same style, Formatting exceptions (Suppress line numbers, Don't hyphenate) (#512) |
|
||||
| 2026-10-11 | trivial | Screen readers: the document canvas exposes paragraphs, headings, lists, tables, links, pictures and the caret through AccessKit (#488); 25% → 50% |
|
||||
|
|
|
|||
Loading…
Reference in New Issue