Fix #4978: Area Type Options › Text Flow by rows or by columns

This commit is contained in:
bflatastic
2026-11-01 06:11:11 +07:00
parent 8704c15c6d
commit 7d8f1d4e06
41 changed files with 248 additions and 16 deletions
+3 -2
View File
@@ -87,7 +87,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Area Type Options…",
["Type"],
None,
"{ids?|id?, width?: pt, height?: pt, rows?, columns?, gutter?: pt, inset?: pt, firstBaseline?: ascent|capHeight|xHeight|leading|fixed, firstBaselineMin?: pt, verticalAlign?: top|center|bottom|justify, fit?: none|autoHeight|shrinkText, fitMinPercent?: 10..100, …the keys of its own reply (it can be passed back; the values it only reports are ignored)} set the selected area type's options; width and height size the type area from its top-left corner, along the type's own axes, and the text reflows at its size (with fit autoHeight, Auto Size, the frame's height follows the text after every edit and height is ignored; setting the height by hand turns it off); fit shrinkText scales overflowing text down (size, leading, baseline shift) by the largest factor down to fitMinPercent % (default 50) that makes it fit (none given: query) → the first object's options, plus overflow (the text doesn't fit its frame) and fitScale (Shrink Text's factor, 1 unshrunk)",
"{ids?|id?, width?: pt, height?: pt, rows?, columns?, gutter?: pt, inset?: pt, firstBaseline?: ascent|capHeight|xHeight|leading|fixed, firstBaselineMin?: pt, verticalAlign?: top|center|bottom|justify, flow?: columns|rows (Text Flow: down each column's rows, then across, the default; or across each row's columns, then down), fit?: none|autoHeight|shrinkText, fitMinPercent?: 10..100, …the keys of its own reply (it can be passed back; the values it only reports are ignored)} set the selected area type's options; width and height size the type area from its top-left corner, along the type's own axes, and the text reflows at its size (with fit autoHeight, Auto Size, the frame's height follows the text after every edit and height is ignored; setting the height by hand turns it off); fit shrinkText scales overflowing text down (size, leading, baseline shift) by the largest factor down to fitMinPercent % (default 50) that makes it fit (none given: query) → the first object's options, plus overflow (the text doesn't fit its frame) and fitScale (Shrink Text's factor, 1 unshrunk)",
has_selection,
area_options
),
@@ -1006,6 +1006,7 @@ pub(crate) fn area_options_json(t: &TextObject) -> Result<Value> {
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t);
if let Some(o) = v.as_object_mut() {
o.insert("fit".into(), json!(t.area.fit.id()));
o.insert("flow".into(), json!(t.area.flow));
let min = match t.area.fit {
AreaFit::ShrinkText { min_percent } => min_percent,
_ => AreaFit::DEFAULT_MIN_PERCENT,
@@ -1022,7 +1023,7 @@ pub(crate) fn area_options_json(t: &TextObject) -> Result<Value> {
}
/// The [`vectorcraft_doc::AreaOptions`] keys `text.areaOptions` merges as they are.
const AREA_KEYS: [&str; 7] = ["rows", "columns", "gutter", "inset", "firstBaseline", "firstBaselineMin", "verticalAlign"];
const AREA_KEYS: [&str; 8] = ["rows", "columns", "gutter", "inset", "firstBaseline", "firstBaselineMin", "verticalAlign", "flow"];
fn area_options(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "text.areaOptions";
+32
View File
@@ -476,3 +476,35 @@ fn area_type_options_align_the_lines_vertically() {
assert_eq!(text(&s, id).cached_bounds, before);
assert!(cmd::find_command("text.areaOptions").unwrap().params.contains("verticalAlign?: top|center|bottom|justify"));
}
/// Area Type Options › Text Flow (#4978): `flow` fills the cells by columns (the default) or by
/// rows, is reported back, refuses other values and undoes.
#[test]
fn area_type_options_set_the_text_flow() {
use vectorcraft_doc::TextFlow;
let mut s = session();
let id = area(&mut s, 0.0, 0.0, 420.0, 300.0, &"Words flow from cell to cell in order. ".repeat(40));
sel(&mut s, &[id]);
let r = s.execute("text.areaOptions", &json!({"rows": 2, "columns": 2, "gutter": 20})).unwrap();
assert_eq!(r["flow"], "columns");
// Where the second cell sits from the first.
let second = |s: &Session| {
let f = layout(s, id).frames;
(f[1].x0 - f[0].x0, f[1].y0 - f[0].y0)
};
assert_eq!(second(&s), (0.0, 160.0), "by columns the second cell is below the first");
let n = undo_len(&s);
assert_eq!(s.execute("text.areaOptions", &json!({"flow": "rows"})).unwrap()["flow"], "rows");
assert_eq!(text(&s, id).area.flow, TextFlow::Rows);
assert_eq!(second(&s), (220.0, 0.0), "by rows the second cell is beside the first");
let l = layout(&s, id);
assert!(l.lines.iter().any(|li| li.region == 1 && li.x0 >= l.frames[1].x0 - 1e-6), "text reaches it");
assert_eq!((undo_len(&s), last_step(&s).as_str()), (n + 1, "Area Type Options"));
for bad in [json!({"flow": "diagonal"}), json!({"flow": "Rows"}), json!({"flow": 1}), json!({"flow": null})] {
assert!(s.execute("text.areaOptions", &bad).is_err(), "{bad}");
}
assert_eq!((undo_len(&s), text(&s, id).area.flow), (n + 1, TextFlow::Rows));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!((text(&s, id).area.flow, second(&s)), (TextFlow::Columns, (0.0, 160.0)));
assert!(cmd::find_command("text.areaOptions").unwrap().params.contains("flow?: columns|rows"));
}