Merge pull request #350 from imachus/pr/ja-mojikumi

Type: mojikumi — Line-end Punctuation Half Width for Japanese punctuation

# Conflicts:
#	ROADMAP.md
#	crates/text/src/layout.rs
#	crates/text/src/tests_vertical.rs
This commit is contained in:
Brandon Thomas
2026-10-07 17:03:07 -04:00
13 changed files with 203 additions and 11 deletions
+2
View File
@@ -402,6 +402,8 @@ fn text_create(s: &mut Session, p: &Value) -> Result<Value> {
let text = str_param(p, "text").unwrap_or("");
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);
// New type is composed the Japanese way (punctuation spacing), as the Type tools set it.
t.para.mojikumi = vectorcraft_doc::Mojikumi::LineEndHalf;
if let Some(a) = p.get("area") {
let w = f64_or(a, "width", 200.0);
let h = f64_or(a, "height", 100.0);
+13 -1
View File
@@ -24,7 +24,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Character / Paragraph",
[],
None,
"{ids?|id?, kerning?: 1/1000 em|\"auto\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, underline?, strikethrough?, allCaps?, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), leftIndent?, rightIndent?, firstLineIndent?, spaceBefore?, spaceAfter?: pt, hyphenate?: bool}",
"{ids?|id?, kerning?: 1/1000 em|\"auto\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, underline?, strikethrough?, allCaps?, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), leftIndent?, rightIndent?, firstLineIndent?, spaceBefore?, spaceAfter?: pt, hyphenate?: bool, mojikumi?: \"none\"|\"lineEndHalf\" (Japanese punctuation spacing)}",
has_doc,
set_format
),
@@ -111,11 +111,20 @@ fn set_format(s: &mut Session, p: &Value) -> Result<Value> {
"spaceBefore",
"spaceAfter",
"hyphenate",
"mojikumi",
];
if !keys.iter().any(|k| p.get(*k).is_some()) {
return Err(bad(C, "nothing to change"));
}
let (position, small_caps) = super::docsetup::script_params(p, &s.doc()?.doc.setup, C)?;
let mojikumi = match p.get("mojikumi") {
None => None,
Some(v) => Some(match v.as_str() {
Some("none") => vectorcraft_doc::Mojikumi::None,
Some("lineEndHalf") => vectorcraft_doc::Mojikumi::LineEndHalf,
_ => return Err(bad(C, "`mojikumi` must be \"none\" or \"lineEndHalf\"")),
}),
};
s.edit("Character", |d, _| {
for id in &ids {
let Some(NodeKind::Text(t)) = d.node_mut(*id).map(|n| &mut n.kind) else { continue };
@@ -171,6 +180,9 @@ fn set_format(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(v) = flag("hyphenate") {
para.hyphenate = v;
}
if let Some(v) = mojikumi {
para.mojikumi = v;
}
super::typecmd::refresh_bounds(t);
}
Ok(())
+1
View File
@@ -378,6 +378,7 @@ fn create_in_path(s: &mut Session, p: &Value) -> Result<Value> {
wrap: Vec::new(),
cached_bounds: None,
};
t.para.mojikumi = vectorcraft_doc::Mojikumi::LineEndHalf;
refresh_bounds(&mut t);
let id = s.edit(if on_path { "Type on a Path" } else { "Area Type" }, |d, sel| {
let (par, idx, _) = d.position(pid).ok_or(EngineError::NoNode(pid))?;
+25
View File
@@ -407,6 +407,31 @@ fn reshape_area(s: &mut Session, p: &Value) -> Result<Value> {
mod area_tests {
use super::*;
#[test]
fn new_type_is_composed_with_line_end_half_width_punctuation() {
use vectorcraft_doc::Mojikumi;
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
let id = s.execute("text.create", &json!({"x": 10, "y": 50, "text": "雅楽。"})).unwrap()["id"].as_u64().unwrap();
let para = |s: &Session| match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind {
NodeKind::Text(t) => t.para.clone(),
_ => panic!("text"),
};
assert_eq!(para(&s).mojikumi, Mojikumi::LineEndHalf);
s.execute("select.set", &json!({"ids": [id]})).unwrap();
assert!(s.execute("text.setFormat", &json!({"mojikumi": "everything"})).is_err());
s.execute("text.setFormat", &json!({"mojikumi": "none"})).unwrap();
assert_eq!(para(&s).mojikumi, Mojikumi::None);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(para(&s).mojikumi, Mojikumi::LineEndHalf);
// Saved only when set; documents from before it read as None.
let json = serde_json::to_value(para(&s)).unwrap();
assert_eq!(json["mojikumi"], "lineEndHalf");
let old: vectorcraft_doc::ParaStyle = serde_json::from_value(json!({"justify": "Left"})).unwrap();
assert_eq!(old.mojikumi, Mojikumi::None);
assert!(serde_json::to_value(&old).unwrap().get("mojikumi").is_none());
}
#[test]
fn vertical_point_type_keeps_its_anchor_on_the_column_centre_line() {
let mut s = Session::new();