Glyphs: accept positional glyph choices and tighten reviewed style values

This commit is contained in:
Taha-Mahmoodi
2026-11-09 17:11:14 +04:30
parent 2726446198
commit 699073d66e
7 changed files with 70 additions and 14 deletions
+9 -1
View File
@@ -1299,7 +1299,15 @@ fn set_glyph_alternate(s: &mut Session, p: &Value) -> Result<Value> {
let substituted = unchosen.is_some_and(|g| g != default);
let choice = match gid.filter(|g| *g != default || substituted) {
None => None,
Some(g) if g == default || face.alternates(ch).iter().any(|a| a.gid == g) || face.is_unlisted_alternate(ch, g) => Some(face.choice(ch, g)),
Some(g)
if g == default
|| face.alternates(ch).iter().any(|a| a.gid == g)
|| face.is_unlisted_alternate(ch, g)
|| vectorcraft_text::alternates_in_context(vectorcraft_text::FontDb::global(), &t, bytes.start)
.is_some_and(|f| f.font_id == face.id() && f.alternates.iter().any(|a| a.gid == g)) =>
{
Some(face.choice(ch, g))
}
Some(g) => return Err(bad(C, format!("glyph {g} is not an alternate of {ch} in {} {}", face.family, face.style))),
};
let drawn = choice.as_ref().map_or(unchosen.unwrap_or(default), |c| c.gid);
+8 -6
View File
@@ -810,8 +810,9 @@ pub const JOINING_LETTERS: [char; 4] = ['\u{0628}', '\u{062A}', '\u{0646}', '\u{
/// alternate substitutions of the medial teh u (w, x, y: three alternates), of the final teh U
/// (W, X), of the isolated teh t (E) and of the medial beh v (z), and `ss01` single substitutions
/// u to w, U to W, t to E and v to z. A set differs by position, as in a font that draws each
/// letter's alternates for the position they join in. Family [`JOINING_ALTERNATES_FAMILY`]. `None`
/// when it can't be built.
/// letter's alternates for the position they join in. `cv01` gives the medial noon m the glyph d,
/// an alternate that has no style tag. Family [`JOINING_ALTERNATES_FAMILY`]. `None` when it can't
/// be built.
pub fn joining_alternates_font() -> Option<Vec<u8>> {
use write_fonts::tables::cmap::Cmap;
use write_fonts::tables::gsub::{
@@ -857,20 +858,21 @@ pub fn joining_alternates_font() -> Option<Vec<u8>> {
vec![AlternateSubstFormat1::new(CoverageFormat1::new(from).into(), sets)],
)))
};
// Lookups: 0 init, 1 medi, 2 fina, 3 salt, 4 ss01.
// Lookups: 0 init, 1 medi, 2 fina, 3 salt, 4 ss01, 5 cv01.
let lookups = SubstitutionLookupList::new(vec![
single(&[('b', 'B'), ('t', 'T'), ('n', 'N')])?,
single(&[('b', 'v'), ('t', 'u'), ('n', 'm')])?,
single(&[('b', 'V'), ('t', 'U'), ('n', 'M'), ('I', 'A')])?,
alternates(&[('u', &['w', 'x', 'y']), ('U', &['W', 'X']), ('t', &['E']), ('v', &['z'])])?,
single(&[('u', 'w'), ('U', 'W'), ('t', 'E'), ('v', 'z')])?,
single(&[('m', 'd')])?,
]);
// Features sorted by tag, as OpenType requires: fina, init, medi, salt, ss01.
let records: Vec<FeatureRecord> = [(b"fina", 2), (b"init", 0), (b"medi", 1), (b"salt", 3), (b"ss01", 4)]
// Features sorted by tag, as OpenType requires: cv01, fina, init, medi, salt, ss01.
let records: Vec<FeatureRecord> = [(b"cv01", 5), (b"fina", 2), (b"init", 0), (b"medi", 1), (b"salt", 3), (b"ss01", 4)]
.into_iter()
.map(|(tag, lookup)| FeatureRecord::new(Tag::new(tag), Feature::new(None, vec![lookup])))
.collect();
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"arab"), Script::new(Some(LangSys::new(vec![0, 1, 2, 3, 4])), vec![]))]);
let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"arab"), Script::new(Some(LangSys::new(vec![0, 1, 2, 3, 4, 5])), vec![]))]);
builder.add_table(&Gsub::new(scripts, FeatureList::new(records), lookups)).ok()?;
for r in font.table_directory.table_records() {
let tag = Tag::new(&r.tag().to_be_bytes());
@@ -505,7 +505,9 @@ fn opentype_attrs(d: &Dialog, original: &Map<String, Value>, original_fields: &M
tags.retain(|t| !sets.contains(&bare(t)));
let listed = d.fields.get("ot_sets").and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_str);
tags.extend(listed.filter(|t| sets.contains(&bare(t))).map(|t| {
let kept = before.iter().rev().find(|b| bare(b) == bare(t)).filter(|b| !t.starts_with('-') && tag_on(b));
let kept = before.iter().rev().find(|b| bare(b) == bare(t)).filter(|b| {
!t.starts_with('-') && vectorcraft_text::OtFeatures::known_tag(b) && tag_on(b)
});
kept.map_or_else(|| t.to_string(), Clone::clone)
}));
}
@@ -1650,6 +1652,11 @@ mod tests {
d.fields.insert("ot_sets".into(), json!(["ss01", "ss02"]));
d.fields.insert("ot_salt".into(), json!(true));
assert_eq!(edited_attrs(&d).unwrap()["features"], json!(["salt", "ss01", "ss02=2"]));
for last in ["ss01=0", "-ss01", "ss01=65536"] {
let mut d = dialog_for(json!({"features": ["ss01=2", last]}));
d.fields.insert("ot_sets".into(), json!(["ss01", "ss02"]));
assert_eq!(edited_attrs(&d).unwrap()["features"], json!(["ss01", "ss02"]), "{last}: only a valid last on-value is kept");
}
}
#[test]
+38 -2
View File
@@ -6,8 +6,9 @@
//! stands in its word), and a double-click applies the OpenType feature that draws one to that
//! character alone, so the text stays real text that joins its neighbors. A double-click on a
//! glyph no character maps in Entire Font does the same when the selected character's font gives
//! that glyph as one of its alternates in its word; any other glyph is drawn dimmed, and its tooltip
//! says why it cannot be applied to the selection.
//! that glyph as one of its alternates in its word. A glyph no feature reaches whose name does
//! not belong to another letter is applied as a glyph choice, which may not join. Other glyphs
//! are drawn dimmed, and their tooltips say why they cannot be applied to the selection.
use std::collections::HashMap;
@@ -1188,6 +1189,41 @@ mod tests {
assert!(text.runs.iter().all(|r| r.style.glyph_alternate.is_none()), "the choice is gone");
}
/// A positional alternate without a supported feature still goes through the engine's glyph
/// choice validation, even though the isolated character has different alternates.
#[test]
fn a_positional_variant_no_feature_draws_replaces_the_glyph() {
use vectorcraft_text::test_fonts::{JOINING_ALTERNATES_FAMILY, joining_alternates_font, source_sans_gid};
FontDb::global().add_font(joining_alternates_font().expect("the test font builds"));
let face = FontDb::global().face(JOINING_ALTERNATES_FAMILY, "Regular").expect("the test face");
let gid = u32::from(source_sans_gid('d').expect("a glyph"));
assert!(face.alternates('\u{646}').is_empty(), "the isolated noon has no alternates");
assert!(!face.is_unlisted_alternate('\u{646}', gid), "cv01 reaches the glyph");
for view in ["Alternates for Current Selection", "Entire Font"] {
let (mut app, id) = editing("\u{628}\u{646}\u{62A}");
app.run("text.setRangeStyle", json!({"id": id, "font": JOINING_ALTERNATES_FAMILY})).unwrap();
let plain = glyph_ids(&app, &id);
select(&mut app, 2, 4);
let ctx = egui::Context::default();
choose(&ctx, &mut app, view);
let at = if view == "Entire Font" {
entire_font_cell(&ctx, &mut app, &face, gid).0
} else {
let cells = squares(&shapes(&ctx, &mut app, vec![]));
assert_eq!(cells.len(), 2, "the medial noon and its cv01 alternate");
cells[1].center()
};
let undo = app.session.active().unwrap().history.undo.len();
double_click_in(&ctx, &mut app, at, if view == "Entire Font" { TALL } else { vec2(280.0, 420.0) });
assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1, "{view}: {}", app.ui.status);
assert_eq!(glyph_ids(&app, &id)[1], gid, "{view}");
let NodeKind::Text(text) = &app.session.active().unwrap().doc.node(NodeId(id.as_u64().unwrap())).unwrap().kind else { panic!("text") };
assert_eq!(text.plain_text(), "\u{628}\u{646}\u{62A}", "the characters stay unchanged");
app.run("edit.undo", json!({})).unwrap();
assert_eq!(glyph_ids(&app, &id), plain, "{view}: undo restores the word");
}
}
/// Show › Entire Font also lists the glyphs no character maps.
#[test]
fn entire_font_lists_the_unencoded_glyphs() {
+1
View File
@@ -839,6 +839,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv
| Date | Change | Summary |
|---|---|---|
| 2026-11-09 | minor | G4 review (#6084): positional variants no supported feature draws can be applied as glyph choices in both Glyphs views; unlisted glyphs still respect name ownership. OpenType style options keep only a valid last on-value when enabling a set. Existing contextual/ligature lookup and unsupported-feature gaps remain; estimate unchanged. |
| 2026-11-07 | minor | G4: the Glyphs panel's Alternates for Current Selection applies the OpenType feature that draws a variant to that letter alone (`salt=2`; the style commands take `tag=N`; runs that differ only in their features shape as one piece of text), so an Arabic word stays joined, and Show › Entire Font lists the unencoded glyphs (#5514). Estimate unchanged. |
| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. |
| 2026-11-06 | minor | Hindi (`hi`): 7,877-row machine-assisted catalog, zero/one singular plurals, desktop font fallback and optional Devanagari craft-fonts fallback; all 55 translation tests pass; native review and a web font remain; scores and estimates unchanged |
+3 -2
View File
@@ -1074,8 +1074,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
The Character panel's Anti-aliasing Method dropdown, under Character Rotation, sets how the edges of the selected type objects are drawn as pixels (`text.setFormat {antiAlias}`), for the whole object, with each frame of a thread keeping its own method, on the canvas, in raster exports and when the type is rasterized. VectorCraft defines the methods: Sharp, the default, draws edges as before; None paints a pixel only where the type covers at least half of it; Crisp shows a pixel covered by a share c at `2c - 0.5`, and Strong shows it at `2c`, each kept between 0 and 1. SVG, PDF and EPS export are unchanged. Scores and hours unchanged.
The OpenType panel's Stylistic Alternates and Titling Alternates checkboxes turn `salt` and `titl` on and off for the selected type, and its Figure menu lists Default Figure, Tabular Lining, Proportional Lining, Proportional Oldstyle and Tabular Oldstyle; Proportional Lining sets `pnum` and `lnum` and turns `tnum` and `onum` off, Tabular Lining turns `pnum` off and keeps `lnum`, Proportional Oldstyle turns `lnum` off and keeps `pnum`, and Default Figure and Tabular Oldstyle turn `pnum` and `lnum` off. Behavior reference: [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html) and [Use swashes, titling alternates, or stylistic alternates](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/use-swashes-titling-alternates-or-stylistic-alternates.html) (public documentation). Tests: `panels::opentype::tests`.
The OpenType panel's Position menu lists Default Position, Superscript, Subscript, Numerator and Denominator, as [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html) describes; Superscript, Subscript, Numerator and Denominator set `sups`, `subs`, `numr` and `dnom` for the selected type and turn the other three off, and Default Position turns all four off. The menu shows Mixed when the characters it acts on differ, and an entry chosen then applies to all of them. These two rules are VectorCraft's own, as is setting the feature on type whose font lacks the glyphs, which then draws unchanged. The panel's menus and checkboxes change only their own features on each selected character, so characters keep their other OpenType features (`text.setStyle` and `text.setRangeStyle` `{featureChanges}`). Tests: `panels::opentype::tests`, `features::tests::numerators_and_denominators_are_kept_and_shaped`.
The Glyphs panel's Show menu and panel menu have Alternates for Current Selection, as [Replace characters with alternate glyphs](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/replace-characters-with-alternate-glyphs.html) describes: the grid lists the alternate glyphs of the character selected with the Type tool, and a double-click draws that character with the glyph as one undo step (`text.glyphAlternates`, `text.setGlyphAlternate`). The alternates come from the font's `GSUB` single and alternate substitutions of `aalt`, then `salt`, `ss01` to `ss20`, `swsh`, `cv01` to `cv99` and a few other alternate features, each glyph once. The text keeps the character, native files keep the choice with the face it was made in, and type set in another font or version draws the default glyph. The list uses the font of the character's text even when the panel browses another font, and the panel shows a note when the character has no alternates, its font is missing or no character is selected; these rules are VectorCraft's own. Tests: `panels::glyphs::tests`, `tests::alternates`, `tests::textedit::a_glyph_alternate_replaces_one_character_through_undo_and_reopen`.
The Glyphs panel's Alternates for Current Selection (#5514) applies the OpenType feature that draws the glyph to the selected character alone, as [Replace characters with alternate glyphs](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/replace-characters-with-alternate-glyphs.html) describes (Alternates For Current Selection in the Show menu; double-click the glyph to replace the selected character). One deliberate difference from a literal reading: the text keeps the Unicode character and gains a character-style feature with a value (`salt=2`, `ss03`; `text.setRangeStyle {featureChanges}`) instead of taking an unencoded glyph in its place. The grid lists the alternates of the glyph the shaper makes for the character in its word (a medial letter shows its medial variants; the glyph it has comes first and is highlighted); a variant no feature draws there stays enabled as a replacement glyph (`text.setGlyphAlternate`), and its tooltip says it may not join its neighbors; the default glyph takes the character's alternate features off again. Runs that differ only in their OpenType features are shaped as one piece of text, each run's features over its own characters, so the letters of an Arabic word keep their joining forms. Show › Entire Font also lists the glyphs no character maps, with the font's name for them (or `gid N`) and the feature that gives them. These rules are VectorCraft's own. Tests: `tests::glyph_alternates` in the text crate (a fixture font; Kufi Murabba PIIIX when it is installed), `panels::glyphs::tests`, `tests::textedit::feature_values_are_set_replaced_and_saved`.
The Glyphs panel's Show menu and panel menu have Alternates for Current Selection, as [Replace characters with alternate glyphs](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/replace-characters-with-alternate-glyphs.html) describes. The grid lists the selected character's alternate glyphs from `GSUB` single and alternate substitutions of `aalt`, then `salt`, `ss01` to `ss20`, `swsh`, `cv01` to `cv99` and a few other alternate features, each glyph once. A double-click applies the feature that draws the variant, or a glyph choice if no supported feature draws it, as detailed below. The list uses the font of the character's text even when the panel browses another font, and the panel shows a note when the character has no alternates, its font is missing or no character is selected; these rules are VectorCraft's own. Native files keep a glyph choice with the face it was made in; another font or version draws the default glyph. Tests: `panels::glyphs::tests`, `tests::alternates`, `tests::textedit::a_glyph_alternate_replaces_one_character_through_undo_and_reopen`.
The Glyphs panel's Alternates for Current Selection (#5514) applies the OpenType feature that draws the glyph to the selected character alone, as [Replace characters with alternate glyphs](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/replace-characters-with-alternate-glyphs.html) describes (Alternates For Current Selection in the Show menu; double-click the glyph to replace the selected character). One deliberate difference from a literal reading: the text keeps the Unicode character and gains a character-style feature with a value (`salt=2`, `ss03`; `text.setRangeStyle {featureChanges}`) instead of taking an unencoded glyph in its place. The grid lists the alternates of the glyph the shaper makes for the character in its word (a medial letter shows its medial variants; the default glyph comes first and the current glyph is highlighted); a variant no supported feature draws there stays enabled as a replacement glyph (`text.setGlyphAlternate`, including positional alternates), and its tooltip says it may not join its neighbors; the default glyph takes the character's alternate features off again. Runs that differ only in their OpenType features are shaped as one piece of text, each run's features over its own characters, so the letters of an Arabic word keep their joining forms. Show › Entire Font also lists the glyphs no character maps, with the font's name for them (or `gid N`) and the feature that gives them. It applies the selected letter's contextual alternates the same way, and applies a glyph no feature reaches whose name does not belong to another letter as a glyph choice; other glyphs are dimmed with the reason. An Arabic presentation form picked there inserts its base letter. These rules are VectorCraft's own. Tests: `tests::glyph_alternates` in the text crate (a fixture font; Kufi Murabba PIIIX when it is installed), `panels::glyphs::tests` (including positional glyph choices, unlisted glyph name ownership, apply and Undo), `tests::textedit::feature_values_are_set_replaced_and_saved`.
The OpenType panel's Stylistic Sets menu offers `ss01`–`ss20` sets present in the selected text's font, using the font's custom labels when available and numbered labels otherwise. Sets can be combined and deselected individually. With mixed fonts, the menu shows the first selected character's font's sets and applies the feature tag to all selected text; a font without that set draws unchanged. The mixed-font rule is a VectorCraft design choice. Behavior reference: [Add stylistic sets to selected text](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html) and the [OpenType specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt).
`text.setStyle {stroke, strokeWidth}` sets the character stroke's color and weight of the selected type or of a range of its characters (the weight in document points, as `stroke.set` sets it), in the same undo step as the command's other changes. Tests: `tests::textedit::set_style_sets_the_character_stroke`.
- **Paragraph alignment:** the shared Paragraph/Properties alignment row leaves every button unselected when the paragraphs it acts on differ. Auto resolves per paragraph; choosing an alignment uses the existing command. Headless paragraph_mixed_alignment_* regressions exercise mixed → Center → Undo, caret/uniform states, Unicode range boundaries, Auto/direction, split runs and grouped text. These regressions and full integration CI pass. Original captures confirm mixed → Center → Undo in the existing alignment-button layout. The [public Paragraph panel guide](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/paragraph-panel-overview.html) lists the seven controls, but does not specify mixed-state rendering. Other paragraph fields remain outside this change; scores and hours unchanged.
@@ -1892,6 +1892,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s
| Date | Change | Summary |
|---|---|---|
| 2026-11-09 | minor | Glyphs review (#6084): positional alternates with no supported feature are accepted as glyph choices in both panel views; Entire Font's unlisted-glyph and name-ownership behavior is retained. OpenType style options keep only a valid last on-value when enabling a set. Contradictory old glyph-choice evidence corrected; scores and estimates unchanged. |
| 2026-11-07 | minor | Type core: the Glyphs panel's Alternates for Current Selection applies the OpenType feature that draws a variant to the selected letter alone, so an Arabic word stays joined (`salt=2`; the style commands take `tag=N`), and Show › Entire Font lists the glyphs no character maps (#5514; [Replace characters with alternate glyphs](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/replace-characters-with-alternate-glyphs.html)); evidence added; scores unchanged. |
| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. |
| 2026-11-07 | minor | File formats and Type core: `.ai`/EPS type keeps its paragraphs' tab stops, and right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); evidence added; scores unchanged. |
+3 -2
View File
@@ -1,6 +1,6 @@
# Type parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (the Glyphs panel's Alternates for Current Selection applies the OpenType feature that draws a variant to that letter alone, so an Arabic word stays joined, and Entire Font lists the unencoded glyphs, #5514; right-aligned area type sets the text after a line's last tab against the right edge; a soft return in justified type no longer keeps its line from being justified; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (Glyphs review: a positional variant no supported feature draws is accepted as a glyph choice; invalid or off stylistic-set values are not preserved when a set is enabled; the Glyphs panel's Alternates for Current Selection applies the OpenType feature that draws a variant to that letter alone, so an Arabic word stays joined, and Entire Font lists the unencoded glyphs, #5514; right-aligned area type sets the text after a line's last tab against the right edge; a soft return in justified type no longer keeps its line from being justified; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen) · **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
@@ -30,7 +30,7 @@ against Illustrator's public documentation; **estimated** unless noted.
| Copy or Cut selected characters, then paste as separate text art | done | A Type-range copy keeps the selected characters' font family, size and color when pasted on the canvas after leaving text editing, in the same document or another. On desktop the clipboard offers plain text to text consumers and rich art for canvas Paste; Paste Without Formatting while editing uses the destination text's style. `tests::clipboard::type_range_copy_stays_styled_when_pasted_as_art` (#5636) |
| Character size and horizontal scale after object/group scaling | done | #1034: fields and setters compensate the stored affine; point size follows the transformed em-height axis, not the glyph ink bounds. Range edits, keyboard increments, leading and baseline shift use document points; rotation, reflection and shear remain editable. |
| Preserve font sizes while scaling a group | missing | Separate enhancement; scaling still scales text with the other artwork. |
| OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The OpenType panel's Stylistic Sets menu lists the selected font's `ss01`–`ss20` features, using a font-supplied name where available and `Set N` otherwise; several sets can be active, and selecting an active one removes it. For a mixed-font selection it uses the first selected character's font, a VectorCraft design choice ([public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html), [OpenType feature specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt)). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges and Alternates for Current Selection, the selected character's alternate glyphs from the font's `GSUB` table, a double-click applies the OpenType feature that draws one (`salt=N`, `ssNN`) to that character alone; a variant no feature reaches is drawn through `text.setGlyphAlternate`, which may not join; Show › Entire Font applies the selected letter's alternates the same way and dims the others with the reason; the glyph-cell pop-up of alternates is not there yet |
| OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The OpenType panel's Stylistic Sets menu lists the selected font's `ss01`–`ss20` features, using a font-supplied name where available and `Set N` otherwise; several sets can be active, and selecting an active one removes it. For a mixed-font selection it uses the first selected character's font, a VectorCraft design choice ([public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html), [OpenType feature specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt)). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges and Alternates for Current Selection, the selected character's alternate glyphs from the font's `GSUB` table. A double-click applies the OpenType feature that draws a variant to that character alone, or uses a glyph choice when no supported feature draws it; see the Glyphs panel row below for Entire Font and the joining limits. The glyph-cell pop-up of alternates is not there yet |
| Glyphs panel: alternates as OpenType feature values, unencoded glyphs | done | #5514, as [Replace characters with alternate glyphs](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/replace-characters-with-alternate-glyphs.html) describes (Show › Alternates For Current Selection; replace the selected character by double-clicking the glyph). A character-style feature takes a value (`"salt=2"`, the font's alternate number; `ss01`–`ss20`, `swsh`, `titl` and `aalt` too) in `features` and `featureChanges`, in SVG `font-feature-settings` (`"salt" 2`) and in native files as the plain string. Show › Entire Font applies an unencoded glyph the same way when it is an alternate of the selected character in its word, applies one no feature reaches (`A.salt` in a font with no `salt`) to the selected character as a glyph choice (it may not join), and dims the others with the reason in the tooltip (e.g. `yehfarsi.fina.alt2 belongs to ی (U+06CC) at the end of a word; your selection is ي (U+064A)`); an Arabic presentation form picked there inserts its letter so the word joins. Runs that differ only in their features shape as one piece of text, each run's features over its own characters, so a variant on one letter of an Arabic word keeps its neighbours' initial, medial and final forms and the letters still touch. The panel lists the alternates of the glyph the shaper makes for the selected character in its word (a medial letter shows medial variants), and a double-click applies the OpenType feature that draws the variant (`salt=N` or `ssNN`) to that character alone, in one undo step (`text.setRangeStyle`); a variant no feature reaches falls back to a replacement glyph (`text.setGlyphAlternate`), which its tooltip says may not join. Show › Entire Font also lists the glyphs no character maps, with their name or `gid N` and the feature that gives them. The text crate reads each face's `GSUB` once (alternate sets, single substitutions, the feature that reaches a glyph). Not done: alternates reached through contextual or ligature lookups, and features of the font that the style has no tag for (`cv01`–`cv99`, `hist`). Evidence: `tests::glyph_alternates` in the text crate (a fixture font with a medial teh at `salt=1` to `3`, the neighbours' forms, no gap; Kufi Murabba PIIIX when it is installed), `features::tests::feature_values_are_parsed_validated_and_shaped`, `tests::textedit::feature_values_are_set_replaced_and_saved`, the `panels::glyphs` tests. |
| Font menu: samples, live preview, filters, favourites, recent families; font matching by any name; Find Font | done | #3515: recently applied families appear first, once each; missing families are omitted and the existing Number of Recent Fonts setting applies; #339; script filter: Japanese, Latin, Thai; the font scan on Linux also reads the folders fontconfig's configuration lists, such as TeX fonts under `/usr/share/texmf/fonts` (#1548, fixes #1529; not verified on Linux); Select Text by Font takes a scope: the document, chosen artboards or chosen layers; Find Font's Find In offers Document, Artboard (the active one) and Layers (the selected ones, else the current one) (#5529, a VectorCraft addition); Replace With Font lists family and style rows, one click setting both, with a search over them; its list scrolls on its own, apart from the document's font list (#4972). |
| Missing glyphs set in an installed font made for their script (Thai, Lao, Khmer, Myanmar first; then any font that has them) | done | Thai: Leelawadee UI, Tahoma, Thonburi, Sukhumvit Set, Noto Sans Thai, TLWG |
@@ -82,6 +82,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha
| Date | Change | Summary |
|---|---|---|
| 2026-11-09 | minor | Glyphs review (#6084): the engine accepts a positional alternate as a glyph choice when no supported feature draws it, not only an isolated character's alternates; both panel views exercise apply and Undo. OpenType style options preserve only a valid last on-value when enabling a stylistic set. Scores and estimates unchanged. |
| 2026-11-07 | minor | Glyphs panel (#5514): Alternates for Current Selection lists the alternates of the selected character's glyph in its word and a double-click applies the OpenType feature that draws one to that letter alone (`salt=2`; `features` and `featureChanges` take `tag=N`), so an Arabic word stays joined, and Entire Font lists the unencoded glyphs, applies the ones that are the selected letter's alternates, or that no feature reaches and the font's names do not give to another letter, and dims, with the reason, those that are not. Runs that differ only in their features shape as one piece of text. Scores and estimates unchanged. |
| 2026-11-07 | minor | Tabs: in right-aligned area type, the text before a line's last tab sits at its stops, measured from the left edge, and the text after it (none, when the tab ends the line) against the right edge; the line was aligned as a whole, which moved its start off the left edge. Illustrator's own layout in a user's `.ai` (used locally, not committed) sets its price lists so. Centred and justified lines keep their alignment as a whole. Scores and estimates unchanged. |
| 2026-11-07 | minor | Soft return: in Justify Left, Center or Right, the line a soft return ends is justified as a wrapped line is; only the paragraph's last line keeps its alignment. Illustrator's own layout in a user's `.ai` (used locally, not committed) justifies those lines. Scores and estimates unchanged. |