Integration: #753 Fixed Width columns resize the frame to fit them; columnWidth and firstBaselineMin are range-checked

This commit is contained in:
aiongg
2026-10-13 19:32:38 +00:00
parent 1907d14699
commit 977b4b26f3
3 changed files with 41 additions and 6 deletions
+1 -1
View File
@@ -232,7 +232,7 @@ pub fn fitted(d: &Document, fid: ItemId, sid: StoryId) -> Option<Rect> {
}
/// Give frame `fid` the inner bounds `to` (its path scaled into them).
fn resize(d: &mut Document, fid: ItemId, to: Rect) -> bool {
pub(crate) fn resize(d: &mut Document, fid: ItemId, to: Rect) -> bool {
let Some(it) = d.item_mut(fid) else { return false };
let r = it.inner_bounds();
if r.width() < EPS || r.height() < EPS {
+39 -4
View File
@@ -2265,6 +2265,8 @@ fn text_frame_options(s: &mut Session, p: &Value) -> Result<Value> {
let rule_bottom = ranged("columnRuleBottomInset", -1440.0, 1440.0, "a number of points")?;
let min_height = ranged("minimumHeight", 0.0, crate::autosize::MAX_SIDE, "a number of points")?;
let min_width = ranged("minimumWidth", 0.0, crate::autosize::MAX_SIDE, "a number of points")?;
let column_width = ranged("columnWidth", 0.0, 15552.0, "a number of points")?;
let first_baseline_min = ranged("firstBaselineMin", 0.0, 15552.0, "a number of points")?;
let reference = match p.get("autoSizeReference") {
None | Some(Value::Null) => None,
Some(v) => Some(
@@ -2334,11 +2336,11 @@ fn text_frame_options(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(v) = p.get("ignoreWrap").and_then(Value::as_bool) {
o.ignore_wrap = v;
}
if let Some(v) = p.get("columnWidth").and_then(Value::as_f64) {
o.column_width = v.max(0.0);
if let Some(v) = column_width {
o.column_width = v;
}
if let Some(v) = p.get("firstBaselineMin").and_then(Value::as_f64) {
o.first_baseline_min = v.max(0.0);
if let Some(v) = first_baseline_min {
o.first_baseline_min = v;
}
if let Some(v) = p.get("balanceColumns").and_then(Value::as_bool) {
o.balance_columns = v;
@@ -2364,6 +2366,16 @@ fn text_frame_options(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(v) = rule_bottom {
o.column_rule_bottom_inset = v;
}
// Fixed Width columns set the frame's width: n columns, the gutters between them and
// the left and right insets.
let fixed_width = o.columns_kind == designcraft_doc::ColumnsKind::FixedWidth && o.column_width > 0.0;
let width = f64::from(o.columns.max(1)) * o.column_width + f64::from(o.columns.max(1) - 1) * o.gutter + o.inset[1] + o.inset[3];
if fixed_width
&& ["columnsKind", "columnWidth", "columns", "gutter", "inset"].iter().any(|k| p.get(*k).is_some())
&& let Some(r) = d.item(*id).map(Item::inner_bounds)
{
crate::autosize::resize(d, *id, Rect::new(r.x0, r.y0, r.x0 + width, r.y1));
}
}
// Story direction belongs to the story: every frame of the thread turns.
if let Some(v) = p.get("vertical").and_then(Value::as_bool) {
@@ -4965,3 +4977,26 @@ mod ungroup_tests {
d.check().unwrap();
}
}
#[cfg(test)]
mod fixed_width_tests {
use super::*;
#[test]
fn fixed_width_columns_set_the_frame_width() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let r = s.execute("frame.create", &json!({"rect": [36, 36, 300, 300], "content": "text", "text": "Hello"})).unwrap();
let id = ItemId(r["id"].as_u64().unwrap());
let p = json!({"ids": [id.0], "columns": 2, "gutter": 12, "inset": 0, "columnsKind": "fixedWidth", "columnWidth": 120});
s.execute("object.textFrameOptions", &p).unwrap();
let b = s.doc().unwrap().doc.item(id).unwrap().inner_bounds();
assert!((b.width() - (2.0 * 120.0 + 12.0)).abs() < 1e-6, "{b:?}");
assert!((b.x0 - 36.0).abs() < 1e-6 && (b.height() - 264.0).abs() < 1e-6, "{b:?}");
// Widths beyond the largest page are refused and change nothing.
for (k, v) in [("columnWidth", 1e9), ("columnWidth", -1.0), ("firstBaselineMin", 1e9)] {
assert!(s.execute("object.textFrameOptions", &json!({"ids": [id.0], k: v})).is_err(), "{k} {v}");
}
assert!((s.doc().unwrap().doc.item(id).unwrap().inner_bounds().width() - 252.0).abs() < 1e-6);
}
}
+1 -1
View File
@@ -413,7 +413,6 @@ fn pdf_export(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
}
}
/// The options of the selected text frame (the first selected item, or the frame of the caret).
/// The selected text frame's width (its own, before rotation).
fn selected_frame_width(app: &DesignApp) -> Option<f64> {
let st = app.session.active()?;
@@ -421,6 +420,7 @@ fn selected_frame_width(app: &DesignApp) -> Option<f64> {
Some(st.doc.item(fid)?.inner_bounds().width())
}
/// The options of the selected text frame (the first selected item, or the frame of the caret).
fn selected_frame_options(app: &DesignApp) -> Option<designcraft_doc::TextFrameOptions> {
let st = app.session.active()?;
let fid = st.selection.items.first().copied().or_else(|| st.selection.text.and_then(|t| t.frame))?;