Part of #394: each run hyphenates in its own language; Default Language sets new type's

Squashed from #2818 onto main.
This commit is contained in:
jo-quast
2026-11-05 12:17:33 +07:00
committed by bflatastic
parent 1943c7272c
commit bddb834231
14 changed files with 204 additions and 24 deletions
+5 -2
View File
@@ -833,7 +833,10 @@ pub(crate) fn new_type_style(s: &Session, p: &Value) -> CharStyle {
None if !s.paint.fill.is_none() && s.paint.fill != vectorcraft_color::Paint::solid(vectorcraft_color::Color::WHITE) => s.paint.fill.clone(),
None => vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK),
};
// In the document's language (Document Setup), none for the default.
style.language = s.doc().ok().and_then(|d| vectorcraft_doc::setup::char_language(&d.doc.setup.language).ok().flatten()).map(str::to_string);
// In the language Document Setup chose, else Preferences › Hyphenation › Default Language; none
// for the default. Type already in a document keeps its own.
let chosen = |name: &str| vectorcraft_doc::setup::char_language(name).ok().flatten();
let document = s.doc().ok().and_then(|d| chosen(&d.doc.setup.language));
style.language = document.or_else(|| chosen(&s.prefs.hyphenation_language)).map(str::to_string);
style
}
+34 -8
View File
@@ -260,14 +260,40 @@ pub const PREF_SPECS: &[PrefSpec] = &[
"",
"Default Language",
choice(&[
("English: USA", "English: USA"),
("English: UK", "English: UK"),
("French", "French"),
("German", "German"),
("Spanish", "Spanish"),
("Italian", "Italian"),
("Dutch", "Dutch"),
("Portuguese", "Portuguese")
("en-US", "English: USA"),
("en-GB", "English: UK"),
("de", "German"),
("de-CH", "German: Swiss"),
("fr", "French"),
("it", "Italian"),
("es", "Spanish"),
("ca", "Catalan"),
("pt", "Portuguese"),
("nl", "Dutch"),
("da", "Danish"),
("sv", "Swedish"),
("fi", "Finnish"),
("nb", "Norwegian"),
("pl", "Polish"),
("cs", "Czech"),
("ru", "Russian"),
("sr-Cyrl", "Serbian: Cyrillic"),
("sr-Latn", "Serbian: Latin"),
("hr", "Croatian"),
("tr", "Turkish"),
("ja", "Japanese"),
("zh", "Chinese"),
("hi", "Hindi"),
("mr", "Marathi"),
("gu", "Gujarati"),
("bn", "Bengali"),
("pa", "Punjabi"),
("ta", "Tamil"),
("te", "Telugu"),
("kn", "Kannada"),
("ml", "Malayalam"),
("or", "Odia"),
("ur", "Urdu")
])
),
p!("hyphenationExceptions", "Hyphenation", "", "Exceptions (comma separated)", text),
+12 -1
View File
@@ -10,6 +10,14 @@ pub enum DrawMode {
Inside,
}
/// A saved Default Language as its tag: older preferences hold the language's name, and one the list
/// no longer has reads as the default.
fn language_tag<'de, D: serde::Deserializer<'de>>(d: D) -> std::result::Result<String, D::Error> {
let saved = <String as serde::Deserialize>::deserialize(d)?;
let tag = vectorcraft_doc::setup::char_language(&saved).ok().flatten();
Ok(tag.or_else(|| vectorcraft_doc::setup::LANGUAGES.first().map(|(_, t, _)| *t)).unwrap_or_default().to_string())
}
/// Application preferences (Edit → Preferences). Every field is reachable through `prefs.get` /
/// `prefs.set {key, value}` (camelCase keys, validated against [`cmd::prefscmds::PREF_SPECS`]).
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
@@ -114,6 +122,9 @@ pub struct Prefs {
pub show_slice_numbers: bool,
pub slice_line_color: String,
// Hyphenation
/// Default Language: the BCP 47 tag of the language new type takes when its document's language
/// is the default ([`vectorcraft_doc::setup::LANGUAGES`]).
#[serde(deserialize_with = "language_tag")]
pub hyphenation_language: String,
pub hyphenation_exceptions: String,
/// Type › Options › Additional Fonts Folder: a folder (read with its subfolders) whose fonts
@@ -404,7 +415,7 @@ impl Default for Prefs {
snap_perpendicular: true,
show_slice_numbers: true,
slice_line_color: s("#ff3f3f"),
hyphenation_language: s("English: USA"),
hyphenation_language: s("en-US"),
hyphenation_exceptions: String::new(),
fonts_folder: String::new(),
plugins_folder: String::new(),
+54
View File
@@ -582,6 +582,60 @@ fn set_pref(s: &mut Session, key: &str, value: Value) {
s.execute("prefs.set", &json!({"key": key, "value": value})).unwrap();
}
/// New type with the session's defaults: its id and language.
fn new_type(s: &mut Session) -> (NodeId, Option<String>) {
let id = NodeId(s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Hello"})).unwrap()["id"].as_u64().unwrap());
(id, type_language(s, id))
}
fn type_language(s: &Session, id: NodeId) -> Option<String> {
let Some(NodeKind::Text(t)) = s.doc().unwrap().doc.node(id).map(|n| &n.kind) else { panic!("not text") };
t.runs[0].style.language.clone()
}
/// Preferences › Hyphenation › Default Language (#394) is the language of new type, stored as its
/// tag. Type already there keeps its language, and a language chosen in Document Setup comes first.
#[test]
fn default_language_is_the_language_of_new_type() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
let (before, language) = new_type(&mut s);
assert_eq!(language, None, "the default language isn't stored");
set_pref(&mut s, "hyphenationLanguage", json!("German"));
assert_eq!(s.prefs.hyphenation_language, "de", "a name is stored as its tag");
assert_eq!(new_type(&mut s).1.as_deref(), Some("de"));
assert_eq!(type_language(&s, before), None, "type already there stays as it is");
s.execute("document.setup", &json!({"language": "French"})).unwrap();
assert_eq!(new_type(&mut s).1.as_deref(), Some("fr"), "the document's own language comes first");
set_pref(&mut s, "hyphenationLanguage", json!("en-US"));
s.execute("document.setup", &json!({"language": "English: USA"})).unwrap();
assert_eq!(new_type(&mut s).1, None);
}
/// Preferences saved before the language was a tag hold its name; a language no longer in the list
/// reads as the default.
#[test]
fn a_saved_default_language_reads_as_its_tag() {
let read = |v: &str| serde_json::from_value::<Prefs>(json!({"hyphenationLanguage": v})).unwrap().hyphenation_language;
assert_eq!(read("German"), "de");
assert_eq!(read("English: USA"), "en-US");
assert_eq!(read("de"), "de");
assert_eq!(read("Klingon"), "en-US");
assert_eq!(Prefs::default().hyphenation_language, "en-US");
}
/// The preference offers every language the Character panel and Document Setup do.
#[test]
fn default_language_offers_every_language() {
let spec = PREF_SPECS.iter().find(|p| p.key == "hyphenationLanguage").unwrap();
let crate::cmd::prefscmds::PrefKind::Choice(options) = spec.kind else { panic!("a choice") };
let languages: Vec<(&str, &str)> = vectorcraft_doc::setup::LANGUAGES.iter().map(|(name, tag, _)| (*tag, *name)).collect();
assert_eq!(options, languages.as_slice());
}
fn gesture(s: &mut Session, v: ViewInfo, events: &[(vectorcraft_tools::PointerKind, f64, f64)]) {
for (k, x, y) in events {
s.pointer(&vectorcraft_tools::PointerEvent::new(*k, *x, *y), v).unwrap();