mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-22 19:08:39 +00:00
G4: System Layout spaces every text object in whole points
Squashed from #3412 onto main.
This commit is contained in:
1 parent
675b759a37
commit
adbe0566a6
15 files changed
+199
-19
No files matched your search
@@ -238,6 +238,11 @@ pub struct DocSetup {
|
||||
pub export_text: ExportText,
|
||||
/// Background Contents chosen in New Document.
|
||||
pub background: Background,
|
||||
/// Character panel menu › System Layout: every text object advances by whole points. Off by
|
||||
/// default and for documents from before it, and saved only when on.
|
||||
/// <https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/turn-fractional-character-widths-off-or-on.html>
|
||||
#[serde(default, rename = "systemLayout", skip_serializing_if = "crate::skip::is_default")]
|
||||
pub system_layout: bool,
|
||||
}
|
||||
|
||||
impl Default for DocSetup {
|
||||
@@ -260,6 +265,7 @@ impl Default for DocSetup {
|
||||
small_caps_size: 70.0,
|
||||
export_text: ExportText::Editable,
|
||||
background: Background::Transparent,
|
||||
system_layout: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1413,6 +1413,11 @@ pub struct TextObject {
|
||||
/// Off for new type and for documents from before it, and saved only when on.
|
||||
#[serde(default, rename = "opticalMarginAlignment", skip_serializing_if = "crate::skip::is_default")]
|
||||
pub optical_margin: bool,
|
||||
/// Character panel menu › System Layout, copied from the document: when on, each character
|
||||
/// that takes space advances by a whole number of points. Off by default, and saved only when
|
||||
/// on. The document setting is [`crate::DocSetup::system_layout`].
|
||||
#[serde(default, rename = "systemLayout", skip_serializing_if = "crate::skip::is_default")]
|
||||
pub system_layout: bool,
|
||||
/// Character panel menu › Fractional Widths: spacing between characters uses fractions of a
|
||||
/// point. On for new type and for documents from before it, and saved only when off. Off, each
|
||||
/// character that takes any space advances by a whole number of points, at least one, so small
|
||||
@@ -1517,6 +1522,7 @@ impl TextObject {
|
||||
path_spacing: 0.0,
|
||||
wrap: Vec::new(),
|
||||
optical_margin: false,
|
||||
system_layout: false,
|
||||
fractional_widths: true,
|
||||
cached_bounds: None,
|
||||
cached_baselines: Vec::new(),
|
||||
|
||||
@@ -441,6 +441,7 @@ fn import_text(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
return Err(bad(C, "the text is empty"));
|
||||
}
|
||||
let mut t = TextObject::point(Point::ZERO, text, super::super::create::new_type_style(s, &Value::Null));
|
||||
t.system_layout = s.doc()?.doc.setup.system_layout;
|
||||
crate::cmd::typecmd::refresh_bounds(&mut t);
|
||||
let n = Node::new(NodeId(1), NodeKind::Text(Box::new(t)));
|
||||
let count = s.load_clipboard(Clipboard { nodes: vec![n], ..Default::default() }, p)?;
|
||||
|
||||
@@ -506,6 +506,7 @@ fn text_create(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
t.kind = vectorcraft_doc::TextKind::Area { frame: shapes::rectangle(Rect::new(0.0, 0.0, w, h)) };
|
||||
t.area.fit = new_area_fit(s, a, "text.create")?;
|
||||
}
|
||||
t.system_layout = s.doc()?.doc.setup.system_layout;
|
||||
if bool_or(p, "placeholder", false) {
|
||||
super::typemenu::fill_with_placeholder(&mut t);
|
||||
} else {
|
||||
|
||||
@@ -353,6 +353,7 @@ fn artwork(doc: &mut Document, d: &Dimension, old: &[NodeId]) -> Result<Vec<Arc<
|
||||
};
|
||||
let mut text = TextObject::point(Point::ZERO, &label, style);
|
||||
text.para.justify = vectorcraft_doc::Justify::Center;
|
||||
text.system_layout = doc.setup.system_layout;
|
||||
super::typecmd::refresh_bounds(&mut text);
|
||||
// Centre the label's box on its anchor point, along the line.
|
||||
let bounds = text.cached_bounds.unwrap_or_else(|| text.estimate_bounds());
|
||||
|
||||
@@ -373,6 +373,7 @@ impl Gen<'_> {
|
||||
let style = label_style(self.styles, role);
|
||||
let mut t = TextObject::point(at, s, style);
|
||||
t.para.justify = justify;
|
||||
t.system_layout = self.d.setup.system_layout;
|
||||
refresh_bounds(&mut t);
|
||||
Arc::new(Node::new(self.d.alloc_id(), NodeKind::Text(Box::new(t))))
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ pub(super) fn area_text(story: Story, frame: Rect, setup: &vectorcraft_doc::DocS
|
||||
t.set_paragraph_styles(story.paras);
|
||||
}
|
||||
t.kind = vectorcraft_doc::TextKind::Area { frame: shapes::rectangle(Rect::from_origin_size(Point::ORIGIN, frame.size())) };
|
||||
t.system_layout = setup.system_layout;
|
||||
crate::cmd::typecmd::refresh_bounds(&mut t);
|
||||
t
|
||||
}
|
||||
|
||||
@@ -862,6 +862,7 @@ fn create_in_path(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
path_spacing: 0.0,
|
||||
wrap: Vec::new(),
|
||||
optical_margin: false,
|
||||
system_layout: false,
|
||||
fractional_widths: true,
|
||||
cached_bounds: None,
|
||||
cached_baselines: Vec::new(),
|
||||
@@ -870,6 +871,7 @@ fn create_in_path(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if !on_path {
|
||||
t.area.fit = super::create::new_area_fit(s, p, C)?;
|
||||
}
|
||||
t.system_layout = s.doc()?.doc.setup.system_layout;
|
||||
if bool_or(p, "placeholder", false) {
|
||||
super::typemenu::fill_with_placeholder(&mut t);
|
||||
} else {
|
||||
@@ -1085,7 +1087,8 @@ fn headline_tracking(t: &TextObject, target: f64) -> Option<f64> {
|
||||
path_spacing: 0.0,
|
||||
wrap: Vec::new(),
|
||||
optical_margin: false,
|
||||
fractional_widths: true,
|
||||
system_layout: t.system_layout,
|
||||
fractional_widths: t.fractional_widths,
|
||||
cached_bounds: None,
|
||||
cached_baselines: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -205,7 +205,7 @@ pub(crate) fn with_threads(d: &Document, ids: &[NodeId]) -> Vec<NodeId> {
|
||||
}
|
||||
|
||||
/// A closed path converted to an empty area-type frame with `style`.
|
||||
fn frame_from_path(n: &Node, style: vectorcraft_doc::CharStyle) -> Option<Node> {
|
||||
fn frame_from_path(n: &Node, style: vectorcraft_doc::CharStyle, system_layout: bool) -> Option<Node> {
|
||||
let NodeKind::Path { path, .. } = &n.kind else { return None };
|
||||
if !path.subpaths.first().is_some_and(|s| s.closed) {
|
||||
return None;
|
||||
@@ -213,6 +213,7 @@ fn frame_from_path(n: &Node, style: vectorcraft_doc::CharStyle) -> Option<Node>
|
||||
let mut t = TextObject::point(vectorcraft_geom::Point::ZERO, "", style);
|
||||
t.kind = TextKind::Area { frame: path.clone() };
|
||||
t.xf = vectorcraft_geom::Affine::IDENTITY;
|
||||
t.system_layout = system_layout;
|
||||
refresh_bounds(&mut t);
|
||||
Some(Node::new(n.id, NodeKind::Text(Box::new(t))))
|
||||
}
|
||||
@@ -222,7 +223,8 @@ fn create(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let ids = selected_roots(s)?;
|
||||
let d = &s.doc()?.doc;
|
||||
let style = ids.iter().find_map(|i| text_of(d, *i)).map(|t| t.first_style()).unwrap_or_default();
|
||||
let ok = ids.iter().all(|i| text_of(d, *i).is_some() || d.node(*i).is_some_and(|n| frame_from_path(n, style.clone()).is_some()));
|
||||
let system = d.setup.system_layout;
|
||||
let ok = ids.iter().all(|i| text_of(d, *i).is_some() || d.node(*i).is_some_and(|n| frame_from_path(n, style.clone(), system).is_some()));
|
||||
if ids.len() < 2 || !ok || !ids.iter().any(|i| text_of(d, *i).is_some()) {
|
||||
return Err(bad(C, "select area type and closed paths (at least one area type object)"));
|
||||
}
|
||||
@@ -231,7 +233,7 @@ fn create(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
for id in &ids {
|
||||
if text_of(d, *id).is_none() {
|
||||
let n = d.node(*id).cloned().ok_or(EngineError::NoNode(*id))?;
|
||||
let f = frame_from_path(&n, style.clone()).ok_or_else(|| bad(C, "not a closed path"))?;
|
||||
let f = frame_from_path(&n, style.clone(), d.setup.system_layout).ok_or_else(|| bad(C, "not a closed path"))?;
|
||||
*d.node_mut(*id).ok_or(EngineError::NoNode(*id))? = f;
|
||||
}
|
||||
}
|
||||
@@ -271,7 +273,8 @@ fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
(Some(v), None, None) => {
|
||||
let to = checked_id(s, v, C)?;
|
||||
let parent = d.parent_of(to).and_then(|i| d.node(i));
|
||||
let frame = text_of(d, to).is_some() || d.node(to).is_some_and(|n| frame_from_path(n, style.clone()).is_some());
|
||||
let system = d.setup.system_layout;
|
||||
let frame = text_of(d, to).is_some() || d.node(to).is_some_and(|n| frame_from_path(n, style.clone(), system).is_some());
|
||||
if to == from || !frame || parent.is_some_and(|n| matches!(n.kind, NodeKind::Compound { .. })) {
|
||||
return Err(bad(C, "`to` must be a closed path, area type or type on a path other than `from`"));
|
||||
}
|
||||
@@ -316,7 +319,8 @@ fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
None => {
|
||||
if text_of(d, to).is_none() {
|
||||
let n = d.node(to).cloned().ok_or(EngineError::NoNode(to))?;
|
||||
*d.node_mut(to).ok_or(EngineError::NoNode(to))? = frame_from_path(&n, style).ok_or_else(|| bad(C, "not a closed path"))?;
|
||||
*d.node_mut(to).ok_or(EngineError::NoNode(to))? =
|
||||
frame_from_path(&n, style, d.setup.system_layout).ok_or_else(|| bad(C, "not a closed path"))?;
|
||||
}
|
||||
to
|
||||
}
|
||||
|
||||
@@ -52,6 +52,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_selection,
|
||||
optical_margin_alignment
|
||||
),
|
||||
cmd!(
|
||||
"type.systemLayout",
|
||||
"System Layout",
|
||||
[],
|
||||
None,
|
||||
"{on?: bool} turn System Layout on or off for the document (on omitted: the opposite of the document's setting) and every text object in it. On, each character that takes any space advances by a whole number of points, at least one, so small type does not run together; a character with no width stays at none. Off (the default, and what a document from before it reads as), spacing uses fractions of a point. New point, area and path type inherits the document's setting → {on}",
|
||||
has_doc,
|
||||
system_layout
|
||||
),
|
||||
cmd!(
|
||||
"type.fractionalWidths",
|
||||
"Fractional Widths",
|
||||
@@ -155,6 +164,31 @@ fn optical_margin_alignment(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"on": on, "ids": ids.iter().map(|id| id.0).collect::<Vec<_>>()}))
|
||||
}
|
||||
|
||||
/// Character panel menu › System Layout: set `on` (without it, the opposite of the document's
|
||||
/// setting) on the document and every text object, in one undo step. Off is the default.
|
||||
fn system_layout(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let current = s.doc()?.doc.setup.system_layout;
|
||||
let on = p.get("on").and_then(Value::as_bool).unwrap_or(!current);
|
||||
s.edit("System Layout", |d, _| {
|
||||
d.setup.system_layout = on;
|
||||
let mut ids = vec![];
|
||||
d.walk(|n| {
|
||||
if matches!(n.kind, NodeKind::Text(_)) {
|
||||
ids.push(n.id);
|
||||
}
|
||||
});
|
||||
for id in ids {
|
||||
let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else {
|
||||
continue;
|
||||
};
|
||||
t.system_layout = on;
|
||||
refresh_bounds(t);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({ "on": on }))
|
||||
}
|
||||
|
||||
/// Character panel menu › Fractional Widths: set `on` (without it, the opposite of the first
|
||||
/// object's setting) on every selected text object and every frame of their threads, in one undo
|
||||
/// step. On is the default.
|
||||
@@ -1167,6 +1201,55 @@ mod area_tests {
|
||||
assert!(!serde_json::from_value::<TextObject>(old).unwrap().optical_margin);
|
||||
}
|
||||
|
||||
/// Character panel menu › System Layout: `type.systemLayout` sets the document and every text
|
||||
/// object in one undo step. New type inherits it. Off by default and in a document from before
|
||||
/// the flag, and saved only when on. On, each positive advance is a whole number of points.
|
||||
#[test]
|
||||
fn system_layout_sets_the_document_and_every_text_object() {
|
||||
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": 40, "size": 13, "text": "Hi"})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("text.setStyle", &json!({"id": id, "tracking": 50})).unwrap();
|
||||
let other = s.execute("text.create", &json!({"x": 10, "y": 80, "text": "Yo"})).unwrap()["id"].as_u64().unwrap();
|
||||
let text = |s: &Session, id: u64| match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => (**t).clone(),
|
||||
_ => panic!("text"),
|
||||
};
|
||||
let setup = |s: &Session| s.doc().unwrap().doc.setup.clone();
|
||||
let advances = |s: &Session| {
|
||||
vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &text(s, id)).glyphs.iter().map(|g| g.advance).collect::<Vec<_>>()
|
||||
};
|
||||
assert!(!setup(&s).system_layout);
|
||||
assert!(!text(&s, id).system_layout && !text(&s, other).system_layout);
|
||||
assert!(serde_json::to_value(setup(&s)).unwrap().get("systemLayout").is_none());
|
||||
assert!(serde_json::to_value(text(&s, id)).unwrap().get("systemLayout").is_none());
|
||||
let fractional = advances(&s);
|
||||
assert!(fractional.iter().any(|a| (a - a.round()).abs() > 1e-6), "{fractional:?}");
|
||||
assert_eq!(s.execute("type.systemLayout", &json!({})).unwrap()["on"], true);
|
||||
assert!(setup(&s).system_layout);
|
||||
assert!(text(&s, id).system_layout && text(&s, other).system_layout);
|
||||
let whole = advances(&s);
|
||||
assert!(whole.iter().all(|a| *a >= 1.0 && (a - a.round()).abs() < 1e-9), "{whole:?}");
|
||||
assert!(fractional.iter().zip(&whole).any(|(a, b)| (a - b).abs() > 0.05), "{fractional:?} {whole:?}");
|
||||
assert_eq!(serde_json::to_value(setup(&s)).unwrap()["systemLayout"], true);
|
||||
assert_eq!(serde_json::to_value(text(&s, id)).unwrap()["systemLayout"], true);
|
||||
// New type inherits the document's setting.
|
||||
let made = s.execute("text.create", &json!({"x": 10, "y": 120, "text": "Z"})).unwrap()["id"].as_u64().unwrap();
|
||||
assert!(text(&s, made).system_layout);
|
||||
// A boolean given as text is ignored, so the setting toggles off.
|
||||
assert_eq!(s.execute("type.systemLayout", &json!({"on": "no"})).unwrap()["on"], false);
|
||||
assert!(!setup(&s).system_layout && !text(&s, id).system_layout && !text(&s, made).system_layout);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(setup(&s).system_layout && text(&s, id).system_layout);
|
||||
let back = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap();
|
||||
assert!(back.setup.system_layout);
|
||||
let NodeKind::Text(t) = &back.node(NodeId(id)).unwrap().kind else { panic!("text") };
|
||||
assert!(t.system_layout);
|
||||
let mut old = serde_json::to_value(setup(&s)).unwrap();
|
||||
old.as_object_mut().unwrap().remove("systemLayout");
|
||||
assert!(!serde_json::from_value::<vectorcraft_doc::DocSetup>(old).unwrap().system_layout);
|
||||
}
|
||||
|
||||
/// Character panel menu › Fractional Widths: `type.fractionalWidths` sets or toggles the flag
|
||||
/// on the selected type in one undo step. New type and a document from before the flag are on,
|
||||
/// and the flag is saved only when off. Off, each positive advance is a whole number of points.
|
||||
|
||||
@@ -37,6 +37,8 @@ struct Ctx<'a> {
|
||||
/// a quarter turn back (see [`stands_upright`]); [`TextLayout::line_xf`] then stands the lines
|
||||
/// up as columns.
|
||||
vertical: bool,
|
||||
/// Character panel menu › System Layout. On: positive advances are whole points.
|
||||
system_layout: bool,
|
||||
/// Character panel menu › Fractional Widths. Off, positive advances are whole points.
|
||||
fractional_widths: bool,
|
||||
out: TextLayout,
|
||||
@@ -73,9 +75,9 @@ impl Ctx<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fractional Widths off: whole-point advances, after tate-chu-yoko and the upright cell
|
||||
// have set the widths the line breaker will use. A zero advance stays zero.
|
||||
if !self.fractional_widths {
|
||||
// System Layout or Fractional Widths off: whole-point advances, after tate-chu-yoko and the
|
||||
// upright cell have set the widths the line breaker will use. A zero advance stays zero.
|
||||
if self.system_layout || !self.fractional_widths {
|
||||
whole_character_widths(&mut v);
|
||||
}
|
||||
v
|
||||
@@ -387,6 +389,7 @@ fn layout_once(db: &FontDb, t: &TextObject, opts: &LayoutOptions) -> TextLayout
|
||||
default: CharStyle::default(),
|
||||
opts,
|
||||
vertical,
|
||||
system_layout: t.system_layout,
|
||||
fractional_widths: t.fractional_widths,
|
||||
out: TextLayout::default(),
|
||||
};
|
||||
@@ -454,9 +457,9 @@ fn layout_once(db: &FontDb, t: &TextObject, opts: &LayoutOptions) -> TextLayout
|
||||
cx.out
|
||||
}
|
||||
|
||||
/// Fractional Widths off: each character that takes space advances by a whole number of points,
|
||||
/// at least one, so small type does not collapse onto itself. A zero advance (a tate-chu-yoko
|
||||
/// continuation, a zero-width mark) stays zero.
|
||||
/// System Layout on or Fractional Widths off: each character that takes space advances by a whole
|
||||
/// number of points, at least one, so small type does not collapse onto itself. A zero advance (a
|
||||
/// tate-chu-yoko continuation, a zero-width mark) stays zero.
|
||||
fn whole_character_widths(glyphs: &mut [SGlyph]) {
|
||||
for g in glyphs {
|
||||
if g.adv.is_finite() && g.adv > 0.0 {
|
||||
|
||||
@@ -67,6 +67,34 @@ fn add_font_rejects_garbage_and_duplicates() {
|
||||
assert_eq!(db().add_font(bytes), 0, "already bundled");
|
||||
}
|
||||
|
||||
/// Character panel menu › System Layout: each character that takes space advances by a whole
|
||||
/// number of points, at least one. Off (the default) keeps the font's fractional advances. A
|
||||
/// tate-chu-yoko continuation stays at zero.
|
||||
/// <https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/turn-fractional-character-widths-off-or-on.html>
|
||||
#[test]
|
||||
fn system_layout_rounds_positive_advances_to_whole_points() {
|
||||
let mut off = point("Hi", style(13.0));
|
||||
off.runs[0].style.tracking = 50.0;
|
||||
assert!(!off.system_layout);
|
||||
let fractional: Vec<f64> = layout(db(), &off).glyphs.iter().map(|g| g.advance).collect();
|
||||
assert!(fractional.iter().any(|a| (a - a.round()).abs() > 1e-6), "{fractional:?}");
|
||||
let mut on = off.clone();
|
||||
on.system_layout = true;
|
||||
let whole: Vec<f64> = layout(db(), &on).glyphs.iter().map(|g| g.advance).collect();
|
||||
assert!(whole.iter().all(|a| *a >= 1.0 && (a - a.round()).abs() < 1e-9), "{whole:?}");
|
||||
assert!(fractional.iter().zip(&whole).any(|(a, b)| (a - b).abs() > 0.05), "{fractional:?} {whole:?}");
|
||||
let mut tiny = point("i", style(0.4));
|
||||
tiny.system_layout = true;
|
||||
let laid = layout(db(), &tiny);
|
||||
assert!(laid.glyphs.iter().any(|g| (g.advance - 1.0).abs() < 1e-9), "{:?}", laid.glyphs.iter().map(|g| g.advance).collect::<Vec<_>>());
|
||||
let mut tcy = point("10", style(20.0));
|
||||
tcy.vertical = true;
|
||||
tcy.system_layout = true;
|
||||
let adv: Vec<f64> = layout(db(), &tcy).glyphs.iter().map(|g| g.advance).collect();
|
||||
assert!(adv.contains(&0.0), "a tate-chu-yoko continuation stays at zero: {adv:?}");
|
||||
assert!(adv.iter().all(|a| *a == 0.0 || (a - a.round()).abs() < 1e-9), "{adv:?}");
|
||||
}
|
||||
|
||||
/// Character panel menu › Fractional Widths, off: each character that takes space advances by a
|
||||
/// whole number of points, at least one. On (the default) keeps the font's fractional advances,
|
||||
/// including tracking. A tate-chu-yoko continuation stays at zero.
|
||||
|
||||
@@ -807,7 +807,14 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
app.run("type.fractionalWidths", p).ok();
|
||||
}
|
||||
for l in ["Standard Vertical Roman Alignment", "Tate-chu-yoko", "System Layout"] {
|
||||
// System Layout: whole-point spacing for every text object in the document. It does not need
|
||||
// a type selection; a document is enough.
|
||||
let open = app.session.active().is_some();
|
||||
let system = app.session.active().is_some_and(|st| st.doc.setup.system_layout);
|
||||
if menu_item(ui, tl!("System Layout"), open, system) {
|
||||
app.run("type.systemLayout", json!({})).ok();
|
||||
}
|
||||
for l in ["Standard Vertical Roman Alignment", "Tate-chu-yoko"] {
|
||||
menu_item(ui, tl!(l), false, false);
|
||||
}
|
||||
ui.separator();
|
||||
@@ -1163,6 +1170,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Character panel menu › System Layout is off for a new document, and a click turns whole-point
|
||||
/// spacing on and back off for every text object.
|
||||
#[test]
|
||||
fn system_layout_menu_item_toggles_the_document() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Hi", "size": 13})).unwrap()["id"].as_u64().unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let on = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
let object = match &st.doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => t.system_layout,
|
||||
_ => panic!("text"),
|
||||
};
|
||||
(st.doc.setup.system_layout, object)
|
||||
};
|
||||
let row = |app: &mut VectorcraftApp| {
|
||||
let texts = super::super::tests_appearance::frame_events(&ctx, app, vec![], menu);
|
||||
texts.into_iter().find(|(t, _)| t.trim_start_matches(['✓', ' ']) == "System Layout")
|
||||
};
|
||||
assert_eq!(on(&app), (false, false));
|
||||
assert!(row(&mut app).is_some_and(|(t, _)| !t.starts_with('✓')));
|
||||
let at = row(&mut app).unwrap().1;
|
||||
super::super::tests_appearance::click(&ctx, &mut app, at.center(), menu);
|
||||
assert_eq!(on(&app), (true, true));
|
||||
assert!(row(&mut app).is_some_and(|(t, _)| t.starts_with('✓')));
|
||||
let at = row(&mut app).unwrap().1;
|
||||
super::super::tests_appearance::click(&ctx, &mut app, at.center(), menu);
|
||||
assert_eq!(on(&app), (false, false));
|
||||
assert!(row(&mut app).is_some_and(|(t, _)| !t.starts_with('✓')));
|
||||
}
|
||||
|
||||
/// Character panel menu › Fractional Widths is on for new type, and a click turns it off and
|
||||
/// back on for the whole type object.
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (ruby (ふりがな) for live text: `type.ruby` sets or removes a reading over a byte range, above or below (right of the column in vertical type), aligned, sized and overhanging or squeezed; saved natively, laid out with line growth and drawn, with SVG/PDF outlines where ruby is present; scores and hours unchanged; previously:; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; one selected clip group's Properties panel has Edit Clipping Path and Edit Contents at its top, G1; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 Line truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (ruby (ふりがな) for live text: `type.ruby` sets or removes a reading over a byte range, above or below (right of the column in vertical type), aligned, sized and overhanging or squeezed; saved natively, laid out with line growth and drawn, with SVG/PDF outlines where ruby is present; scores and hours unchanged; previously:; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; one selected clip group's Properties panel has Edit Clipping Path and Edit Contents at its top, G1; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; G4: System Layout; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; PhotoshopLine truncated
|
||||
|
||||
The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is
|
||||
the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md),
|
||||
@@ -340,7 +340,7 @@ about 1–1.5 agent-hours each, so the other rows (estimated on the older scale)
|
||||
| Raster effects (Effect Gallery, Document Raster Effects Settings) | 4 | 35% | ~36 Photoshop-style filters (Artistic, Brush Strokes, Sketch, Stylize), Texturizer's Load Texture and the Effect Gallery; Document Raster Effects Settings, raster effects in PDF, the pixel-filter pipeline, Radial Blur, Smart Blur (Normal, Edge Only and Overlay Edge), Unsharp Mask, Glowing Edges, Distort (Diffuse Glow, Glass, Ocean Ripple), Pixelate (Color Halftone, Crystallize, Mezzotint, Pointillize), Texture (Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass, Texturizer) and Video (De-Interlace, NTSC Colors) are done | 8–14 |
|
||||
| 3D and Materials | 4 | ~25% | Initial Revolve, Extrude & Bevel, Inflate and Rotate, with simple lighting and four original lighting presets; materials, shadows, ray tracing, 3D Classic and more complete visibility remain | 25–45 |
|
||||
| Type advanced | 4 | 50% | CJK composition for vertical type (mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko; No Break is done), variable font axis sliders (named instances are styles), tab leaders, spell check (open dictionary), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 |
|
||||
| Type core | 9 | 80% | Middle Eastern features beyond bidi layout and Paragraph Direction (Character Direction, kashidas, the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); Hyphenation… and Justification… options (Every-line Composer for justified and ragged text, per object, is done; it is tuned by eye, not yet compared side by side with Illustrator; mixed type sizes in a paragraph still break single-line in non-rectangular area type and with text wrap), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034). Fractional Widths is done; System Layout remains a panel stub | 14–19 |
|
||||
| Type core | 9 | 80% | Middle Eastern features beyond bidi layout and Paragraph Direction (Character Direction, kashidas, the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); Hyphenation… and Justification… options (Every-line Composer for justified and ragged text, per object, is done; it is tuned by eye, not yet compared side by side with Illustrator; mixed type sizes in a paragraph still break single-line in non-rectangular area type and with text wrap), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034). Fractional Widths and System Layout are done | 14–19 |
|
||||
| Symbols, blends, envelopes, Repeat, perspective | 5 | 90% | symbol libraries (original), dynamic symbols, envelope Anti-Alias/Preserve Shape output and warp point editing, brush interpolation in blends (M8 fidelity pass done); Symbol Options › Registration and Export Type done (9-slice guides remain) | 4–7 |
|
||||
| Image Trace, graphs, image tools | 3 | 72% | graph Column designs (Graph Design, Marker designs and series colours set with Group Selection are done), Create Object Mosaic, per-feature stroke/fill choice and varying widths in Image Trace's Create Strokes (Vector Halftone and the Image Trace View modes, Palette and Create Strokes are done) | 6–10 |
|
||||
| Layers, artboards, document setup | 5 | 82% | Layers panel: a confirmation before deleting layers that hold art, Collect for Export, row context menus, the Layer Options dialog for several rows with mixed values, Paste Remembers Layers for sublayers, drag-scrolling a long panel while dragging rows; artboard preset polish (artboard escape routing and document setup modal handoff done; Move Artwork for artboard X/Y edits, Rearrange All Artboards, Document Setup, New Document and the rest of the Layers panel are done) | 4–7 |
|
||||
@@ -922,11 +922,12 @@ Evidence for the shared light Color: geometry tests in `tests_lighting`, effect
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-21 | minor | Type core: System Layout (Character panel menu, whole document, off by default) is done; scores and hours unchanged |
|
||||
| 2026-10-22 | minor | Ruby (ふりがな) for live text: `type.ruby` sets/removes a reading over a byte range, above or below (right of the column in vertical type), aligned, sized and overhanging or squeezed; saved natively, laid out with line growth, drawn on the canvas and in SVG/PDF (outlines where ruby is present); scores and hours unchanged |
|
||||
| 2026-10-21 | minor | 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged |
|
||||
| 2026-10-21 | minor | Properties: one selected clip group has Edit Clipping Path and Edit Contents at the top of the panel (public doc cited) (G1). Scores and hours unchanged. |
|
||||
| 2026-10-21 | minor | Print: the Print dialog's Media list carries the Japanese papers — JIS B4 (257 × 364 mm), JIS B5 (182 × 257 mm) and JIS B6 next to the ISO B4/B5, which keep their ISO 216 sizes, plus はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; the JSON id or the dialog label names a paper, any case and spacing. Print score and hours unchanged. |
|
||||
| 2026-10-21 | minor | Type core: Fractional Widths (Character panel menu, per type object, on by default) is done; System Layout remains a panel stub; scores and hours unchanged |
|
||||
| 2026-10-21 | minor | Type core: Fractional Widths (Character panel menu, per type object, on by default) is done; scores and hours unchanged |
|
||||
| 2026-10-20 | minor | Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; existing opening gestures retained; scores and hours unchanged. |
|
||||
| 2026-10-20 | minor | Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; scores and hours unchanged. |
|
||||
| 2026-10-20 | minor | M3.57: CSS swatch libraries preserve fractional stop positions in linear and radial gradients; synthetic color-crate regression. Scores and hours unchanged. |
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
# Type parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (ruby (ふりがな) for live text: `type.ruby` sets or removes a reading over a byte range, above or below (right of the column in vertical type), start/centre/end aligned, sized and overhanging or squeezed; saved natively, laid out with its line growing, drawn on the canvas and in SVG/PDF (outlines where ruby is present); scores unchanged; previously:; Fractional Widths: Character panel menu turns whole-point advances on or off per type object, on by default; scores unchanged; type on a path threads, mixed with area type in one thread; scores unchanged; No Break keeps marked characters on one line; scores unchanged; a click on the out port threads the text into a closed path, a new frame at the click or a dragged rectangle, #2572 in part; scores unchanged; native family labels fall back to localized legacy names when typographic names are English-only, #2208; scores unchanged; Cmd+Shift+C aligns centre off the Mac, #1940; tab leaders are drawn in the tab's gap, #1877; type in symbols, pattern tiles, graph designs and opacity masks gets its layout bounds back on opening a native file, #1738; score unchanged; Escape while typing ends the edit, #1686; Select › Same on type, #1737; `justify` when creating point type, #1748; scores unchanged; OpenType stylistic sets, alternates, figure styles, superscript and subscript as character features, #1607; a wide word in non-rectangular area type moves down, #1615; scores unchanged; Character Styles New/Redefine capture the first selected character at run boundaries and end prior typing before capture; boundary and typing-history input tests and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Linux font folders from fontconfig, #1529; commands and MCP `add_text` report area type that does not fit its frame, #1471; Tabs panel stop drags, #1458; Fit Headline in a frame one line tall, #1444; frames and paths beyond a billion points hold no text, #1451; scores unchanged; missing glyphs are looked for again after a font rescan, #1376; scores unchanged; Full-name font matching (#1258); Windows font-service QA pending; scores unchanged; Middle Eastern digit types: Character panel menu › Digits, `{digits}`; the font menu's script filter lists Thai fonts; Thai, Lao, Khmer and Myanmar lines break between words; the caret steps over whole clusters such as Thai consonants with their marks; Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966; Cmd+Shift+C aligns centre off the Mac, a double-click on area type's bottom handle turns on Auto Size, #1940; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged. tab leaders are drawn in the tab's gap, #1877; type in symbols, pattern tiles, graph designs and opacity masks gets its layout bounds back on opening a native file, #1738; score unchanged; Escape while typing ends the edit, #1686; Select › Same on type, #1737; `justify` when creating point type, #1748; scores unchanged; OpenType stylistic sets, alternates, figure styles, superscript and subscript as character features, #1607; a wide word in non-rectangular area type moves down, #1615; scores unchanged; Character Styles New/Redefine capture the first selected character at run boundaries and end prior typing before capture; boundary and typing-history input tests and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Linux font folders from fontconfig, #1529; commands and MCP `add_text` report area type that does not fit its frame, #1471; Tabs panel stop drags, #1458; Fit Headline in a frame one line tall, #1444; frames and paths beyond a billion points hold no text, #1451; scores unchanged; missing glyphs are looked for again after a font rescan, #1376; scores unchanged; Full-name font matching (#1258); Windows font-service QA pending; scores unchanged; Middle Eastern digit types: Character panel menu › Digits, `{digits}`; the font menu's script filter lists Thai fonts; Thai, Lao, Khmer and Myanmar lines break between words; the caret steps over whole clusters such as Thai consonants with their marks; Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (System Layout: Character panel menu sets whole-point advances for every text object in the document (off by default); ruby (ふりがな) for live text: `type.ruby` sets or removes a reading over a byte range, above or below (right of the column in vertical type), start/centre/end aligned, sized and overhanging or squeezed; saved natively, laid out with its line growing, drawn on the canvas and in SVG/PDF (outlines where ruby is present); scores unchanged; previously:; Fractional Widths: Character panel menu turns whole-point advances on or off per type object, on by default; scores unchanged; type on a path threads, mixed with area type in one thread; scores unchanged; No Break keeps marked characters on one line; scores unchanged; a click on the out port threads the text into a closed path, a new frame at the click or a dragged rectangle, #2572 in part; scores unchanged; native family labels fall back to localized legacy names when typographic names are English-only, #2208; scores unchanged; Cmd+Shift+C aligns centre off the Mac, #1940; tab leaders are drawn in the tab's gap, #1877; type in symbols, pattern tiles, graph designs and opacity masks gets its layout bounds back on opening a native file, #1738; score unchanged; Escape while typing ends the edit, #1686; Select › Same on type, #1737; `justify` when creating point type, #1748; scores unchanged; OpenType stylistic sets, alternates, figure styles, superscript and subscript as character features, #1607; a wide word in non-rectangular area type moves down, #1615; scores unchanged; Character Styles New/Redefine capture the first selected character at run boundaries and end prior typing before capture; boundary and typing-history input tests and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Linux font folders from fontconfig, #1529; commands and MCP `add_text` report area type that does not fit its frame, #1471; Tabs panel stop drags, #1458; Fit Headline in a frame one line tall, #1444; frames and paths beyond a billion points hold no text, #1451; scores unchanged; missing glyphs are looked for again after a font rescan, #1376; scores unchanged; Full-name font matching (#1258); Windows font-service QA pending; scores unchanged; Middle Eastern digit types: Character panel menu › Digits, `{digits}`; the font menu's script filter lists Thai fonts; Thai, Lao, Khmer and Myanmar lines break between words; the caret steps over whole clusters such as Thai consonants with their marks; Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966; Cmd+Shift+C aligns centre off the Mac, a double-click on area type's bottom handle turns on Auto Size, #1940; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged. tab leaders are drawn in the tab's gap, #1877; type in symbols, pattern tiles, graph designs and opacity masks gets its layout bounds back on opening a native file, #1738; score unchanged; Escape while typing ends the edit, #1686; Select › Same on type, #1737; `justify` when creating point type, #1748; scores unchanged; OpenType stylistic sets, alternates, figure styles, superscript and subscript as character features, #1607; a wide word in non-rectangular area type moves down, #1615; scores unchanged; Character Styles New/Redefine capture the first selected character at run boundaries and end prior typing before capture; boundary and typing-history input tests and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Linux font folders from fontconfig, #1529; commands and MCP `add_text` report area type that does not fit its frame, #1471; Tabs panel stop drags, #1458; Fit Headline in a frame one line tall, #1444; frames and paths beyond a billion points hold no text, #1451; scores unchanged; missing glyphs are looked for again after a font rescan, #1376; scores unchanged; Full-name font matching (#1258); Windows font-service QA pending; scores unchanged; Middle Eastern digit types: Character panel menu › Digits, `{digits}`; the font menu's script filter lists Thai fonts; Thai, Lao, Khmer and Myanmar lines break between words; the caret steps over whole clusters such as Thai consonants with their marks; Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966) · **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
|
||||
@@ -35,8 +35,9 @@ against Illustrator's public documentation; **estimated** unless noted.
|
||||
| Fallback glyphs keep the text's weight and slant | done | #2491: a character from a fallback face is set in that family's face nearest the run's weight and slant; Bold Italic of a missing family is drawn Bold (Source Sans 3 has no Bold Italic) rather than Italic |
|
||||
| Colour fonts (OpenType SVG / COLR) | partial | |
|
||||
| Every-line and Single-line composers | done | tuned by eye, never compared side by side |
|
||||
| Fractional Widths | done | Character panel menu › Fractional Widths, `type.fractionalWidths`: per type object, and every frame of its threads. On by default and for documents from before it; saved only when off. Off, each character that takes space advances by a whole number of points, at least one, so small type does not run together. System Layout (the document-wide whole-pixel setting) remains a panel stub. Evidence: `tests::fractional_widths_off_rounds_positive_advances_to_whole_points`, `typecmd::area_tests::fractional_widths_is_set_per_object_and_saved_only_when_off`, `panels::character::tests::fractional_widths_menu_item_toggles_the_type_object` |
|
||||
| Optical Margin Alignment | done | Type › Optical Margin Alignment, `type.opticalMarginAlignment`: per type object, with one setting for all the frames of a thread, in point and area type of left-to-right and right-to-left paragraphs, and at the brackets of type on a path |
|
||||
| Optical Margin Alignment | done | Type › Optical Margin Alignment, `type.opticalMarginAlignment`: per type object, with one setting for all the frames of a thread, in point and area type of left-to-right paragraphs, and at the brackets of type on a path whose first paragraph runs left to right; right-to-left paragraphs of point and area type, and type on a path whose first paragraph runs right to left, do not hang |
|
||||
| System Layout | done | Character panel menu › System Layout, `type.systemLayout`: the whole document, off by default and for documents from before it, saved only when on. On, every text object advances by whole points (at least one when a character takes space). New point, area and path type, threaded frames, placed text, graph and dimension labels and pasted plain text inherit it. Evidence: `tests::system_layout_rounds_positive_advances_to_whole_points`, `typecmd::area_tests::system_layout_sets_the_document_and_every_text_object`, `panels::character::tests::system_layout_menu_item_toggles_the_document` |
|
||||
| Fractional Widths | done | Character panel menu › Fractional Widths, `type.fractionalWidths`: per type object, and every frame of its threads. On by default and for documents from before it; saved only when off. Off, each character that takes space advances by a whole number of points, at least one, so small type does not run together. Evidence: `tests::fractional_widths_off_rounds_positive_advances_to_whole_points`, `typecmd::area_tests::fractional_widths_is_set_per_object_and_saved_only_when_off`, `panels::character::tests::fractional_widths_menu_item_toggles_the_type_object` |
|
||||
| Roman Hanging Punctuation | done | Paragraph panel menu › Roman Hanging Punctuation, `text.setFormat {romanHangingPunctuation}` (#2756): per paragraph, Optical Margin Alignment's punctuation and amounts without the letters; paragraph styles carry it. Evidence: `tests::roman_hanging_punctuation_hangs_punctuation_but_not_letters`, `typecmd::area_tests::roman_hanging_punctuation_is_set_per_paragraph_and_saved_only_when_on` |
|
||||
| Hyphenation… and Justification… options | done | Hyphenation… (`ui.hyphenation`, `text.setFormat`) sets each paragraph's Words Longer Than, After First, Before Last, Hyphen Limit, Hyphenation Zone (Single-line Composer only) and Hyphenate Capitalized Words, and paragraph styles carry them (#2310). Justification… (`ui.justification`, `text.setFormat`, #2755) sets Word Spacing, Letter Spacing and Glyph Scaling (Minimum, Desired, Maximum), Auto Leading and Single Word Justification; Desired applies to every line, justified lines and the Every-line Composer use the ranges. Vertical type isn't glyph scaled; type on a path doesn't use them. Evidence: `crates/text/src/tests_justify.rs`, `dialogs::justification::tests` |
|
||||
| Tab stops and leaders; Tabs panel | partial | Tabs panel and stops with leaders in the model (`text.tabs.set`); a stop drags from where the press landed (#1458); leaders fill the tab's gap, repeated on a grid so they line up from line to line (#1877) |
|
||||
@@ -65,6 +66,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-21 | minor | System Layout: Character panel menu sets whole-point advances for every text object in the document (off by default); scores and hours unchanged |
|
||||
| 2026-10-22 | minor | Ruby (ふりがな) for live text: `type.ruby` sets/removes a reading over a byte range, above or below (right/left of the column in vertical type), aligned, sized and overhanging or squeezed; saved natively, laid out with line growth, drawn on the canvas and in SVG/PDF (outlines where ruby is present); scores unchanged |
|
||||
| 2026-10-21 | minor | Fractional Widths: Character panel menu turns whole-point advances on or off per type object (on by default); System Layout remains a stub; scores and hours unchanged |
|
||||
| 2026-10-21 | minor | Type on a path threads, mixed with area type in one thread: its text that doesn't fit flows on into the next frame, and its red out port, just past its end bracket, loads the Selection tool with that text as area type's does; a new frame threaded from it at a point is type on a path of its shape; scores unchanged |
|
||||
|
||||
Reference in new issue
Block a user