Units: Preferences › East Asian Type reads, with Q and Ha units (#394)

Squashed from #4845 onto main.
This commit is contained in:
Taha-Mahmoodi
2026-11-02 14:36:08 +07:00
committed by bflatastic
parent 15a5e64134
commit 1b5d11ef4b
37 changed files with 335 additions and 37 deletions
+11 -1
View File
@@ -81,6 +81,16 @@ pub const UNITS: &[(&str, &str)] = &[
("yards", "Yards"),
("feet", "Feet"),
];
/// What Units › East Asian Type offers, as (`Unit::key`, `Unit::label`): [`Unit::EAST_ASIAN`].
pub const EAST_ASIAN_UNITS: &[(&str, &str)] = &{
let mut out = [("", ""); Unit::EAST_ASIAN.len()];
let mut i = 0;
while i < out.len() {
out[i] = (Unit::EAST_ASIAN[i].key(), Unit::EAST_ASIAN[i].label());
i += 1;
}
out
};
const LINE_STYLE: &[(&str, &str)] = &[("lines", "Lines"), ("dots", "Dots")];
/// Performance › Graphics Processor (`gpuPreference`), read by the desktop app at startup. The
/// values are WebGPU's power preferences plus `automatic`; 0.5.0's default, `powerSaving`, reads as
@@ -201,7 +211,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
p!("unitsGeneral", "Units", "", "General", choice(UNITS)),
p!("unitsStroke", "Units", "", "Stroke", choice(UNITS)),
p!("unitsType", "Units", "", "Type", choice(UNITS)),
p!("unitsAsianType", "Units", "", "East Asian Type", choice(UNITS)),
p!("unitsAsianType", "Units", "", "East Asian Type", choice(EAST_ASIAN_UNITS)),
p!("numbersWithoutUnitsArePoints", "Units", "", "Numbers Without Units Are Points", bool),
p!("identifyObjectsBy", "Units", "", "Identify Objects By", choice(&[("objectName", "Object Name"), ("xmlId", "XML ID")])),
// Guides & Grid
+55 -1
View File
@@ -6,7 +6,7 @@ use serde_json::json;
use vectorcraft_doc::Unit;
use vectorcraft_tools::{Mods, Overlay, PointerEvent, PointerKind};
use crate::cmd::prefscmds::{PREF_SPECS, PrefKind, UNITS};
use crate::cmd::prefscmds::{EAST_ASIAN_UNITS, PREF_SPECS, PrefKind, UNITS};
use crate::units::Measure;
use crate::*;
@@ -132,3 +132,57 @@ fn distance_params_take_a_unit() {
let st = t.first_style();
assert_eq!((st.size, st.leading, st.tracking), (36.0, Some(72.0), 20.0));
}
#[test]
fn east_asian_type_unit_is_a_preference_that_offers_q_and_ha() {
let want: Vec<(&str, &str)> = Unit::EAST_ASIAN.iter().map(|u| (u.key(), u.label())).collect();
assert_eq!(EAST_ASIAN_UNITS, want.as_slice());
let mut s = session();
assert_eq!(s.east_asian_type_unit(), Unit::Points, "points until chosen");
for u in Unit::EAST_ASIAN {
s.execute("prefs.set", &json!({"key": "unitsAsianType", "value": u.key()})).unwrap();
assert_eq!(s.east_asian_type_unit(), u);
assert_eq!(s.unit(Measure::AsianType), u);
assert_eq!(s.type_unit(), Unit::Points, "Type is its own preference");
}
// By label, in any case.
s.execute("prefs.set", &json!({"key": "unitsAsianType", "value": "ha"})).unwrap();
assert_eq!(s.prefs.units_asian_type, "ha");
s.execute("prefs.set", &json!({"key": "unitsAsianType", "value": "Q"})).unwrap();
assert_eq!(s.prefs.units_asian_type, "q");
// No other Units preference offers them.
for key in ["unitsGeneral", "unitsStroke", "unitsType"] {
for v in ["q", "Q", "ha", "Ha"] {
assert!(s.execute("prefs.set", &json!({"key": key, "value": v})).is_err(), "{key} = {v}");
}
}
assert_eq!((Measure::AsianType.name(), Measure::AsianType.pref_key()), ("asianType", "unitsAsianType"));
let list = s.execute("prefs.list", &json!({})).unwrap();
let row = list.as_array().unwrap().iter().find(|r| r["key"] == "unitsAsianType").unwrap().clone();
assert_eq!(row["options"].as_array().unwrap().iter().rev().take(2).collect::<Vec<_>>(), [&json!("ha"), &json!("q")]);
}
#[test]
fn east_asian_type_unit_leaves_the_document_in_points() {
let mut s = session();
let id = s.execute("text.create", &json!({"x": 10, "y": 10, "text": "日本語"})).unwrap()["id"].clone();
s.execute("select.set", &json!({ "ids": [id] })).unwrap();
s.execute("text.setStyle", &json!({"size": 12, "leading": 15})).unwrap();
let before = s.doc().unwrap().doc.clone();
s.execute("prefs.set", &json!({"key": "unitsAsianType", "value": "q"})).unwrap();
let after = &s.doc().unwrap().doc;
assert_eq!(units(&s), Unit::Points);
assert_eq!(after.units, before.units);
let size = |d: &vectorcraft_doc::Document| match &d.node(NodeId(id.as_u64().unwrap())).unwrap().kind {
NodeKind::Text(t) => (t.first_style().size, t.first_style().leading),
_ => panic!("not text"),
};
assert_eq!(size(after), size(&before));
assert_eq!(size(after), (12.0, Some(15.0)));
// Typed in Q or Ha, the value is stored in points.
s.execute("text.setStyle", &json!({"size": "17 Q", "leading": "20 H"})).unwrap();
let (sz, lead) = size(&s.doc().unwrap().doc);
assert!((sz - Unit::Q.to_pt(17.0)).abs() < 1e-9, "{sz}");
assert!((lead.unwrap() - Unit::Ha.to_pt(20.0)).abs() < 1e-9, "{lead:?}");
assert!((sz - 17.0 * 72.0 / 101.6).abs() < 1e-9);
}
+13 -3
View File
@@ -3,9 +3,10 @@
//! General (rulers, positions, sizes, dialog distances, canvas readouts) is the active document's
//! units, so they travel with the file: Document Setup (`document.setUnits`) changes them, and
//! Preferences ▸ Units ▸ General changes them too and is also the units new documents start in (and
//! the unit shown while no document is open). Stroke (weights, dashes) and Type (sizes, leading,
//! baseline shift) are application preferences. Typed values with a unit ("5 mm", "12 pt") work in
//! any unit.
//! the unit shown while no document is open). Stroke (weights, dashes), Type (sizes, leading,
//! baseline shift) and East Asian Type (the only
//! preference that offers Q and Ha, and no field reads it yet) are application preferences. Typed values with a unit
//! ("5 mm", "12 pt", "13 Q") work in any unit.
use vectorcraft_doc::Unit;
@@ -17,6 +18,7 @@ pub enum Measure {
General,
Stroke,
Type,
AsianType,
}
impl Measure {
@@ -26,6 +28,7 @@ impl Measure {
Measure::General => "general",
Measure::Stroke => "stroke",
Measure::Type => "type",
Measure::AsianType => "asianType",
}
}
@@ -35,6 +38,7 @@ impl Measure {
Measure::General => "unitsGeneral",
Measure::Stroke => "unitsStroke",
Measure::Type => "unitsType",
Measure::AsianType => "unitsAsianType",
}
}
}
@@ -51,6 +55,7 @@ impl Session {
Measure::General => self.active().map_or_else(|| self.default_units(), |d| d.doc.units),
Measure::Stroke => pref_unit(&self.prefs.units_stroke),
Measure::Type => pref_unit(&self.prefs.units_type),
Measure::AsianType => Unit::named_east_asian(&self.prefs.units_asian_type).unwrap_or_default(),
}
}
@@ -64,6 +69,11 @@ impl Session {
self.unit(Measure::Type)
}
/// Units ▸ East Asian Type: no field shows in it yet.
pub fn east_asian_type_unit(&self) -> Unit {
self.unit(Measure::AsianType)
}
/// Preferences ▸ Units ▸ General: the units new documents start in.
pub fn default_units(&self) -> Unit {
pref_unit(&self.prefs.units_general)