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
+1
View File
@@ -18,6 +18,7 @@ pub fn layout(db: &FontDb, t: &TextObject) -> TextLayout {
first_baseline: a.first_baseline,
first_baseline_min: a.first_baseline_min,
vertical_align: a.vertical_align,
flow: a.flow,
fit: a.fit,
..LayoutOptions::default()
};
+7 -3
View File
@@ -142,9 +142,13 @@ impl Region {
let cw = ((bbox.width() - gutter * (cols - 1) as f64) / cols as f64).max(0.0);
let rh = ((bbox.height() - gutter * (rows - 1) as f64) / rows as f64).max(0.0);
let mut out = Vec::with_capacity(rows * cols);
// Text flows down each column, then across (Illustrator's default "by columns").
for c in 0..cols {
for r in 0..rows {
// Text Flow: down each column, then across (by columns, the default), or across each row,
// then down (by rows).
let by_rows = opts.flow == vectorcraft_doc::TextFlow::Rows;
let (outer, inner) = if by_rows { (rows, cols) } else { (cols, rows) };
for o in 0..outer {
for i in 0..inner {
let (r, c) = if by_rows { (o, i) } else { (i, o) };
let x0 = bbox.x0 + c as f64 * (cw + gutter);
let y0 = bbox.y0 + r as f64 * (rh + gutter);
let cell = if rows * cols == 1 { bbox } else { Rect::new(x0, y0, x0 + cw, y0 + rh) };
+5 -2
View File
@@ -47,7 +47,7 @@ use kurbo::{Affine, BezPath, Point, Rect, Shape, Vec2};
pub use layout::{decorations, fit_first_line, layout, layout_with, scaled};
pub use vectorcraft_doc::TextObject;
pub use vectorcraft_doc::{AreaFit, FirstBaseline, VerticalAlign};
pub use vectorcraft_doc::{AreaFit, FirstBaseline, TextFlow, VerticalAlign};
/// Paragraph composer; stored per text object in [`vectorcraft_doc::ParaStyle::composer`].
pub use vectorcraft_doc::Composer;
@@ -56,7 +56,7 @@ pub use vectorcraft_doc::Composer;
/// composer, OpenType features). [`layout`] takes rows/columns/inset/first baseline from the object.
#[derive(Clone, Debug, PartialEq)]
pub struct LayoutOptions {
/// Area type rows and columns (text flows down each column, then across). Layout clamps each
/// Area type rows and columns (text fills them in [`LayoutOptions::flow`] order). Layout clamps each
/// count to 1..=[`vectorcraft_doc::AreaOptions::MAX_ROWS_COLUMNS`].
pub rows: usize,
pub columns: usize,
@@ -69,6 +69,8 @@ pub struct LayoutOptions {
pub first_baseline_min: f64,
/// Vertical alignment of the lines in each row/column (Area Type Options "Align").
pub vertical_align: VerticalAlign,
/// Area Type Options "Text Flow": by columns (down each column, then across) or by rows.
pub flow: TextFlow,
/// Area type only: Shrink Text to Fit scales overflowing text down at layout time (see
/// [`TextLayout::fit_scale`]); the other fits are the engine's business and lay out as `None`.
pub fit: AreaFit,
@@ -87,6 +89,7 @@ impl Default for LayoutOptions {
first_baseline: FirstBaseline::Ascent,
first_baseline_min: 0.0,
vertical_align: VerticalAlign::Top,
flow: TextFlow::Columns,
fit: AreaFit::None,
composer: None,
features: OtFeatures::default(),
+20
View File
@@ -457,6 +457,26 @@ fn hit_testing_picks_the_right_column() {
}
}
/// Area Type Options › Text Flow (#4978): by columns (the default) the second cell is the one
/// below the first; by rows it is the one beside it.
#[test]
fn text_flow_fills_the_cells_by_columns_or_by_rows() {
let t = area(&COPY.repeat(8), style(12.0), Rect::new(0.0, 0.0, 420.0, 300.0), Justify::Left);
let grid = LayoutOptions { rows: 2, columns: 2, gutter: 20.0, ..Default::default() };
for (flow, second) in [(TextFlow::Columns, (0.0, 160.0)), (TextFlow::Rows, (220.0, 0.0))] {
let l = layout_with(db(), &t, &LayoutOptions { flow, ..grid.clone() });
let frame = l.frames.get(1).copied().unwrap();
assert_eq!((frame.x0, frame.y0), second, "{flow:?}");
let line = l.lines.iter().find(|li| li.region == 1).expect("text reaches the second cell");
assert!(line.x0 >= second.0 - 1e-6 && line.baseline >= second.1, "{flow:?}: {line:?}");
assert!(l.lines.iter().all(|li| l.lines.iter().all(|m| m.region >= li.region || m.start <= li.start)), "{flow:?}: text runs in cell order");
}
// The object's own option lays out the same way.
let mut by_rows = t.clone();
(by_rows.area.rows, by_rows.area.columns, by_rows.area.gutter, by_rows.area.flow) = (2, 2, 20.0, TextFlow::Rows);
assert_eq!(layout(db(), &by_rows).frames.get(1).map(|f| (f.x0, f.y0)), Some((220.0, 0.0)));
}
// ---------- area flow ----------
#[test]