mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 19:57:05 -06:00
Merge #527: Type (#432): new type in a Japanese interface starts top-to-top with em box centre alignment
# Conflicts: # ROADMAP.md
This commit is contained in:
@@ -50,7 +50,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Create Text",
|
||||
[],
|
||||
None,
|
||||
"{x, y, text, vertical?: bool = false, size?: pt, font?: family, style?, color?, area?: {width, height}, placeholder?: bool (fill it with placeholder text instead, as the Type tools do with prefs placeholderText)} → {id}",
|
||||
"{x, y, text, vertical?: bool = false, size?: pt, font?: family, style?, color?, area?: {width, height}, placeholder?: bool (fill it with placeholder text instead, as the Type tools do with prefs placeholderText), leadingModel?: \"romanBaseline\"|\"emBoxTop\", charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\" (default: emBoxTop and emBoxCenter while the interface is in Japanese, else romanBaseline)} → {id}",
|
||||
has_doc,
|
||||
text_create
|
||||
),
|
||||
@@ -409,6 +409,7 @@ fn text_create(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut t = TextObject::point(Point::new(x, y), text, new_type_style(s, p));
|
||||
t.vertical = p.get("vertical").and_then(Value::as_bool).unwrap_or(false);
|
||||
t.para = new_type_para();
|
||||
new_type_alignment(s, p, "text.create", &mut t)?;
|
||||
if let Some(a) = p.get("area") {
|
||||
let w = f64_or(a, "width", 200.0);
|
||||
let h = f64_or(a, "height", 100.0);
|
||||
@@ -435,6 +436,40 @@ pub(crate) fn new_type_para() -> vectorcraft_doc::ParaStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// The leading model and character alignment new type starts with (`t`'s paragraph and runs):
|
||||
/// `leadingModel` and `charAlign` from `p`, else em box top to top and em box centre while the
|
||||
/// interface is in Japanese (#432), else the Roman baseline for both. Noted in the journal, so a
|
||||
/// replay sets the same whatever the interface.
|
||||
pub(crate) fn new_type_alignment(s: &mut Session, p: &Value, cmd: &str, t: &mut TextObject) -> Result<()> {
|
||||
use vectorcraft_doc::{CharAlign, LeadingModel};
|
||||
let japanese = s.japanese_interface();
|
||||
let leading = match p.get("leadingModel").filter(|v| !v.is_null()) {
|
||||
Some(v) => match v.as_str() {
|
||||
Some("romanBaseline") => LeadingModel::RomanBaseline,
|
||||
Some("emBoxTop") => LeadingModel::EmBoxTop,
|
||||
_ => return Err(bad(cmd, "`leadingModel` must be \"romanBaseline\" or \"emBoxTop\"")),
|
||||
},
|
||||
None => {
|
||||
let l = if japanese { LeadingModel::EmBoxTop } else { LeadingModel::RomanBaseline };
|
||||
s.note_journal("leadingModel", json!(l));
|
||||
l
|
||||
}
|
||||
};
|
||||
let align = match super::textedit::char_align_param(p, cmd)? {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let a = if japanese { CharAlign::EmBoxCenter } else { CharAlign::RomanBaseline };
|
||||
s.note_journal("charAlign", json!(a));
|
||||
a
|
||||
}
|
||||
};
|
||||
t.para.leading_model = leading;
|
||||
for r in &mut t.runs {
|
||||
r.style.char_align = align;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The character style new type gets: `size`, `font`, `style` and `color` from `p`, else the
|
||||
/// defaults, filled with the current fill unless that is None or white (black then).
|
||||
pub(crate) fn new_type_style(s: &Session, p: &Value) -> CharStyle {
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Area / Path Type",
|
||||
[],
|
||||
None,
|
||||
"{path: id, mode: \"area\"|\"onPath\", text?: \"\", vertical?: bool = false, at?: [x, y] (on-path start: nearest point), size?, font?, placeholder?: bool (placeholder text instead, as text.create)} turn a path into an area-type frame or a type-on-a-path baseline (the path's paint is dropped) → {id}",
|
||||
"{path: id, mode: \"area\"|\"onPath\", text?: \"\", vertical?: bool = false, at?: [x, y] (on-path start: nearest point), size?, font?, placeholder?: bool (placeholder text instead, as text.create), leadingModel?, charAlign? (as text.create)} turn a path into an area-type frame or a type-on-a-path baseline (the path's paint is dropped) → {id}",
|
||||
has_doc,
|
||||
create_in_path
|
||||
),
|
||||
@@ -471,6 +471,7 @@ fn create_in_path(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
cached_bounds: None,
|
||||
cached_baselines: Vec::new(),
|
||||
};
|
||||
super::create::new_type_alignment(s, p, C, &mut t)?;
|
||||
if bool_or(p, "placeholder", false) {
|
||||
super::typemenu::fill_with_placeholder(&mut t);
|
||||
} else {
|
||||
|
||||
@@ -261,6 +261,26 @@ fn attrs_param(p: &Value, kind: Kind, verb: &str) -> Result<Option<Map<String, V
|
||||
}
|
||||
}
|
||||
|
||||
/// The attributes of a new style made from nothing (no `attrs`, no text selected): while the
|
||||
/// interface is in Japanese, new type's em box top-to-top leading (paragraph styles) and em box
|
||||
/// centre alignment (character styles); else none (the defaults).
|
||||
fn new_style_attrs(s: &Session, kind: Kind) -> Map<String, Value> {
|
||||
if !s.japanese_interface() {
|
||||
return Map::new();
|
||||
}
|
||||
let (set, default) = match kind {
|
||||
Kind::Char => {
|
||||
let st = CharStyle { char_align: vectorcraft_doc::CharAlign::EmBoxCenter, ..CharStyle::default() };
|
||||
(to_attrs(&st), to_attrs(&CharStyle::default()))
|
||||
}
|
||||
Kind::Para => {
|
||||
let st = ParaStyle { leading_model: vectorcraft_doc::LeadingModel::EmBoxTop, ..ParaStyle::default() };
|
||||
(to_attrs(&st), to_attrs(&ParaStyle::default()))
|
||||
}
|
||||
};
|
||||
set.into_iter().filter(|(k, v)| default.get(k) != Some(v)).collect()
|
||||
}
|
||||
|
||||
// ---------- commands ----------
|
||||
|
||||
fn list(s: &mut Session, kind: Kind) -> Result<Value> {
|
||||
@@ -275,7 +295,14 @@ fn new(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
let c = kind.cmd("new");
|
||||
let attrs = match attrs_param(p, kind, "new")? {
|
||||
Some(a) => a,
|
||||
None => selection_attrs(s, p, kind)?.unwrap_or_default(),
|
||||
None => match selection_attrs(s, p, kind)? {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let a = new_style_attrs(s, kind);
|
||||
s.note_journal("attrs", Value::Object(a.clone()));
|
||||
a
|
||||
}
|
||||
},
|
||||
};
|
||||
let d = &s.doc()?.doc;
|
||||
let base = if kind == Kind::Char { "Character Style" } else { "Paragraph Style" };
|
||||
|
||||
@@ -444,6 +444,73 @@ mod area_tests {
|
||||
assert_eq!(old.burasagari, Burasagari::None);
|
||||
}
|
||||
|
||||
/// #432: while the interface is in Japanese, new type (text.create, text.createInPath) starts
|
||||
/// with em box top-to-top leading and em box centre alignment, and new styles made from nothing
|
||||
/// carry them; params override; the values are journaled; imported text keeps the Roman
|
||||
/// baseline.
|
||||
#[test]
|
||||
fn new_type_takes_the_japanese_defaults_while_the_interface_is_japanese() {
|
||||
use vectorcraft_doc::{CharAlign, LeadingModel};
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
||||
let made = |s: &mut Session, cmd: &str, p: Value| -> (LeadingModel, CharAlign) {
|
||||
let id = s.execute(cmd, &p).unwrap()["id"].as_u64().unwrap();
|
||||
match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => (t.para.leading_model, t.runs[0].style.char_align),
|
||||
_ => panic!("text"),
|
||||
}
|
||||
};
|
||||
let roman = (LeadingModel::RomanBaseline, CharAlign::RomanBaseline);
|
||||
let japanese = (LeadingModel::EmBoxTop, CharAlign::EmBoxCenter);
|
||||
let point = json!({"x": 10, "y": 50, "text": "雅楽"});
|
||||
assert!(!s.japanese_interface());
|
||||
assert_eq!(made(&mut s, "text.create", point.clone()), roman, "headless, `auto`: the Roman defaults");
|
||||
s.ui_language = Some("ja".into());
|
||||
assert_eq!(made(&mut s, "text.create", point.clone()), japanese);
|
||||
assert_eq!(made(&mut s, "text.create", json!({"x": 10, "y": 90, "text": "笙", "area": {"width": 100, "height": 50}})), japanese);
|
||||
// Journaled: a replay in another language does the same.
|
||||
let (_, logged) = s.journal.last().unwrap().clone();
|
||||
assert_eq!((logged["leadingModel"].as_str(), logged["charAlign"].as_str()), (Some("emBoxTop"), Some("emBoxCenter")));
|
||||
// Params override.
|
||||
assert_eq!(
|
||||
made(&mut s, "text.create", json!({"x": 10, "y": 120, "text": "a", "leadingModel": "romanBaseline", "charAlign": "romanBaseline"})),
|
||||
roman
|
||||
);
|
||||
assert!(s.execute("text.create", &json!({"x": 0, "y": 0, "text": "a", "leadingModel": "middle"})).is_err());
|
||||
// Area type in a path.
|
||||
let path = s.execute("shape.rectangle", &json!({"x": 200, "y": 200, "width": 100, "height": 80})).unwrap()["id"].clone();
|
||||
assert_eq!(made(&mut s, "text.createInPath", json!({"path": path, "mode": "area", "text": "篳篥"})), japanese);
|
||||
// A preference set to Japanese counts without a UI; another language doesn't.
|
||||
s.ui_language = None;
|
||||
s.prefs.interface_language = "ja".into();
|
||||
assert_eq!(made(&mut s, "text.create", point.clone()), japanese);
|
||||
s.ui_language = Some("en".into());
|
||||
assert_eq!(made(&mut s, "text.create", point.clone()), roman);
|
||||
// New styles made from nothing.
|
||||
s.execute("select.set", &json!({"ids": []})).unwrap();
|
||||
s.ui_language = Some("ja".into());
|
||||
let style = |s: &mut Session, kind: &str| {
|
||||
let name = s.execute(&format!("{kind}.new"), &json!({})).unwrap()["name"].as_str().unwrap().to_string();
|
||||
let list = s.execute(&format!("{kind}.list"), &json!({})).unwrap();
|
||||
list["styles"].as_array().unwrap().iter().find(|st| st["name"] == name.as_str()).unwrap()["attrs"].clone()
|
||||
};
|
||||
assert_eq!(style(&mut s, "paraStyle"), json!({"leading_model": "emBoxTop"}));
|
||||
assert_eq!(style(&mut s, "charStyle"), json!({"charAlign": "emBoxCenter"}));
|
||||
s.ui_language = Some("en".into());
|
||||
assert_eq!(style(&mut s, "paraStyle"), json!({}));
|
||||
// Imported text keeps the Roman baseline.
|
||||
s.ui_language = Some("ja".into());
|
||||
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100"><text x="10" y="50">雅楽</text></svg>"#;
|
||||
s.execute("document.open", &json!({"name": "t.svg", "dataBase64": vectorcraft_format::base64_encode(svg.as_bytes())})).unwrap();
|
||||
let mut found = vec![];
|
||||
s.doc().unwrap().doc.walk(|n| {
|
||||
if let NodeKind::Text(t) = &n.kind {
|
||||
found.push((t.para.leading_model, t.runs[0].style.char_align));
|
||||
}
|
||||
});
|
||||
assert_eq!(found, [roman]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_model_is_set_per_paragraph_and_saved_only_when_top_to_top() {
|
||||
use vectorcraft_doc::LeadingModel;
|
||||
|
||||
Reference in New Issue
Block a user