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
+2 -1
View File
@@ -109,7 +109,8 @@ pub use style_libs::StyleLibrary;
pub use text::{
AreaFit, AreaOptions, Burasagari, CharAlign, CharPosition, CharStyle, Composer, Digits, FirstBaseline, HyphenationOptions, InlineArt,
JustificationOptions, Justify, Kinsoku, LeadingModel, Mojikumi, ParaDirection, ParaStyle, PathAlign, PathEffect, RubyAlign, RubyPosition,
RubySpan, ScriptMetrics, SingleWordJustify, TabAlign, TabStop, TextKind, TextObject, TextRun, TextStyleDef, TextWrap, VerticalAlign, WrapShape,
RubySpan, ScriptMetrics, SingleWordJustify, TabAlign, TabStop, TextFlow, TextKind, TextObject, TextRun, TextStyleDef, TextWrap, VerticalAlign,
WrapShape,
};
pub use trace::TraceView;
pub use variables::{DataSet, DataValue, Variable, VariableKind, Variables};
+1 -1
View File
@@ -13,7 +13,7 @@ mod para_style;
mod runs;
mod tabs_lists;
pub use area::{AreaFit, AreaOptions, PathAlign, PathEffect, TextWrap, VerticalAlign, WrapShape};
pub use area::{AreaFit, AreaOptions, PathAlign, PathEffect, TextFlow, TextWrap, VerticalAlign, WrapShape};
use char_style::yes;
pub use char_style::{CharAlign, CharPosition, CharStyle, Digits, ScriptMetrics};
pub use para_style::{
+17
View File
@@ -29,6 +29,19 @@ impl VerticalAlign {
}
}
/// Area Type Options › Text Flow: the order text fills the cells of area type with several rows
/// and columns.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TextFlow {
/// Down each column's rows, then on to the next column (the default, and what a document
/// from before the option reads as).
#[default]
Columns,
/// Across each row's columns, then on to the next row.
Rows,
}
/// Text Wrap Options of a wrap object (Object → Text Wrap).
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
@@ -135,6 +148,9 @@ pub struct AreaOptions {
pub first_baseline_min: f64,
/// Vertical alignment of the lines in each row/column.
pub vertical_align: VerticalAlign,
/// Text Flow: by columns (the default, left out when saved) or by rows.
#[serde(skip_serializing_if = "crate::skip::is_default")]
pub flow: TextFlow,
/// How the frame and its text fit each other (Auto Size, Shrink Text to Fit).
#[serde(skip_serializing_if = "crate::skip::is_default")]
pub fit: AreaFit,
@@ -150,6 +166,7 @@ impl Default for AreaOptions {
first_baseline: FirstBaseline::Ascent,
first_baseline_min: 0.0,
vertical_align: VerticalAlign::Top,
flow: TextFlow::Columns,
fit: AreaFit::None,
}
}
+13
View File
@@ -293,6 +293,19 @@ fn paragraph_styles_split_merge_and_normalize() {
assert_eq!(back.para_at(3).space_before, 4.0);
}
/// Text Flow is saved only when by rows: area type by columns (the default) saves as it did before
/// the option, and files from before it read by columns; other values don't read.
#[test]
fn text_flow_is_saved_only_when_by_rows() {
let by_columns = serde_json::to_value(AreaOptions::default()).unwrap();
assert!(by_columns.get("flow").is_none());
assert_eq!(serde_json::from_value::<AreaOptions>(by_columns).unwrap().flow, TextFlow::Columns);
let by_rows = serde_json::to_value(AreaOptions { flow: TextFlow::Rows, ..AreaOptions::default() }).unwrap();
assert_eq!(by_rows["flow"], "rows");
assert_eq!(serde_json::from_value::<AreaOptions>(by_rows).unwrap().flow, TextFlow::Rows);
assert!(serde_json::from_value::<AreaOptions>(serde_json::json!({"flow": "diagonal"})).is_err());
}
/// 120 × 40 area type at (40, 40), its text drawn at twice its size.
fn area_type() -> TextObject {
let mut t = TextObject::point(Point::ZERO, "Some text", CharStyle::default());
+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"));
}
+6 -1
View File
@@ -720,7 +720,7 @@ fn shape_builder_options_junk() {
assert!(failures.is_empty(), "{} failures:\n{}", failures.len(), failures.join("\n"));
}
/// Area Type Options with junk fit values, on selected area type (the fixtures have none), with
/// Area Type Options with junk fit and flow values, on selected area type (the fixtures have none), with
/// text that overflows: never a panic, the document stays sound, and editing still works.
#[test]
fn area_options_fit_junk() {
@@ -741,6 +741,11 @@ fn area_options_fit_junk() {
json!({"fit": "shrinkText", "columns": 100, "gutter": 0, "inset": 10000}),
json!({"fitMinPercent": 50}),
json!({"fit": "none", "fitMinPercent": 1e-308}),
json!({"flow": "rows", "rows": u64::MAX, "columns": u64::MAX, "gutter": 1e308}),
json!({"flow": "rows", "fit": "shrinkText", "rows": 3, "columns": 3}),
json!({"flow": "rows", "fit": "autoHeight", "columns": 4}),
json!({"flow": {"rows": 1}}),
json!({"flow": [1]}),
];
let story = "Words that overflow a small frame. ".repeat(40);
let mut failures = vec![];
+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]
+46 -2
View File
@@ -3,11 +3,12 @@
use std::sync::LazyLock;
use serde_json::Value;
use serde_json::{Value, json};
use super::{DialogSpec, form};
use crate::VectorcraftApp;
use crate::state::Dialog;
use crate::theme::Tokens;
use crate::{VectorcraftApp, widgets};
pub(super) const SPEC: DialogSpec = DialogSpec {
heading: |d| tl!(&d.str("__label")).to_string(),
@@ -15,7 +16,28 @@ pub(super) const SPEC: DialogSpec = DialogSpec {
let command = d.str("__command");
let (lengths, order) = (lengths(&command), order(&command));
let rank = |k: &str| order.iter().position(|o| *o == k).unwrap_or(order.len());
// Area Type Options › Text Flow is a pair of radio buttons after the other options.
let flow = if command == "text.areaOptions" { d.fields.remove("flow") } else { None };
form::param_fields(ui, d, &|k| lengths.contains(&k), &|k| choices(&command, k), &rank, app.session.general_unit());
if let Some(v) = flow {
let mut rows = v.as_str() == Some("rows");
ui.add_space(4.0);
egui::Grid::new("text-flow").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
let row = ui.label(egui::RichText::new(format!("{}:", tl!("Text Flow"))).color(Tokens::get(ui.ctx()).text));
ui.label_inputs_by(row.id, |ui| {
ui.horizontal(|ui| {
if widgets::radio(ui, tl!("By Rows"), rows, true) {
rows = true;
}
if widgets::radio(ui, tl!("By Columns"), !rows, true) {
rows = false;
}
});
});
ui.end_row();
});
d.fields.insert("flow".into(), json!(if rows { "rows" } else { "columns" }));
}
if previews(d) {
let (cmd, params) = call(d);
form::preview(app, ui, d, &d.str("__label"), &cmd, params);
@@ -177,6 +199,28 @@ mod tests {
assert_eq!(t.area.vertical_align, VerticalAlign::Center);
}
/// Area Type Options › Text Flow (#4978): two radio buttons, By Columns as new area type has
/// it, and OK fills the cells by rows when By Rows is picked.
#[test]
fn area_type_options_dialog_sets_the_text_flow() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
let id = app.run("text.create", json!({"x": 20, "y": 20, "text": "Hello", "area": {"width": 200, "height": 200}})).unwrap()["id"]
.as_u64()
.unwrap();
app.run("select.set", json!({"ids": [id]})).unwrap();
crate::menus::invoke(&mut app, "text.areaOptions", json!({}));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::dialogs::show(&mut app, ui.ctx()));
out.textures_delta.clear();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("flow"), "columns", "drawing the radio buttons keeps the value");
app.ui.dialog.as_mut().unwrap().fields.insert("flow".into(), json!("rows"));
crate::dialogs::confirm(&mut app).unwrap();
let NodeKind::Text(t) = &app.session.doc().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().kind else { panic!("text") };
assert_eq!(t.area.flow, vectorcraft_doc::TextFlow::Rows);
}
/// Graph Type › Value Axis is a dropdown of the values `graph.setType` takes, and OK puts the
/// value axis on the side picked.
#[test]
+3
View File
@@ -5988,3 +5988,6 @@ breakText Glyphs Глифи
@msg no object at path {shown} in the symbol's art в графиката на символа няма обект на пътя {shown}
@msg give fill, stroke, opacity and/or blend задайте fill, stroke, opacity и/или blend
@msg pass newName, registration, exportType, nineSlice and/or type задайте newName, registration, exportType, nineSlice и/или type
Text Flow Поток на текста
By Rows По редове
By Columns По колони
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6021,3 +6021,6 @@ shape Open Otvoren
@msg no object at path {shown} in the symbol's art nema objekta na putanji {shown} u grafici simbola
@msg give fill, stroke, opacity and/or blend navedite fill, stroke, opacity i/ili blend
@msg pass newName, registration, exportType, nineSlice and/or type navedite newName, registration, exportType, nineSlice i/ili type
Text Flow Tok teksta
By Rows Po redovima
By Columns Po kolonama
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6094,3 +6094,6 @@ breakText Glyphs Glifs
@msg no object at path {shown} in the symbol's art no hi ha cap objecte al camí {shown} de l’art del símbol
@msg give fill, stroke, opacity and/or blend indiqueu fill, stroke, opacity i/o blend
@msg pass newName, registration, exportType, nineSlice and/or type indiqueu newName, registration, exportType, nineSlice i/o type
Text Flow Flux del text
By Rows Per files
By Columns Per columnes
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6104,3 +6104,6 @@ breakText Glyphs Glyfy
@msg no object at path {shown} in the symbol's art v kresbě symbolu není na cestě {shown} žádný objekt
@msg give fill, stroke, opacity and/or blend zadejte fill, stroke, opacity a/nebo blend
@msg pass newName, registration, exportType, nineSlice and/or type zadejte newName, registration, exportType, nineSlice a/nebo type
Text Flow Tok textu
By Rows Po řádcích
By Columns Po sloupcích
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6002,3 +6002,6 @@ breakText Glyphs Glyffer
@msg no object at path {shown} in the symbol's art intet objekt ved stien {shown} i symbolets grafik
@msg give fill, stroke, opacity and/or blend angiv fill, stroke, opacity og/eller blend
@msg pass newName, registration, exportType, nineSlice and/or type angiv newName, registration, exportType, nineSlice og/eller type
Text Flow Tekstflow
By Rows Efter rækker
By Columns Efter kolonner
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5689,3 +5689,6 @@ breakText Glyphs Glyphen
@msg no object at path {shown} in the symbol's art kein Objekt am Pfad {shown} in der Grafik des Symbols
@msg give fill, stroke, opacity and/or blend geben Sie fill, stroke, opacity und/oder blend an
@msg pass newName, registration, exportType, nineSlice and/or type geben Sie newName, registration, exportType, nineSlice und/oder type an
Text Flow Textfluss
By Rows Nach Zeilen
By Columns Nach Spalten
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6056,3 +6056,6 @@ breakText Glyphs Γλύφοι
@msg no object at path {shown} in the symbol's art δεν υπάρχει αντικείμενο στη διαδρομή {shown} στο γραφικό του συμβόλου
@msg give fill, stroke, opacity and/or blend δώστε fill, stroke, opacity ή/και blend
@msg pass newName, registration, exportType, nineSlice and/or type δώστε newName, registration, exportType, nineSlice ή/και type
Text Flow Ροή κειμένου
By Rows Κατά γραμμές
By Columns Κατά στήλες
Can't render this file because it contains an unexpected character in line 164 and column 5.
+3
View File
@@ -6046,3 +6046,6 @@ breakText Glyphs Glifos
@msg no object at path {shown} in the symbol's art no hay ningún objeto en la ruta {shown} del arte del símbolo
@msg give fill, stroke, opacity and/or blend indique fill, stroke, opacity y/o blend
@msg pass newName, registration, exportType, nineSlice and/or type indique newName, registration, exportType, nineSlice y/o type
Text Flow Flujo de texto
By Rows Por filas
By Columns Por columnas
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6134,3 +6134,6 @@ breakText Glyphs Merkit
@msg no object at path {shown} in the symbol's art symbolin kuvituksessa ei ole objektia polussa {shown}
@msg give fill, stroke, opacity and/or blend anna fill, stroke, opacity ja/tai blend
@msg pass newName, registration, exportType, nineSlice and/or type anna newName, registration, exportType, nineSlice ja/tai type
Text Flow Tekstin kulku
By Rows Riveittäin
By Columns Sarakkeittain
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6101,3 +6101,6 @@ breakText Glyphs Glyphes
@msg no object at path {shown} in the symbol's art aucun objet au chemin {shown} dans l'illustration du symbole
@msg give fill, stroke, opacity and/or blend indiquez fill, stroke, opacity et/ou blend
@msg pass newName, registration, exportType, nineSlice and/or type indiquez newName, registration, exportType, nineSlice et/ou type
Text Flow Flux du texte
By Rows Par lignes
By Columns Par colonnes
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6090,3 +6090,6 @@ breakText Glyphs Glifák
@msg no object at path {shown} in the symbol's art a szimbólum grafikájában nincs objektum a(z) {shown} útvonalon
@msg give fill, stroke, opacity and/or blend adja meg a fill, stroke, opacity és/vagy blend értékét
@msg pass newName, registration, exportType, nineSlice and/or type adja meg a newName, registration, exportType, nineSlice és/vagy type értékét
Text Flow Szövegfolyás
By Rows Soronként
By Columns Oszloponként
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6002,3 +6002,6 @@ breakText Glyphs Glif
@msg no object at path {shown} in the symbol's art tidak ada objek di jalur {shown} dalam karya seni simbol
@msg give fill, stroke, opacity and/or blend berikan fill, stroke, opacity dan/atau blend
@msg pass newName, registration, exportType, nineSlice and/or type berikan newName, registration, exportType, nineSlice dan/atau type
Text Flow Alur teks
By Rows Per baris
By Columns Per kolom
Can't render this file because it contains an unexpected character in line 2188 and column 5.
+3
View File
@@ -5723,3 +5723,6 @@ breakText Glyphs Glifi
@msg no object at path {shown} in the symbol's art nessun oggetto al percorso {shown} nella grafica del simbolo
@msg give fill, stroke, opacity and/or blend specifica fill, stroke, opacity e/o blend
@msg pass newName, registration, exportType, nineSlice and/or type specifica newName, registration, exportType, nineSlice e/o type
Text Flow Flusso del testo
By Rows Per righe
By Columns Per colonne
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5790,3 +5790,6 @@ breakText Glyphs 字形
@msg no object at path {shown} in the symbol's art シンボルのアートのパス {shown} にオブジェクトがありません
@msg give fill, stroke, opacity and/or blend fill、stroke、opacity、blend のいずれかを指定してください
@msg pass newName, registration, exportType, nineSlice and/or type newName、registration、exportType、nineSlice、type のいずれかを指定してください
Text Flow テキストの流れ
By Rows 行方向
By Columns 列方向
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6093,3 +6093,6 @@ breakText Glyphs გლიფები
@msg no object at path {shown} in the symbol's art სიმბოლოს გრაფიკაში გზაზე {shown} ობიექტი არ არის
@msg give fill, stroke, opacity and/or blend მიუთითეთ fill, stroke, opacity და/ან blend
@msg pass newName, registration, exportType, nineSlice and/or type მიუთითეთ newName, registration, exportType, nineSlice და/ან type
Text Flow ტექსტის დინება
By Rows სტრიქონების მიხედვით
By Columns სვეტების მიხედვით
Can't render this file because it is too large.
+3
View File
@@ -5686,3 +5686,6 @@ breakText Glyphs 글리프
@msg no object at path {shown} in the symbol's art 심볼 아트의 경로 {shown}에 개체가 없습니다
@msg give fill, stroke, opacity and/or blend fill, stroke, opacity 및/또는 blend를 지정하세요
@msg pass newName, registration, exportType, nineSlice and/or type newName, registration, exportType, nineSlice 및/또는 type을 지정하세요
Text Flow 텍스트 흐름
By Rows 행 방향
By Columns 열 방향
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6046,3 +6046,6 @@ breakText Glyphs Glyphs
@msg no object at path {shown} in the symbol's art geen object op pad {shown} in de illustratie van het symbool
@msg give fill, stroke, opacity and/or blend geef fill, stroke, opacity en/of blend op
@msg pass newName, registration, exportType, nineSlice and/or type geef newName, registration, exportType, nineSlice en/of type op
Text Flow Tekstdoorloop
By Rows Per rij
By Columns Per kolom
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5959,3 +5959,6 @@ breakText Glyphs Glify
@msg no object at path {shown} in the symbol's art brak obiektu w ścieżce {shown} w grafice symbolu
@msg give fill, stroke, opacity and/or blend podaj fill, stroke, opacity i/lub blend
@msg pass newName, registration, exportType, nineSlice and/or type podaj newName, registration, exportType, nineSlice i/lub type
Text Flow Przepływ tekstu
By Rows Wierszami
By Columns Kolumnami
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6023,3 +6023,6 @@ breakText Glyphs Glifos
@msg no object at path {shown} in the symbol's art nenhum objeto no caminho {shown} na arte do símbolo
@msg give fill, stroke, opacity and/or blend informe fill, stroke, opacity e/ou blend
@msg pass newName, registration, exportType, nineSlice and/or type informe newName, registration, exportType, nineSlice e/ou type
Text Flow Fluxo do texto
By Rows Por linhas
By Columns Por colunas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -6081,3 +6081,6 @@ breakText Glyphs Глифы
@msg no object at path {shown} in the symbol's art в графике символа нет объекта по пути {shown}
@msg give fill, stroke, opacity and/or blend укажите fill, stroke, opacity и/или blend
@msg pass newName, registration, exportType, nineSlice and/or type укажите newName, registration, exportType, nineSlice и/или type
Text Flow Поток текста
By Rows По строкам
By Columns По столбцам
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -6156,3 +6156,6 @@ breakText Glyphs Glyfer
@msg no object at path {shown} in the symbol's art inget objekt vid sökvägen {shown} i symbolens bild
@msg give fill, stroke, opacity and/or blend ange fill, stroke, opacity och/eller blend
@msg pass newName, registration, exportType, nineSlice and/or type ange newName, registration, exportType, nineSlice och/eller type
Text Flow Textflöde
By Rows Radvis
By Columns Kolumnvis
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -6061,3 +6061,6 @@ breakText Glyphs Glifler
@msg no object at path {shown} in the symbol's art sembolün çiziminde {shown} yolunda nesne yok
@msg give fill, stroke, opacity and/or blend fill, stroke, opacity ve/veya blend verin
@msg pass newName, registration, exportType, nineSlice and/or type newName, registration, exportType, nineSlice ve/veya type verin
Text Flow Metin Akışı
By Rows Satırlara Göre
By Columns Sütunlara Göre
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -5703,3 +5703,6 @@ breakText Glyphs Гліфи
@msg no object at path {shown} in the symbol's art у графіці символу немає об’єкта за шляхом {shown}
@msg give fill, stroke, opacity and/or blend вкажіть fill, stroke, opacity та/або blend
@msg pass newName, registration, exportType, nineSlice and/or type вкажіть newName, registration, exportType, nineSlice та/або type
Text Flow Потік тексту
By Rows За рядками
By Columns За стовпцями
Can't render this file because it contains an unexpected character in line 178 and column 5.
+3
View File
@@ -5706,3 +5706,6 @@ breakText Glyphs Glyph
@msg no object at path {shown} in the symbol's art không có đối tượng tại đường dẫn {shown} trong hình của ký hiệu
@msg give fill, stroke, opacity and/or blend hãy cung cấp fill, stroke, opacity và/hoặc blend
@msg pass newName, registration, exportType, nineSlice and/or type hãy cung cấp newName, registration, exportType, nineSlice và/hoặc type
Text Flow Luồng văn bản
By Rows Theo hàng
By Columns Theo cột
Can't render this file because it contains an unexpected character in line 200 and column 5.
+3
View File
@@ -5946,3 +5946,6 @@ breakText Glyphs 字形
@msg no object at path {shown} in the symbol's art 符号图稿中路径 {shown} 处没有对象
@msg give fill, stroke, opacity and/or blend 请提供 fill、stroke、opacity 和/或 blend
@msg pass newName, registration, exportType, nineSlice and/or type 请提供 newName、registration、exportType、nineSlice 和/或 type
Text Flow 文本流向
By Rows 按行
By Columns 按列
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5743,3 +5743,6 @@ breakText Glyphs 字符
@msg no object at path {shown} in the symbol's art 符號圖稿中路徑 {shown} 處沒有物件
@msg give fill, stroke, opacity and/or blend 請提供 fill、stroke、opacity 和/或 blend
@msg pass newName, registration, exportType, nineSlice and/or type 請提供 newName、registration、exportType、nineSlice 和/或 type
Text Flow 文字流向
By Rows 依列
By Columns 依欄
Can't render this file because it contains an unexpected character in line 6 and column 47.
+11
View File
@@ -4593,6 +4593,17 @@ again, so SVG, PDF and EPS show the aligned text.
{"name":"run_command","arguments":{"command":"text.areaOptions","params":{"verticalAlign":"center"}}}
```
### Text flow
`text.areaOptions {flow: columns|rows}` (Area Type Options › Text Flow) sets the order area type with several rows
and columns fills its cells: `columns` (the default) runs down each column's rows, then on to the next column;
`rows` runs across each row's columns, then on to the next row. The reply reports `flow`; other values are refused.
Native files save it only when it is `rows`, so files from before it open by columns.
```json
{"name":"run_command","arguments":{"command":"text.areaOptions","params":{"rows":2,"columns":3,"flow":"rows"}}}
```
## Fitting area type: Auto Size, Shrink Text to Fit, overflow
`text.areaOptions` (and `text.create`'s `area`, `text.createInPath` in area mode) take a `fit`:
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -1,6 +1,6 @@
# Type parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Digits: the native digits of the Indic scripts, #4942; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Area Type Options › Text Flow by rows or by columns, #4978; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
Type is Illustrator's largest single area by use (weight 13 of 105 across "Type core" and "Type advanced" in
[target-app-parity.md](target-app-parity.md)). This checklist lists the features one by one. Status is from the
@@ -21,7 +21,7 @@ against Illustrator's public documentation; **estimated** unless noted.
| Soft return (forced line break, Shift+Enter) | done | Shift+Enter inserts a soft return (U+2028), breaking the line without starting a new paragraph; paragraph spacing (space before/after) and first-line indents apply only at paragraph ends; continuation lines in lists do not repeat the marker; soft return lines in justified text do not spread (#2754) |
| Native font-family labels | partial | With Show Font Names in English off, localized legacy names are used when typographic names contain no native label (#2208); loaded-font and catalog-only regressions. Choosing among multiple native languages is unchanged. |
| Point, area and type-on-a-path type; vertical variants | done / partial | vertical point, area and path type have initial support; fullwidth `!` / `?` keep the nominal glyph upright when no vertical alternate exists (Unicode Tu); in a non-rectangular area a word too wide for the top lines moves down to the first line that holds it (#1615); Escape while typing ends the edit, keeps the type selected and switches to the Selection tool (#1802); `text.create` and `text.createInPath` take `justify` (#1752); MCP `add_text` forwards `vertical` to both commands |
| Area Type Options (rows, columns, inset, first baseline, Auto Size, Shrink Text to Fit) | done | the commands that make or restyle type say when it does not fit its frame; MCP `add_text` takes Auto Size and Shrink Text to Fit (#1471) |
| Area Type Options (rows, columns, inset, first baseline, Text Flow by rows or by columns, Auto Size, Shrink Text to Fit) | done | Text Flow fills the cells down each column first (By Columns, the default, as files from before it open) or across each row first (By Rows), in the dialog and `text.areaOptions {flow}` (#4978); the commands that make or restyle type say when it does not fit its frame; MCP `add_text` takes Auto Size and Shrink Text to Fit (#1471) |
| Threaded text, Fit Headline, Text Wrap | done | Fit Headline sees wrapping in a frame one line tall (#1444); a thread holds area type and type on a path in any mix, as Illustrator links type in an area or along a path ([help](https://helpx.adobe.com/illustrator/using/manage-text-area.html)) |
| Threading from the ports: selected area type and type on a path show an in port and an out port, empty, with an arrow on a side where the frame is threaded, or with a red "+" in the out port when the text doesn't fit; a click on either port loads the text; a click on a closed path, area type or type on a path threads into it, a click on empty canvas makes a frame of the same kind, shape and size there, a drag draws a rectangular frame; the frame placed comes after the clicked one from its out port and before it from its in port (the story then starts there); Escape or another tool cancels; a double-click on a threaded port breaks the thread there | partial | #2572: the Selection tool, `text.thread.add {from, to?, rect?, at?, before?}`, as the reference app's help describes ports and threading ([archived help, Threading text between objects](https://web.archive.org/web/20210119051048/https://helpx.adobe.com/illustrator/using/creating-text.html), [create text threads](https://helpx.adobe.com/illustrator/desktop/design-with-text/add-manage-text/create-text-threads-between-objects.html)); area type's in port sits on the left side below the top-left corner, type on a path's just before its start bracket; Show Text Threads draws its lines between the same ports; with more than 1,000 such objects selected no ports are drawn. a double-click on a port where the frame is threaded breaks the thread there, the story flowing into the frames before the break and the frames after it left empty, still threaded with each other when two or more (`text.thread.break`, [archived help](https://web.archive.org/web/20220127090642/https://helpx.adobe.com/illustrator/using/creating-text.html)). Remaining: the ports only take clicks with the Selection tool, and the loaded cursor borrows the I-beam until it's drawn |
| Type on a Path effects and options; brackets | done | #429 |
@@ -72,6 +72,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | Area Type Options › Text Flow (#4978): By Rows / By Columns radio buttons and `text.areaOptions {flow}`; area type with several rows and columns fills them across each row first or down each column first (the default, so existing documents are unchanged; saved only when by rows); scores unchanged |
| 2026-11-01 | minor | Digits (#4942): the native digits of Devanagari, Bengali, Gurmukhi, Gujarati, Odia, Tamil, Telugu, Kannada and Malayalam in the Character panel menu › Digits and `{digits}`; scores unchanged |
| 2026-11-01 | minor | Language per character (#4946): Character panel › Language, `text.setFormat` / `text.setRangeStyle {language}`, character styles; text is shaped in its language (`locl`, #4940); new type takes the document's; Indian languages in the list (#4943); scores unchanged |
| 2026-10-31 | minor | PDF import finds an installed font whose PDF name holds hyphens (`Noto-Sans-Bold` is Noto Sans, Bold: the longest part before a hyphen that names an installed family) and a composite font by its CIDFont's name rather than its base name, which ends in the CMap's (`Name-Identity-H`, [ISO 32000-1 §9.7.6.1](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf)); a missing font is split only before a style part (`Helvetica-BoldOblique`), so it is reported as `MS PGothic`, not `MS`; scores unchanged |