Type: Character panel › Show Snap to Glyph Options (#4232)

This commit is contained in:
bflatastic
2026-10-30 02:41:25 +07:00
parent fffc2b747f
commit 5d4b4b9b35
42 changed files with 514 additions and 67 deletions
+9 -4
View File
@@ -388,10 +388,11 @@ pub fn spec(key: &str) -> Option<&'static PrefSpec> {
/// Preferences kept as one object with a command of their own instead of [`PREF_SPECS`] rows (they
/// aren't in the Preferences dialog): the Eyedropper Options (`eyedropper.setOptions`), the
/// Perspective Grid Options (`perspective.widget.options`) and the Pathfinder Options
/// (`object.pathfinder.options`). `prefs.get` and `prefs.set` take them by key (a partial object
/// updates what it names) and `prefs.reset` without a category resets them.
pub const PREF_GROUPS: &[&str] = &["eyedropper", "perspectiveWidget", "pathfinder"];
/// Perspective Grid Options (`perspective.widget.options`), the Pathfinder Options
/// (`object.pathfinder.options`) and the Snap to Glyph options (`view.snapToGlyph.options`).
/// `prefs.get` and `prefs.set` take them by key (a partial object updates what it names) and
/// `prefs.reset` without a category resets them.
pub const PREF_GROUPS: &[&str] = &["eyedropper", "perspectiveWidget", "pathfinder", "glyphSnap"];
/// Validate a value for preference group `key` against `current`.
fn validate_group(key: &str, current: &Value, v: &Value) -> std::result::Result<Value, String> {
@@ -404,6 +405,10 @@ fn validate_group(key: &str, current: &Value, v: &Value) -> std::result::Result<
let cur: super::PathfinderOptions = serde_json::from_value(current.clone()).unwrap_or_default();
cur.merged(v).map(|o| json!(o))
}
"glyphSnap" => {
let cur: vectorcraft_tools::GlyphSnap = serde_json::from_value(current.clone()).unwrap_or_default();
cur.merged(v).map(|o| json!(o))
}
"perspectiveWidget" => {
let mut merged = current.clone();
if let (Some(o), Some(p)) = (merged.as_object_mut(), v.as_object()) {
+16 -2
View File
@@ -1,11 +1,11 @@
//! View → New View… / Edit Views…: saved views stored in the document; per-document view
//! toggles (Show Transparency Grid).
//! toggles (Show Transparency Grid); the Character panel's Snap to Glyph options.
use serde_json::{Value, json};
use vectorcraft_doc::SavedView;
use vectorcraft_geom::Point;
use super::{CommandSpec, Result, Session, bad, bool_or, cmd, f64_req, finite_numbers, has_doc, str_param};
use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, f64_req, finite_numbers, has_doc, str_param};
pub fn specs() -> Vec<CommandSpec> {
vec![
@@ -37,9 +37,23 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
transparency_grid
),
cmd!(
query "view.snapToGlyph.options",
"Snap to Glyph Options",
[],
None,
"{baseline?: bool, xHeight?: bool, bounds?: bool, proximity?: bool, angular?: bool, anchors?: bool} the Character panel's Snap to Glyph options, kept in the preferences (the glyphSnap group, all on by default): which guides View › Snap to Glyph gives — the baseline and x-height of a single line of type, Glyph Bounds (each glyph's edges), Proximity Guides (both edges of each character's width), Angular Guides (a glyph's slanted strokes, and every guide of rotated type) and the glyphs' anchor points; {} reads them → the options",
always,
glyph_snap_options
),
]
}
fn glyph_snap_options(s: &mut Session, p: &Value) -> Result<Value> {
s.prefs.glyph_snap = s.prefs.glyph_snap.merged(p).map_err(|e| bad("view.snapToGlyph.options", e))?;
Ok(json!(s.prefs.glyph_snap))
}
fn transparency_grid(s: &mut Session, p: &Value) -> Result<Value> {
let st = s.doc_mut()?;
st.transparency_grid = p.get("on").and_then(Value::as_bool).unwrap_or(!st.transparency_grid);
+4
View File
@@ -203,6 +203,9 @@ pub struct Prefs {
/// Pathfinder panel › Pathfinder Options (`object.pathfinder.options`; a preference group, see
/// [`cmd::prefscmds::PREF_GROUPS`]).
pub pathfinder: PathfinderOptions,
/// Character panel › Show Snap to Glyph Options: which guides View › Snap to Glyph gives
/// (`view.snapToGlyph.options`; a preference group, see [`cmd::prefscmds::PREF_GROUPS`]).
pub glyph_snap: vectorcraft_tools::GlyphSnap,
/// Edit → Transparency Flattener Presets: the user's presets (the built-in ones aren't stored).
/// Not a `prefs.set` key or group (resetting the preferences keeps them): `flattener.presets.*`
/// edit it.
@@ -442,6 +445,7 @@ impl Default for Prefs {
},
eyedropper: Default::default(),
pathfinder: Default::default(),
glyph_snap: Default::default(),
flattener_presets: vec![],
trace_presets: vec![],
width_profiles: vec![],
+24
View File
@@ -1868,3 +1868,27 @@ fn transform_panel_patterns_defaults_to_following_the_general_preference() {
s.execute("prefs.reset", &json!({"category": "General"})).unwrap();
assert_eq!(s.prefs.transform_panel_patterns, "default");
}
/// Character panel › Show Snap to Glyph Options (#4232): `view.snapToGlyph.options` reads and sets
/// the options, all on by default, kept as the `glyphSnap` preference group; bad keys and values
/// change nothing, and resetting the preferences turns them all back on.
#[test]
fn snap_to_glyph_options_read_set_and_reset() {
let mut s = Session::new();
let all = json!({"baseline": true, "xHeight": true, "bounds": true, "proximity": true, "angular": true, "anchors": true});
assert_eq!(s.execute("view.snapToGlyph.options", &json!({})).unwrap(), all);
let mut set = all.clone();
(set["xHeight"], set["angular"]) = (json!(false), json!(false));
assert_eq!(s.execute("view.snapToGlyph.options", &json!({"xHeight": false, "angular": false})).unwrap(), set);
assert_eq!(s.execute("prefs.get", &json!({"key": "glyphSnap"})).unwrap(), set);
assert!(!s.prefs.glyph_snap.x_height && s.prefs.glyph_snap.baseline);
for bad in [json!({"xHeight": 1}), json!({"glyphBounds": false})] {
assert!(s.execute("view.snapToGlyph.options", &bad).is_err(), "{bad}");
assert!(s.execute("prefs.set", &json!({"key": "glyphSnap", "value": bad})).is_err(), "{bad}");
}
s.execute("prefs.set", &json!({"key": "glyphSnap", "value": {"anchors": false}})).unwrap();
set["anchors"] = json!(false);
assert_eq!(s.execute("view.snapToGlyph.options", &json!({})).unwrap(), set);
s.execute("prefs.reset", &json!({})).unwrap();
assert_eq!(s.execute("view.snapToGlyph.options", &json!({})).unwrap(), all);
}
+1
View File
@@ -271,6 +271,7 @@ impl Session {
snap_tangent: self.prefs.snap_tangent,
snap_perpendicular: self.prefs.snap_perpendicular,
snap_to_glyph: view.snap_to_glyph && view.smart_guides && !view.snap_to_grid,
glyph_snap: self.prefs.glyph_snap,
corner_widgets: view.corner_widgets,
fill_active: self.fill_active,
gradient_stop: self.selected_stop(),
+46
View File
@@ -63,6 +63,50 @@ impl ScreenFrame {
}
}
/// The Character panel's Snap to Glyph options (`view.snapToGlyph.options`): which guides View ›
/// Snap to Glyph gives. All on by default.
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct GlyphSnap {
/// The baseline of a single line of type.
pub baseline: bool,
/// The x-height: the top of its lowercase letters.
pub x_height: bool,
/// Glyph Bounds: each glyph's top, bottom, left and right edges.
pub bounds: bool,
/// Proximity Guides: across the baseline at both edges of each character's width.
pub proximity: bool,
/// Angular Guides: along a glyph's slanted straight segments, and every guide of rotated type
/// (which runs at its angle).
pub angular: bool,
/// Anchor Point: the anchor points of each glyph's outline.
pub anchors: bool,
}
impl Default for GlyphSnap {
fn default() -> Self {
Self { baseline: true, x_height: true, bounds: true, proximity: true, angular: true, anchors: true }
}
}
impl GlyphSnap {
/// These options with the ones `p` names (camelCase booleans), or why `p` can't set them.
pub fn merged(self, p: &serde_json::Value) -> Result<Self, String> {
let mut o = serde_json::to_value(self).map_err(|e| e.to_string())?;
match (p, o.as_object_mut()) {
(serde_json::Value::Null, _) => {}
(serde_json::Value::Object(given), Some(cur)) => {
for (k, v) in given {
let slot = cur.get_mut(k).ok_or_else(|| format!("unknown Snap to Glyph option `{k}`"))?;
*slot = serde_json::Value::Bool(v.as_bool().ok_or_else(|| format!("`{k}` must be true or false"))?);
}
}
_ => return Err("the Snap to Glyph options must be an object".into()),
}
serde_json::from_value(o).map_err(|e| e.to_string())
}
}
/// Read-only context a tool sees.
pub struct ToolContext<'a> {
pub doc: &'a Document,
@@ -99,6 +143,8 @@ pub struct ToolContext<'a> {
/// View → Snap to Glyph: drawn points snap to live-text glyph bounds, and on a single line of type
/// to its baseline, x-height, character widths and glyph anchor points (with Smart Guides).
pub snap_to_glyph: bool,
/// Which of Snap to Glyph's guides there are (the Character panel's Snap to Glyph options).
pub glyph_snap: GlyphSnap,
/// View → Show Corner Widget: paths show draggable Live Corners widgets in their corners.
pub corner_widgets: bool,
/// Which paint proxy is in front (true = Fill): the one the Gradient tool edits.
+1 -1
View File
@@ -18,7 +18,7 @@ use vectorcraft_geom::{Affine, BezPath, Line, ParamCurve, PathSeg, Point, Rect,
use vectorcraft_text::{FontDb, layout as layout_text};
use crate::bbox::Handle;
use crate::{Overlay, ScreenFrame, ToolContext};
use crate::{GlyphSnap, Overlay, ScreenFrame, ToolContext};
mod snap;
mod targets;
+5 -4
View File
@@ -1,7 +1,7 @@
use super::targets::{Targets, span};
use super::{
Affine, GuideStyle, Handle, HitOptions, Line, NodeId, OrientedBox, Overlay, ParamCurve, ParamCurveDeriv, ParamCurveNearest, PathSeg, Point, Rect,
ScreenFrame, ToolContext, Vec2, constrain_angle_from, hit_test,
Affine, GlyphSnap, GuideStyle, Handle, HitOptions, Line, NodeId, OrientedBox, Overlay, ParamCurve, ParamCurveDeriv, ParamCurveNearest, PathSeg,
Point, Rect, ScreenFrame, ToolContext, Vec2, constrain_angle_from, hit_test,
};
/// Where curves `a` and `b` cross near `p`: a point projected from one onto the other and back
@@ -295,7 +295,8 @@ struct SnapKey {
exclude: Vec<NodeId>,
screen: Option<ScreenFrame>,
view: [bool; 7],
snap_to_glyph: bool,
/// The Snap to Glyph options while it gives guides.
snap_to_glyph: Option<GlyphSnap>,
style: GuideStyle,
tolerances: [f64; 2],
}
@@ -307,7 +308,7 @@ impl SnapKey {
exclude: exclude.to_vec(),
screen: cx.screen,
view: [cx.smart_guides, cx.snap_to_point, cx.snap_to_pixel, cx.snap_to_grid, cx.guides, cx.snap_tangent, cx.snap_perpendicular],
snap_to_glyph: cx.snap_to_glyph && cx.smart_guides && cx.alignment_guides,
snap_to_glyph: (cx.snap_to_glyph && cx.smart_guides && cx.alignment_guides).then_some(cx.glyph_snap),
style: GuideStyle::of(cx),
tolerances: [cx.snapping_tolerance, cx.snap_tolerance],
}
+31 -23
View File
@@ -1,6 +1,6 @@
use super::snap::{Leave, meeting, seg_direction, touch_params};
use super::{
Affine, Arc, BezPath, Document, FontDb, GuideStyle, Handle, HashSet, Line, Node, NodeId, NodeKind, OrientedBox, Overlay, ParamCurve,
Affine, Arc, BezPath, Document, FontDb, GlyphSnap, GuideStyle, Handle, HashSet, Line, Node, NodeId, NodeKind, OrientedBox, Overlay, ParamCurve,
ParamCurveNearest, PathSeg, Point, Rect, Ruler, Selection, Shape, SubPath, ToolContext, Vec2, layout_text,
};
@@ -163,12 +163,13 @@ impl Targets {
/// Targets for a drawn point: the art in `visible` (all of it for None) but `exclude`, its
/// anchors, centres, edges and path segments, its anchors in line too. The anchors and
/// segments of `exclude` (the path being drawn, whose bounds grow with it) stay targets.
pub fn for_drawing(doc: &Document, exclude: &[NodeId], visible: Option<Rect>, glyphs: bool) -> Self {
/// segments of `exclude` (the path being drawn, whose bounds grow with it) stay targets. With
/// `glyphs` (Snap to Glyph on), live text gives the guides those options ask for.
pub fn for_drawing(doc: &Document, exclude: &[NodeId], visible: Option<Rect>, glyphs: Option<GlyphSnap>) -> Self {
let mut t = Self::collect_inner(doc, exclude, None, visible, Gather::Drawing);
t.push_paths(doc, exclude, true);
if glyphs {
t.push_glyphs(doc, exclude, visible);
if let Some(o) = glyphs {
t.push_glyphs(doc, exclude, visible, o);
}
t.index()
}
@@ -214,8 +215,9 @@ impl Targets {
}
/// A glyph guide through `at` along `dir` (on the page): a line the art lines up with when it
/// runs across or down the page, else an angled line a point lands on.
fn push_glyph_line(&mut self, at: Point, dir: Vec2) {
/// runs across or down the page, else (with `angular`, Angular Guides) an angled line a point
/// lands on.
fn push_glyph_line(&mut self, at: Point, dir: Vec2, angular: bool) {
let len = dir.hypot();
if !(len.is_finite() && len > 1e-12 && at.is_finite()) {
return;
@@ -225,7 +227,7 @@ impl Targets {
self.ys.push((at.y, at, Kind::Glyph));
} else if d.x.abs() <= 1e-9 {
self.xs.push((at.x, at, Kind::Glyph));
} else {
} else if angular {
self.angled.push((at, d));
}
}
@@ -234,8 +236,9 @@ impl Targets {
/// a single line of type also its baseline and x-height, the proximity guides at the edges of
/// each character's advance width, and its glyphs' anchor points. In rotated type these
/// guides run at its angle (angular guides), and type of one glyph also has angular guides
/// along the glyph's slanted straight segments.
fn push_glyphs(&mut self, doc: &Document, exclude: &[NodeId], visible: Option<Rect>) {
/// along the glyph's slanted straight segments. Each kind of guide is there only while its
/// option `o` is on.
fn push_glyphs(&mut self, doc: &Document, exclude: &[NodeId], visible: Option<Rect>, o: GlyphSnap) {
let skip: HashSet<NodeId> = exclude.iter().copied().collect();
let db = FontDb::global();
let kind = Kind::Glyph;
@@ -264,10 +267,10 @@ impl Targets {
// Text space → the page, and a direction in text space → one on the page.
let to_doc = |p: Point| t.xf * p;
let along = |m: Affine, v: Vec2| (m * v.to_point()) - (m * Point::ZERO);
if single {
if single && o.baseline {
let line_doc = t.xf * lay.line_xf;
for line in &lay.lines {
self.push_glyph_line(line_doc * Point::new(line.x0, line.baseline), along(line_doc, Vec2::new(1.0, 0.0)));
self.push_glyph_line(line_doc * Point::new(line.x0, line.baseline), along(line_doc, Vec2::new(1.0, 0.0)), o.angular);
}
}
// The last x-height pushed, to push each run of glyphs alike once.
@@ -300,23 +303,26 @@ impl Targets {
let (l, k) = (d.hypot(), dir.hypot());
l > 1e-12 && k > 1e-12 && (d / l - dir / k).hypot() <= 1e-9 && ((at - a).cross(d / l)).abs() <= 1e-6
});
if face.x_height() > 0.0 && !same {
self.push_glyph_line(at, dir);
if o.x_height && face.x_height() > 0.0 && !same {
self.push_glyph_line(at, dir, o.angular);
last_x_height = Some((at, dir));
}
// Proximity guides: across the baseline at both edges of the width the layout
// gives the character (its advance with tracking and kerning).
for e in [g.origin, g.origin + ahead * g.advance] {
self.push_glyph_line(to_doc(e), along(t.xf, up));
if o.proximity {
for e in [g.origin, g.origin + ahead * g.advance] {
self.push_glyph_line(to_doc(e), along(t.xf, up), o.angular);
}
}
}
if single {
for seg in g.outline.segments() {
if anchors > 0 {
if o.anchors && anchors > 0 {
self.points.push((to_doc(seg.start()), Kind::Anchor));
anchors -= 1;
}
if one_glyph
if o.angular
&& one_glyph
&& angular > 0
&& let PathSeg::Line(l) = seg
{
@@ -324,16 +330,18 @@ impl Targets {
let d = q - p;
let len = d.hypot();
if len > 1e-6 && d.x.abs() > 1e-9 * len && d.y.abs() > 1e-9 * len {
self.push_glyph_line(p, d);
self.push_glyph_line(p, d, true);
angular -= 1;
}
}
}
}
let r = t.xf.transform_rect_bbox(bb);
for p in [Point::new(r.x0, r.y0), Point::new(r.x1, r.y0), Point::new(r.x1, r.y1), Point::new(r.x0, r.y1)] {
self.xs.push((p.x, p, kind));
self.ys.push((p.y, p, kind));
if o.bounds {
let r = t.xf.transform_rect_bbox(bb);
for p in [Point::new(r.x0, r.y0), Point::new(r.x1, r.y0), Point::new(r.x1, r.y1), Point::new(r.x0, r.y1)] {
self.xs.push((p.x, p, kind));
self.ys.push((p.y, p, kind));
}
}
}
});
+72 -27
View File
@@ -251,7 +251,7 @@ fn shaper_sources_are_no_targets() {
|| t.segments.iter().any(|(_, s)| s.start() == corner || s.end() == corner)
};
assert!(!gives_corner(&Targets::collect(&d, &[], None)));
assert!(!gives_corner(&Targets::for_drawing(&d, &[], None, false)));
assert!(!gives_corner(&Targets::for_drawing(&d, &[], None, None)));
assert!(!gives_corner(&Targets::for_anchor_drag(&d, &Selection::default())));
assert!(!gives_corner(&Targets::for_handle(&d, face)));
assert!(!object_boxes(&d, &[face]).contains(&Rect::new(0.0, 300.0, 150.0, 400.0)), "the source shapes' bounds");
@@ -341,7 +341,7 @@ fn drawn_points_land_on_anchors_paths_alignments_and_construction_guides() {
let (d, _) = doc_with_rect();
let (s, p) = (Selection::default(), paint());
let c = cx(&d, &s, &p);
let t = Targets::for_drawing(&d, &[], None, false).styled(&c);
let t = Targets::for_drawing(&d, &[], None, None).styled(&c);
// The rect is 100..200: its corner, a point on its side, and in line with an anchor.
let (q, ov) = t.snap_from(Point::new(202.0, 198.0), None, 4.0);
assert_eq!((q, labels(&ov)), (Point::new(200.0, 200.0), vec!["anchor"]));
@@ -395,7 +395,7 @@ fn drawn_points_land_on_intersections() {
d.guides.push(vectorcraft_doc::Guide::new(false, 150.0));
let (s, p) = (Selection::default(), paint());
let c = ToolContext { guides: true, ..cx(&d, &s, &p) };
let t = Targets::for_drawing(&d, &[], None, false).styled(&c).with_rulers(&c);
let t = Targets::for_drawing(&d, &[], None, None).styled(&c).with_rulers(&c);
let (q, ov) = t.snap_from(Point::new(152.0, 101.5), None, 4.0);
assert!((q - Point::new(150.0, 100.0)).hypot() < 1e-9, "{q:?}");
assert_eq!(labels(&ov), ["intersect"]);
@@ -428,7 +428,7 @@ fn perpendicular_snapping_rejects_a_curved_target() {
),
)
.unwrap();
let targets = Targets { tangent: false, perpendicular: true, ..Targets::for_drawing(&d, &[], None, false) };
let targets = Targets { tangent: false, perpendicular: true, ..Targets::for_drawing(&d, &[], None, None) };
assert!(targets.touching(Point::new(400.0, 500.0), Point::new(450.0, 500.0), 4.0).is_none());
assert!(targets.touching(Point::new(300.0, 160.0), Point::new(200.0, 160.0), 4.0).is_some());
}
@@ -448,7 +448,7 @@ fn drawn_lines_land_perpendicular_to_segments_and_tangent_to_curves() {
let c = cx(&d, &s, &p);
let snap = |c: &ToolContext, from: Point, to: Point| {
let leave = Leave::segment(c, from, false);
PointSnap::new(c, || Targets::for_drawing(&d, &[], None, false)).snap_from(c, to, Some(&leave))
PointSnap::new(c, || Targets::for_drawing(&d, &[], None, None)).snap_from(c, to, Some(&leave))
};
// From (300, 160) the rect's right side (x = 200) is square at (200, 160).
let (q, ov) = snap(&c, Point::new(300.0, 160.0), Point::new(202.0, 163.0));
@@ -459,7 +459,7 @@ fn drawn_lines_land_perpendicular_to_segments_and_tangent_to_curves() {
assert_eq!(snap(&c, Point::new(300.0, 160.0), Point::new(202.0, 163.0)).0, q, "deterministic");
// Off, the point only lands on the path, where the pointer is.
let curved = Leave::segment(&c, Point::new(300.0, 160.0), false).without_contact();
let (_, ov) = PointSnap::new(&c, || Targets::for_drawing(&d, &[], None, false)).snap_from(&c, Point::new(202.0, 163.0), Some(&curved));
let (_, ov) = PointSnap::new(&c, || Targets::for_drawing(&d, &[], None, None)).snap_from(&c, Point::new(202.0, 163.0), Some(&curved));
assert!(!labels(&ov).contains(&"perpendicular"));
let off = ToolContext { snap_perpendicular: false, ..cx(&d, &s, &p) };
let (q, ov) = snap(&off, Point::new(300.0, 160.0), Point::new(202.0, 163.0));
@@ -474,7 +474,7 @@ fn drawn_lines_land_perpendicular_to_segments_and_tangent_to_curves() {
assert!(!labels(&snap(&none, Point::new(400.0, 500.0), Point::new(477.0, 459.0)).1).contains(&"tangent"));
// Out of reach, or with nothing finite to go on: left alone, no panic.
assert_eq!(snap(&c, Point::new(300.0, 160.0), Point::new(250.0, 175.0)).0, Point::new(250.0, 175.0));
let t = Targets::for_drawing(&d, &[], None, false);
let t = Targets::for_drawing(&d, &[], None, None);
let t = Targets { tangent: true, perpendicular: true, ..t };
for bad in [f64::NAN, f64::INFINITY] {
assert!(t.touching(Point::new(bad, 1.0), Point::new(200.0, 160.0), 4.0).is_none());
@@ -492,7 +492,7 @@ fn contact_snaps_only_for_segments_and_in_busy_spots() {
let (s, p) = (Selection::default(), paint());
let c = cx(&d, &s, &p);
let snap = |t: &Targets, leave: &Leave| t.snap_from(Point::new(202.0, 163.0), Some(leave), 4.0);
let t = Targets { tangent: true, perpendicular: true, ..Targets::for_drawing(&d, &[], None, false) };
let t = Targets { tangent: true, perpendicular: true, ..Targets::for_drawing(&d, &[], None, None) };
let (_, ov) = snap(&t, &Leave::segment(&c, Point::new(300.0, 160.0), false));
assert_eq!(labels(&ov), ["perpendicular"]);
let (_, ov) = snap(&t, &Leave::diagonal(Point::new(300.0, 160.0), false));
@@ -540,7 +540,7 @@ fn bisection_finds_what_a_scan_finds() {
let mut s = Selection::default();
s.set([id]);
s.anchors.insert(id, [(0, 1)].into_iter().collect());
for t in [Targets::collect(&d, &[], None), Targets::for_drawing(&d, &[id], None, false), Targets::for_anchor_drag(&d, &s)] {
for t in [Targets::collect(&d, &[], None), Targets::for_drawing(&d, &[id], None, None), Targets::for_anchor_drag(&d, &s)] {
for q in (0..40).flat_map(|i| (0..40).map(move |j| Point::new(i as f64 * 12.7, j as f64 * 11.3))) {
let scan = t.points.iter().copied().filter(|(a, _)| a.distance(q) <= 6.0).min_by(|a, b| a.0.distance(q).total_cmp(&b.0.distance(q)));
assert_eq!(t.nearest_point(q, 6.0).map(|h| h.0.distance(q)), scan.map(|h| h.0.distance(q)), "{q:?}");
@@ -642,15 +642,17 @@ fn pen_click_near_a_glyph_anchor_lands_on_it() {
let anchor = t.xf * g.outline.segments().map(|s| s.start()).find(|p| inside(*p)).expect("an anchor inside the glyph");
let (s, p) = (Selection::default(), paint());
let click = anchor + Vec2::new(1.5, -1.0);
let first_anchor = |glyphs: bool| {
let c = ToolContext { snap_to_glyph: glyphs, ..cx(&d, &s, &p) };
let first_anchor = |glyphs: bool, anchors: bool| {
let c = ToolContext { snap_to_glyph: glyphs, glyph_snap: GlyphSnap { anchors, ..GlyphSnap::default() }, ..cx(&d, &s, &p) };
let mut pen = crate::pen::PenTool::default();
let acts = pen.pointer(&c, &PointerEvent::new(PointerKind::Down, click.x, click.y));
let Some(Action::Preview(_, v)) = acts.get(1) else { panic!("{acts:?}") };
Point::new(v["anchors"][0]["x"].as_f64().unwrap(), v["anchors"][0]["y"].as_f64().unwrap())
};
assert!(first_anchor(true).distance(anchor) < 1e-9, "{:?} vs {anchor:?}", first_anchor(true));
assert!(first_anchor(false).distance(anchor) > 1e-3);
assert!(first_anchor(true, true).distance(anchor) < 1e-9, "{:?} vs {anchor:?}", first_anchor(true, true));
assert!(first_anchor(false, true).distance(anchor) > 1e-3);
// The Character panel's Snap to Glyph › Anchor Point off: no glyph anchors (#4232).
assert!(first_anchor(true, false).distance(anchor) > 1e-3);
// Snap to Glyph works with Smart Guides on: Snap to Point alone does not land on glyph anchors.
let c = ToolContext { snap_to_glyph: true, smart_guides: false, snap_to_point: true, ..cx(&d, &s, &p) };
let mut pen = crate::pen::PenTool::default();
@@ -669,21 +671,21 @@ fn drawn_points_line_up_with_the_x_height_of_a_single_line() {
let face = FontDb::global().face_by_id(g.font_id).unwrap();
let x_height = (t.xf * g.xf * Point::new(0.0, -face.x_height())).y;
let p = Point::new(400.0, x_height + 2.0);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert!((q.y - x_height).abs() < 1e-9, "{q:?}, x-height {x_height}");
let (q, _) = Targets::for_drawing(&d, &[], None, false).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, None).snap_point(p, 4.0);
assert_eq!(q.y, p.y);
let (d, _) = glyph_doc("H\nH", 0.0);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert_eq!(q.y, p.y, "a paragraph has no x-height guide");
// Baseline Shift raises the x-height with the glyphs.
let (d, _) = glyph_doc_with("H", 0.0, vectorcraft_doc::CharStyle { size: 60.0, baseline_shift: 10.0, ..Default::default() });
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(Point::new(400.0, x_height - 10.0 + 2.0), 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(Point::new(400.0, x_height - 10.0 + 2.0), 4.0);
assert!((q.y - (x_height - 10.0)).abs() < 1e-9, "{q:?}, shifted x-height {}", x_height - 10.0);
// Character Rotation turns the glyph, not its baseline shift.
let style = vectorcraft_doc::CharStyle { size: 60.0, baseline_shift: 10.0, rotation: 30.0, ..Default::default() };
let (d, _) = glyph_doc_with("H", 0.0, style);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(Point::new(400.0, x_height - 10.0 + 2.0), 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(Point::new(400.0, x_height - 10.0 + 2.0), 4.0);
assert!((q.y - (x_height - 10.0)).abs() < 1e-9, "{q:?}, shifted x-height {}", x_height - 10.0);
// Glyph Scaling widens a rotated glyph without raising its x-height.
let mut t =
@@ -701,7 +703,7 @@ fn drawn_points_line_up_with_the_x_height_of_a_single_line() {
let g = lay.glyphs.iter().find(|g| !g.outline.elements().is_empty()).expect("a glyph with an outline");
assert!(g.scale > 1.2, "Glyph Scaling widens the glyph: {}", g.scale);
let expected = (t.xf * g.origin).y - (300.0 - x_height);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(Point::new(30.0, expected + 2.0), 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(Point::new(30.0, expected + 2.0), 4.0);
assert!((q.y - expected).abs() < 1e-9, "{q:?}, x-height {expected}");
}
@@ -727,7 +729,7 @@ fn the_x_height_guide_of_skewed_path_type_runs_through_the_glyphs_x_height() {
let normal = Vec2::new(ahead.y, -ahead.x);
// Beside the guide's line, past the glyph's end along it.
let p = top + ahead * 80.0 + normal * 1.5;
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert!((q - top).cross(ahead).abs() < 1e-6, "{q:?} is off the x-height line through {top:?}");
}
@@ -738,16 +740,16 @@ fn drawn_points_line_up_with_the_edge_of_a_characters_width() {
let lay = layout_text(FontDb::global(), &t);
let edge = (t.xf * lay.glyphs[1].origin).x;
let p = Point::new(edge + 0.3, 450.0);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert!((q.x - edge).abs() < 1e-9, "{q:?}, edge {edge}");
let (q, _) = Targets::for_drawing(&d, &[], None, false).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, None).snap_point(p, 4.0);
assert_eq!(q.x, p.x);
// Character Rotation turns the glyphs, not their widths along the baseline.
let (d, t) = glyph_doc_with("HH", 0.0, vectorcraft_doc::CharStyle { size: 60.0, rotation: 30.0, ..Default::default() });
let lay = layout_text(FontDb::global(), &t);
let edge = (t.xf * lay.glyphs[1].origin).x;
let p = Point::new(edge + 0.3, 450.0);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert!((q.x - edge).abs() < 1e-9, "{q:?}, edge {edge}");
}
@@ -774,13 +776,56 @@ fn a_rotated_glyph_has_angular_guides_along_its_strokes() {
let out = if far == a { -dir } else { dir };
let p = far + out * 30.0 + Vec2::new(-dir.y, dir.x) * 1.5;
let on_line = |q: Point| (q - a).cross(dir).abs();
let (q, ov) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, ov) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert!(on_line(q) < 1e-9, "{q:?} is {} off the stroke's line", on_line(q));
assert!(!lines(&ov).is_empty());
let (q, _) = Targets::for_drawing(&d, &[], None, false).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, None).snap_point(p, 4.0);
assert!(on_line(q) > 1e-3, "no angular guide with Snap to Glyph off");
}
/// The Character panel's Snap to Glyph options (#4232): each one off takes away its own guides and
/// leaves the others.
#[test]
fn each_snap_to_glyph_option_takes_away_only_its_own_guides() {
// The glyph guides: lines across the page (ys), down it (xs), at an angle, and anchor points.
let count = |d: &vectorcraft_doc::Document, o: GlyphSnap| {
let t = Targets::for_drawing(d, &[], None, Some(o));
let glyph = |v: &[(f64, Point, Kind)]| v.iter().filter(|e| e.2 == Kind::Glyph).count();
[glyph(&t.ys), glyph(&t.xs), t.angled.len(), t.points.iter().filter(|e| e.1 == Kind::Anchor).count()]
};
let all = GlyphSnap::default();
let (d, t) = glyph_doc("H", 0.0);
let on = count(&d, all);
let lay = layout_text(FontDb::global(), &t);
let g = &lay.glyphs[0];
let anchors = g.outline.segments().count();
assert!(anchors > 0);
// One line, one glyph: a baseline, an x-height, two width edges, four corners and its anchors.
for (off, less) in [
(GlyphSnap { baseline: false, ..all }, [1, 0, 0, 0]),
(GlyphSnap { x_height: false, ..all }, [1, 0, 0, 0]),
(GlyphSnap { bounds: false, ..all }, [4, 4, 0, 0]),
(GlyphSnap { proximity: false, ..all }, [0, 2, 0, 0]),
(GlyphSnap { anchors: false, ..all }, [0, 0, 0, anchors]),
(GlyphSnap { angular: false, ..all }, [0, 0, 0, 0]),
] {
let got = count(&d, off);
assert_eq!(std::array::from_fn::<usize, 4, _>(|i| on[i] - got[i]), less, "{off:?}: {got:?} of {on:?}");
}
// A point near the x-height lands on it only with x-height on.
let face = FontDb::global().face_by_id(g.font_id).unwrap();
let x_height = (t.xf * g.xf * Point::new(0.0, -face.x_height())).y;
let p = Point::new(400.0, x_height + 2.0);
assert!((Targets::for_drawing(&d, &[], None, Some(all)).snap_point(p, 4.0).0.y - x_height).abs() < 1e-9);
assert_eq!(Targets::for_drawing(&d, &[], None, Some(GlyphSnap { x_height: false, ..all })).snap_point(p, 4.0).0.y, p.y);
// Rotated type: its baseline, x-height and width edges and its strokes run at an angle, all
// Angular Guides; its glyph bounds still run across and down the page.
let (d, _) = glyph_doc("H", 20f64.to_radians());
let (on, off) = (count(&d, all), count(&d, GlyphSnap { angular: false, ..all }));
assert!(on[2] > 0, "{on:?}");
assert_eq!(off, [on[0], on[1], 0, on[3]]);
}
/// Snap to Glyph: the baseline of type turned slightly runs at its angle, and a point near it
/// lands on it even close to where it starts, far nearer the horizontal through that point.
#[test]
@@ -792,7 +837,7 @@ fn a_slightly_rotated_baseline_pulls_points_near_its_start() {
let dir = (m * Point::new(1.0, 0.0)) - (m * Point::ZERO);
let dir = dir / dir.hypot();
let p = start - dir * 10.0 + Vec2::new(-dir.y, dir.x) * 1.0;
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_point(p, 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_point(p, 4.0);
assert!((q - start).cross(dir).abs() < 1e-9, "{q:?} is off the baseline");
}
@@ -810,6 +855,6 @@ fn a_shift_constrained_point_slides_onto_a_rotated_baseline() {
let (s, p) = (Selection::default(), paint());
let c = cx(&d, &s, &p);
let from = Leave::segment(&c, crossing - Vec2::new(60.0, 0.0), true);
let (q, _) = Targets::for_drawing(&d, &[], None, true).snap_from(crossing + Vec2::new(1.5, 0.3), Some(&from), 4.0);
let (q, _) = Targets::for_drawing(&d, &[], None, Some(GlyphSnap::default())).snap_from(crossing + Vec2::new(1.5, 0.3), Some(&from), 4.0);
assert!(q.distance(crossing) < 1e-9, "{q:?} vs {crossing:?}");
}
+2 -1
View File
@@ -55,7 +55,7 @@ mod context;
mod cursor;
mod input;
pub use context::{PaintDefaults, ScreenFrame, ToolContext};
pub use context::{GlyphSnap, PaintDefaults, ScreenFrame, ToolContext};
pub use cursor::Cursor;
pub use input::{Action, Mods, PointerEvent, PointerKind, ToolKey};
@@ -463,6 +463,7 @@ pub(crate) mod testutil {
snap_tangent: true,
snap_perpendicular: true,
snap_to_glyph: false,
glyph_snap: GlyphSnap::default(),
corner_widgets: true,
fill_active: true,
gradient_stop: None,
+8
View File
@@ -5575,3 +5575,11 @@ axis Y Axis Y ос
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Точков текст
Area Text Текст в зона
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Показване на опциите за прилепване към глиф
Hide Snap to Glyph Options Скриване на опциите за прилепване към глиф
Snap to Glyph Options Опции за прилепване към глиф
x-height Височина на x
Glyph Bounds Граници на глифа
Proximity Guides Водачи за близост
Angular Guides Ъглови водачи
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5676,3 +5676,11 @@ theme Appearance Aparença
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Text de punt
Area Text Text d’àrea
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Mostra les opcions d’ajust al glif
Hide Snap to Glyph Options Amaga les opcions d’ajust al glif
Snap to Glyph Options Opcions d’ajust al glif
x-height Alçada x
Glyph Bounds Límits del glif
Proximity Guides Guies de proximitat
Angular Guides Guies angulars
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5685,3 +5685,11 @@ axis Y Axis osa Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Bodový text
Area Text Text v oblasti
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Zobrazit možnosti přichytávání ke glyfům
Hide Snap to Glyph Options Skrýt možnosti přichytávání ke glyfům
Snap to Glyph Options Možnosti přichytávání ke glyfům
x-height Výška x
Glyph Bounds Hranice glyfů
Proximity Guides Vodítka blízkosti
Angular Guides Úhlová vodítka
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5590,3 +5590,11 @@ axis Y Axis Y-akse
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Punkttekst
Area Text Områdetekst
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Vis indstillinger for fastgørelse til tegn
Hide Snap to Glyph Options Skjul indstillinger for fastgørelse til tegn
Snap to Glyph Options Indstillinger for fastgørelse til tegn
x-height x-højde
Glyph Bounds Glyfafgrænsning
Proximity Guides Nærhedshjælpelinjer
Angular Guides Vinkelhjælpelinjer
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5267,3 +5267,11 @@ axis Y Axis Y-Achse
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Punkttext
Area Text Flächentext
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Optionen für „An Glyphe ausrichten“ einblenden
Hide Snap to Glyph Options Optionen für „An Glyphe ausrichten“ ausblenden
Snap to Glyph Options Optionen für „An Glyphe ausrichten“
x-height x-Höhe
Glyph Bounds Glyphenbegrenzung
Proximity Guides Näherungshilfslinien
Angular Guides Winkelhilfslinien
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5637,3 +5637,11 @@ axis Y Axis Άξονας Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Κείμενο σημείου
Area Text Κείμενο περιοχής
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Εμφάνιση επιλογών προσκόλλησης σε γλυφή
Hide Snap to Glyph Options Απόκρυψη επιλογών προσκόλλησης σε γλυφή
Snap to Glyph Options Επιλογές προσκόλλησης σε γλυφή
x-height Ύψος x
Glyph Bounds Όρια γλυφής
Proximity Guides Οδηγοί εγγύτητας
Angular Guides Γωνιακοί οδηγοί
Can't render this file because it contains an unexpected character in line 164 and column 5.
+8
View File
@@ -5627,3 +5627,11 @@ axis Y Axis Eje Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Texto de punto
Area Text Texto de área
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Mostrar opciones de ajuste a glifo
Hide Snap to Glyph Options Ocultar opciones de ajuste a glifo
Snap to Glyph Options Opciones de ajuste a glifo
x-height Altura x
Glyph Bounds Límites del glifo
Proximity Guides Guías de proximidad
Angular Guides Guías angulares
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5716,3 +5716,11 @@ axis Y Axis Y-akseli
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Pisteteksti
Area Text Alueteksti
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Näytä merkkiin kohdistuksen asetukset
Hide Snap to Glyph Options Piilota merkkiin kohdistuksen asetukset
Snap to Glyph Options Merkkiin kohdistuksen asetukset
x-height x-korkeus
Glyph Bounds Merkin rajat
Proximity Guides Läheisyysapuviivat
Angular Guides Kulma-apuviivat
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5684,3 +5684,11 @@ axis Y Axis Axe Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Texte de point
Area Text Texte captif
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Afficher les options de magnétisme du glyphe
Hide Snap to Glyph Options Masquer les options de magnétisme du glyphe
Snap to Glyph Options Options de magnétisme du glyphe
x-height Hauteur d’x
Glyph Bounds Limites du glyphe
Proximity Guides Repères de proximité
Angular Guides Repères angulaires
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5671,3 +5671,11 @@ axis Y Axis Y tengely
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Pontszöveg
Area Text Területszöveg
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Glifához illesztés beállításainak megjelenítése
Hide Snap to Glyph Options Glifához illesztés beállításainak elrejtése
Snap to Glyph Options Glifához illesztés beállításai
x-height x-magasság
Glyph Bounds Glifahatárok
Proximity Guides Közelségi segédvonalak
Angular Guides Szög-segédvonalak
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5590,3 +5590,11 @@ axis Y Axis Sumbu Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Teks titik
Area Text Teks area
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Tampilkan opsi snap ke glif
Hide Snap to Glyph Options Sembunyikan opsi snap ke glif
Snap to Glyph Options Opsi snap ke glif
x-height Tinggi-x
Glyph Bounds Batas glif
Proximity Guides Panduan kedekatan
Angular Guides Panduan sudut
Can't render this file because it contains an unexpected character in line 2169 and column 5.
+8
View File
@@ -5301,3 +5301,11 @@ axis Y Axis Asse Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Testo per punti
Area Text Testo in area
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Mostra opzioni di aggancio a glifo
Hide Snap to Glyph Options Nascondi opzioni di aggancio a glifo
Snap to Glyph Options Opzioni di aggancio a glifo
x-height Altezza x
Glyph Bounds Limiti del glifo
Proximity Guides Guide di prossimità
Angular Guides Guide angolari
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5363,3 +5363,11 @@ axis Y Axis Y 軸
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text ポイント文字
Area Text エリア内文字
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options グリフにスナップのオプションを表示
Hide Snap to Glyph Options グリフにスナップのオプションを隠す
Snap to Glyph Options グリフにスナップのオプション
x-height x ハイト
Glyph Bounds 字形の境界
Proximity Guides 近接ガイド
Angular Guides 角度ガイド
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5674,3 +5674,11 @@ swatch Bright Violet კაშკაშა იისფერი
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text წერტილოვანი ტექსტი
Area Text არის ტექსტი
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options გლიფზე მიბმის პარამეტრების ჩვენება
Hide Snap to Glyph Options გლიფზე მიბმის პარამეტრების დამალვა
Snap to Glyph Options გლიფზე მიბმის პარამეტრები
x-height x სიმაღლე
Glyph Bounds გლიფის საზღვრები
Proximity Guides სიახლოვის დამხმარე ხაზები
Angular Guides კუთხური დამხმარე ხაზები
Can't render this file because it is too large.
+8
View File
@@ -5264,3 +5264,11 @@ axis Y Axis Y축
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text 포인트 문자
Area Text 영역 문자
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options 글리프에 스냅 옵션 표시
Hide Snap to Glyph Options 글리프에 스냅 옵션 숨기기
Snap to Glyph Options 글리프에 스냅 옵션
x-height x 높이
Glyph Bounds 글리프 경계
Proximity Guides 근접 안내선
Angular Guides 각도 안내선
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5634,3 +5634,11 @@ axis Y Axis Y-as
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Punttekst
Area Text Vlaktekst
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Opties voor uitlijnen op glyph tonen
Hide Snap to Glyph Options Opties voor uitlijnen op glyph verbergen
Snap to Glyph Options Opties voor uitlijnen op glyph
x-height x-hoogte
Glyph Bounds Glyphbegrenzing
Proximity Guides Nabijheidshulplijnen
Angular Guides Hoekhulplijnen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5539,3 +5539,11 @@ axis Y Axis Oś Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Tekst punktowy
Area Text Tekst akapitowy
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Pokaż opcje przyciągania do glifów
Hide Snap to Glyph Options Ukryj opcje przyciągania do glifów
Snap to Glyph Options Opcje przyciągania do glifów
x-height Wysokość x
Glyph Bounds Granice glifów
Proximity Guides Linie bliskości
Angular Guides Linie kątowe
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5602,3 +5602,11 @@ axis Y Axis Eixo Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Texto de Ponto
Area Text Texto de Área
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Mostrar Opções de Ajuste ao Glifo
Hide Snap to Glyph Options Ocultar Opções de Ajuste ao Glifo
Snap to Glyph Options Opções de Ajuste ao Glifo
x-height Altura-x
Glyph Bounds Limites do Glifo
Proximity Guides Guias de Proximidade
Angular Guides Guias Angulares
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -5663,3 +5663,11 @@ theme Appearance Оформление
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Точечный текст
Area Text Текст в области
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Показать параметры привязки к глифу
Hide Snap to Glyph Options Скрыть параметры привязки к глифу
Snap to Glyph Options Параметры привязки к глифу
x-height Высота x
Glyph Bounds Границы глифа
Proximity Guides Направляющие близости
Angular Guides Угловые направляющие
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5709,3 +5709,11 @@ axis Y Axis Y-axel
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Punkttext
Area Text Områdestext
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Visa alternativ för fäst mot glyf
Hide Snap to Glyph Options Dölj alternativ för fäst mot glyf
Snap to Glyph Options Alternativ för fäst mot glyf
x-height x-höjd
Glyph Bounds Glyfgränser
Proximity Guides Närhetsstödlinjer
Angular Guides Vinkelstödlinjer
Can't render this file because it contains an unexpected character in line 5 and column 25.
+8
View File
@@ -5643,3 +5643,11 @@ axis Y Axis Y ekseni
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Nokta Metni
Area Text Alan Metni
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Glif'e Yapışma Seçeneklerini Göster
Hide Snap to Glyph Options Glif'e Yapışma Seçeneklerini Gizle
Snap to Glyph Options Glif'e Yapışma Seçenekleri
x-height x Yüksekliği
Glyph Bounds Glif Sınırları
Proximity Guides Yakınlık Kılavuzları
Angular Guides Açısal Kılavuzlar
Can't render this file because it contains an unexpected character in line 5 and column 25.
+8
View File
@@ -5281,3 +5281,11 @@ axis Y Axis Вісь Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Точковий текст
Area Text Текст в області
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Показати параметри прив’язки до гліфа
Hide Snap to Glyph Options Приховати параметри прив’язки до гліфа
Snap to Glyph Options Параметри прив’язки до гліфа
x-height Висота x
Glyph Bounds Межі гліфа
Proximity Guides Напрямні близькості
Angular Guides Кутові напрямні
Can't render this file because it contains an unexpected character in line 178 and column 5.
+8
View File
@@ -5283,3 +5283,11 @@ axis Y Axis Trục Y
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text Chữ điểm
Area Text Chữ vùng
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options Hiện tùy chọn hút vào glyph
Hide Snap to Glyph Options Ẩn tùy chọn hút vào glyph
Snap to Glyph Options Tùy chọn hút vào glyph
x-height Chiều cao x
Glyph Bounds Giới hạn glyph
Proximity Guides Đường căn lân cận
Angular Guides Đường căn góc
Can't render this file because it contains an unexpected character in line 200 and column 5.
+8
View File
@@ -5524,3 +5524,11 @@ axis Y Axis Y 轴
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text 点文字
Area Text 区域文字
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options 显示靠齐字符选项
Hide Snap to Glyph Options 隐藏靠齐字符选项
Snap to Glyph Options 靠齐字符选项
x-height x 高度
Glyph Bounds 字形边界
Proximity Guides 邻近参考线
Angular Guides 角度参考线
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5320,3 +5320,11 @@ axis Y Axis Y 軸
# Align panel menu › Align to Glyph Bounds (#4231).
Point Text 點狀文字
Area Text 區域文字
# Character panel menu › Show Snap to Glyph Options (#4232).
Show Snap to Glyph Options 顯示靠齊字符選項
Hide Snap to Glyph Options 隱藏靠齊字符選項
Snap to Glyph Options 靠齊字符選項
x-height x 高度
Glyph Bounds 字符邊界
Proximity Guides 鄰近參考線
Angular Guides 角度參考線
Can't render this file because it contains an unexpected character in line 6 and column 47.
+46
View File
@@ -790,7 +790,49 @@ pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.separator();
}
/// The panel state key of the panel menu's Show Snap to Glyph Options.
const SNAP_TO_GLYPH_OPTIONS: &str = "ch-snap-to-glyph-options";
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
attributes(app, ui);
// Drawing over type needs no type selected, so the options show without one too.
if pstate::<bool>(ui.ctx(), SNAP_TO_GLYPH_OPTIONS) {
snap_to_glyph_options(app, ui);
}
}
/// The Snap to Glyph options (Show Snap to Glyph Options): which guides View › Snap to Glyph gives,
/// each a checkbox running `view.snapToGlyph.options`. Dimmed while Snap to Glyph is off.
fn snap_to_glyph_options(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.add_space(4.0);
ui.separator();
widgets::section_header(ui, "Snap to Glyph");
let (o, enabled) = (app.session.prefs.glyph_snap, app.ui.view.snap_to_glyph);
egui::Grid::new("ch-snap-to-glyph").num_columns(2).show(ui, |ui| {
for (i, (label, key, on)) in [
("Baseline", "baseline", o.baseline),
("x-height", "xHeight", o.x_height),
("Glyph Bounds", "bounds", o.bounds),
("Proximity Guides", "proximity", o.proximity),
("Angular Guides", "angular", o.angular),
("Anchor Point", "anchors", o.anchors),
]
.into_iter()
.enumerate()
{
// A known key and a bool: the command can't refuse it.
if widgets::check(ui, label, on, enabled) {
app.run("view.snapToGlyph.options", json!({ key: !on })).ok();
}
if i % 2 == 1 {
ui.end_row();
}
}
});
}
/// The character attributes of the type selected (an empty state without any).
fn attributes(app: &mut VectorcraftApp, ui: &mut Ui) {
let Some((s, _)) = text_style(app) else {
app.character_focus = None;
super::empty_state(ui, "type", tl!("No text selected"), tl!("Select a text object to edit its character attributes."));
@@ -919,6 +961,10 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
if menu_item(ui, if hidden { tl!("Show Options") } else { tl!("Hide Options") }, true, false) {
set_pstate(ui.ctx(), "ch-hide-options", !hidden);
}
let glyph = pstate::<bool>(ui.ctx(), SNAP_TO_GLYPH_OPTIONS);
if menu_item(ui, if glyph { tl!("Hide Snap to Glyph Options") } else { tl!("Show Snap to Glyph Options") }, true, false) {
set_pstate(ui.ctx(), SNAP_TO_GLYPH_OPTIONS, !glyph);
}
ui.separator();
let s = st.map(|x| x.0);
for (label, key, on) in [
@@ -704,3 +704,39 @@ fn reset_panel_resets_every_attribute_but_the_font() {
reset_panel(&mut app);
assert_eq!(runs(&app), [("One ".to_string(), styled), ("12".to_string(), plain)]);
}
/// #4232: the panel menu's Show Snap to Glyph Options shows the Snap to Glyph section (with no
/// type selected too) and Hide Snap to Glyph Options hides it; its checkboxes, dimmed while View ›
/// Snap to Glyph is off, set the options.
#[test]
fn snap_to_glyph_options_show_from_the_menu_and_set_the_options() {
use super::super::tests_appearance::{click, frame_events, text_rect};
let mut app = VectorcraftApp::new(Session::new(), Default::default());
let ctx = egui::Context::default();
let toggle = |app: &mut VectorcraftApp, label: &str| {
let texts = frame_events(&ctx, app, vec![], menu);
let at = texts.iter().find(|(t, _)| t.trim() == label).unwrap_or_else(|| panic!("{label} in {texts:?}")).1;
click(&ctx, app, at.center(), menu);
};
let section = |app: &mut VectorcraftApp| frame_events(&ctx, app, vec![], show);
let labels = ["Baseline", "x-height", "Glyph Bounds", "Proximity Guides", "Angular Guides", "Anchor Point"];
assert!(!section(&mut app).iter().any(|(t, _)| t == "Proximity Guides"), "hidden at first");
toggle(&mut app, "Show Snap to Glyph Options");
let texts = section(&mut app);
assert!(labels.iter().all(|l| texts.iter().any(|(t, _)| t == l)), "{texts:?}");
let check = |app: &mut VectorcraftApp, label: &str| {
let at = text_rect(&section(app), label).center();
click(&ctx, app, at, show);
};
check(&mut app, "Angular Guides");
assert!(app.session.prefs.glyph_snap.angular, "dimmed while Snap to Glyph is off");
app.ui.view.snap_to_glyph = true;
check(&mut app, "Angular Guides");
check(&mut app, "x-height");
let o = app.session.prefs.glyph_snap;
assert!(!o.angular && !o.x_height && o.baseline && o.bounds && o.proximity && o.anchors, "{o:?}");
check(&mut app, "Angular Guides");
assert!(app.session.prefs.glyph_snap.angular);
toggle(&mut app, "Hide Snap to Glyph Options");
assert!(!section(&mut app).iter().any(|(t, _)| t == "Proximity Guides"));
}
+12
View File
@@ -3101,6 +3101,18 @@ Guides change where a snapped point lands:
of their two edges on it) or, over no object, to the left, right, top and bottom edges of the artboard under the
pointer: measured lines labelled in the General unit. Off, Alt-hovering shows none.
The Snap to Glyph options (the desktop app's Character panel menu › Show Snap to Glyph Options; View › Snap to Glyph
is the desktop app's): `view.snapToGlyph.options {baseline?, xHeight?, bounds?, proximity?, angular?, anchors?}` (or
`prefs.get`/`prefs.set` with key `glyphSnap`, a partial object) reads and sets which guides Snap to Glyph gives, all on
by default: the baseline and the x-height of a single line of type, the glyph bounds (each glyph's edges), the
proximity guides at both edges of each character's width, the angular guides (along a glyph's slanted strokes, and
every guide of rotated type, which runs at its angle) and the glyphs' anchor points. `{}` reads them; a key it doesn't
know or a value that isn't a boolean is refused and changes nothing.
```json
{"name":"run_command","arguments":{"command":"view.snapToGlyph.options","params":{"angular":false,"anchors":false}}}
```
With Smart Guides on, a bounding-box handle dragged with the Selection or Free Transform tool (`mods.shift`
proportional, `mods.alt` from the centre) lands on another object's anchor or centre (labelled "anchor" or
"center"), else its corners and side line up with the edges and centres of the other objects and the artboards (a
+3 -1
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Selection, transform & align tools: Align panel menu › Align to Glyph Bounds › Point Text and Area Text align and distribute type by the ink of its glyphs (#4231); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Drawing aids: Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
## Feature submission evidence
@@ -753,6 +753,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
A bounding-box handle dragged with the Selection or Free Transform tool snaps what it moves as a dragged point snaps (#2054). With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. With Smart Guides off and Snap to Point on, it snaps to the other objects' anchors and the ruler guides within the Snap to Point distance. With Smart Guides and Snap to Point both on, it lines up with the ruler guides as well as the other objects. Shift still keeps the proportions, and Alt still scales about the center.
A user slice's edge or corner handle dragged with the Slice Selection tool snaps as the Slice tool's drag does. With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. Otherwise, with Smart Guides on, it lands on or lines up with the anchors, centers and edges of the art and the artboards, and with Smart Guides off and Snap to Point on, it snaps to the art's anchors and the ruler guides within the Snap to Point distance. Then a ruler guide within 5 px, shown and unlocked, moves the side or corner onto it. Shift still keeps the proportions, and Alt still resizes about the center.
View › Snap to Glyph: with Smart Guides on, a point drawn near a single line of live type lands on its glyphs' anchor points and lines up with its baseline, its x-height and the edges of each character's advance width (proximity guides), as well as the glyph bounds. In rotated type these guides run at the type's angle, and type of one glyph also has angular guides along its slanted straight segments. A paragraph shows glyph bounds only.
Snap to Glyph options (#4232, [Snap to Glyph options](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/snap-to-glyph-options.html)): the Character panel menu's Show Snap to Glyph Options (Hide Snap to Glyph Options) shows a Snap to Glyph section in the panel, with or without type selected, with a checkbox for each kind of guide: Baseline, x-height, Glyph Bounds, Proximity Guides (the edges of each character's width), Angular Guides and Anchor Point. Each off takes its guides away and leaves the others (`view.snapToGlyph.options`, the `glyphSnap` preference group, all on by default). Angular Guides covers a glyph's slanted strokes and every guide of rotated type, which runs at its angle; the boxes are dimmed while View › Snap to Glyph is off (VectorCraft choices; the page settles neither). Tested in `crates/tools/src/guides/tests.rs`, `crates/ui-egui/src/panels/character/tests.rs` and `crates/engine/src/tests/prefs.rs`.
- **Artboard click-to-fit (#1227):** a plain click fits the topmost editable object or group to a new artboard, including its stroke like Fit to Selected Art, and makes the new artboard active. Art and its selection stay unchanged; creation is one undo step. Handles, modified clicks, drags, Escape and tool changes retain their meaning. Tested inside an artboard and on the pasteboard (`crates/engine/tests/it/artboard_click.rs`); the public [CS6 Printing Guide](https://helpx.adobe.com/content/dam/help/en/indesign/pdf/CS6_PrintGuide.pdf) documents the object-click gesture. This legacy gesture adds evidence without regrading the 2026 canvas controls.
- **Rotated views:** with the view rotated, Fit Artboard in Window, Fit All in Window and the status bar's artboard navigator fit the artboard or the art as the view turns it, and a Zoom tool marquee (Animated Zoom or GPU Performance off) zooms to the area dragged across. Ruler guides on no artboard turn with the view. The canvas draws the selection marquee where the tools select, Show Hidden Characters draws the marks of type partly inside the canvas, the Contextual Task Bar stays under the selection, and the Navigator's red box outlines what the window shows.
- **Views:** the status bar's artboard navigator (first, previous, next, last; `view.goToArtboard`) goes to an artboard, fits it and synchronizes the Artboard tool selection when that tool is active; ordinary object selection is preserved. Actual Next-button regressions cover keyboard/Properties deletion and Undo in `chrome.rs`. Fit Artboard in Window and Actual Size show the navigator's artboard. The Artboards panel highlights that active artboard: clicking a row makes it active (the Artboard tool's too, while it is chosen) as does clicking an artboard with the Artboard tool, double-clicking its number also fits it in the window and double-clicking its name renames it. Duplicate Artboards, or a row dragged onto New Artboard, copies the artboard with its art (#446). Escape leaves Presentation Mode, `view.screenMode` from it goes back to Normal Screen Mode, and the screen mode isn't saved with the preferences: the app always starts in Normal Screen Mode with its menus and panels (#472). View › Pixel Preview shows the art as the document pixels it rasterizes to, one per point and hard-edged when magnified, turning with a rotated view (it used to draw the smooth vectors, with only the pixel grid over them). In Outline view a template layer keeps drawing in Preview, dimmed, so a picture placed to be traced stays visible under the outlines (#1505, fixes #1486).
@@ -1202,6 +1203,7 @@ applies to fills). Tests: `color::gradient::tests` (perceptual midpoint, expansi
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | Drawing aids: Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); evidence added; score and hours unchanged (estimated). |
| 2026-10-30 | minor | Selection, transform & align tools: Align panel menu › Align to Glyph Bounds › Point Text and Area Text align and distribute type by the ink of its glyphs (#4231); evidence added; score and hours unchanged (estimated). |
| 2026-10-30 | minor | Blob Brush and Eraser Tool Options: Size follows the pen pressure, Fixed or Pressure with a Variation (#4590, #3961); scores and estimates unchanged |
| 2026-10-30 | minor | Blob Brush Tool Options: Size follows the pen pressure, Fixed or Pressure with a Variation (#4590); scores and estimates unchanged |
+3 -2
View File
@@ -1,6 +1,6 @@
# Type parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (copying text in Type tool retains formatted runs across documents and on canvas paste (#4459) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); 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
@@ -50,7 +50,7 @@ against Illustrator's public documentation; **estimated** unless noted.
| Caret: the arrows and Delete step over whole clusters (Thai consonant with its marks, combining accents, emoji); Backspace removes one character | done | in text with right-to-left characters the arrows move in visual order and stop on both sides of a character drawn with no width, such as a zero width non-joiner |
| Touch Type tool | done | moves, scales and rotates one character through its kerning, baseline shift, scales and rotation (`text.touchType`); the first character of a line and right-to-left characters move only up and down |
| Retype (identify fonts in outlines/images) | missing | Illustrator's uses a cloud model |
| Snap to Glyph | done | View › Snap to Glyph (`view.snapToGlyph`): with Smart Guides on, new point type and drawing tools snap to live-text baselines and glyph bounds (`Targets::for_drawing`, `tools::text_tests::snap_to_glyph_lines_up_new_point_type_with_a_baseline`) |
| Snap to Glyph | done | View › Snap to Glyph (`view.snapToGlyph`): with Smart Guides on, new point type and drawing tools snap to live-text baselines and glyph bounds (`Targets::for_drawing`, `tools::text_tests::snap_to_glyph_lines_up_new_point_type_with_a_baseline`). Character panel menu › Show Snap to Glyph Options: Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides and Anchor Point each turn their guides on or off (`view.snapToGlyph.options`, all on by default; #4232) |
| Thai, Lao, Khmer, Myanmar: shaping (marks, reordering), lines broken between words, word-by-word caret and double-click | done | words found by ICU4X's line segmenter: dictionaries on the desktop, LSTM models on the web |
| Middle Eastern: bidirectional layout, Paragraph Direction | done | |
| Middle Eastern: digit types (Character panel menu › Digits: Default, Arabic, Hindi, Farsi) | done | `text.setFormat` / `text.setRangeStyle {digits}`; the text keeps the typed digits, the glyphs and SVG export follow the style |
@@ -71,6 +71,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); scores unchanged |
| 2026-10-29 | minor | Font fallback keeps a letter and its combining marks in one face; Vietnamese stacked diacritics (Ể, Ứ, Ự) attach in precomposed, decomposed and keyboard forms (#4491); scores and hours unchanged |
| 2026-10-25 | minor | Threading ports: a double-click on a threaded port with the Selection tool breaks the thread there (`text.thread.break`); the story flows into the frames before the break; scores unchanged |
| 2026-10-24 | minor | Ruby: Type › Create Outlines, EPS, EMF/WMF, legacy `.ai` and type that effects, Expand Appearance and flattening outline keep the reading's glyphs where the canvas draws them (they were dropped), stacked with their run as the canvas paints it (Create Outlines puts each run's reading right after that run's glyphs, below later runs); legacy `.ai` writes type with ruby as outlines; scores unchanged |
+3 -2
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Align panel menu › Align to Glyph Bounds › Point Text and Area Text align and distribute type by the ink of its glyphs (#4231); scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -150,7 +150,7 @@ tools, never checked side by side) isn't captured row by row.
| Distance Guides: Alt/Option over another object, or an empty part of an artboard, shows the selection's distances to it (Smart Guides › Invoke Distance Guides) | done | Selection tool hover with Smart Guides on: the gap between facing edges, else the offsets of matching edges, to another object; the left, right, top and bottom distances to the artboard; labelled in the General unit. The selection is measured as a whole. Nothing while moving or nudging, which the public pages don't describe. [Use Distance Guides](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-distance-guides-for-accurate-placement.html), [Smart Guides options](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/smart-guides-options.html) |
| Snapping tolerance preference | done | |
| Snap to Point, Snap to Grid, Snap to Pixel; Pixel Preview | done | #1840: with Snap to Grid on, anchors and objects snap to the grid even when Snap to Pixel is also ticked (#1816 waits for the reporter) |
| Snap to Glyph | done | View › Snap to Glyph (`view.snapToGlyph`); new type and drawing snap to live-text baselines and glyph bounds when Smart Guides are on (#3124) |
| Snap to Glyph | done | View › Snap to Glyph (`view.snapToGlyph`); new type and drawing snap to live-text baselines and glyph bounds when Smart Guides are on (#3124). Character panel menu › Show Snap to Glyph Options: a checkbox for each guide (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point), dimmed while Snap to Glyph is off (#4232) |
| Tangent and perpendicular snapping for line endpoints (new in 30.0) | partial | #2530: tangent snapping to curves and perpendicular snapping to straight segments for drawn lines; saved Preferences › Smart Guides › Geometric Guides controls, on by default. Other tool/drag cases remain. [Public source](https://helpx.adobe.com/illustrator/desktop/measure-and-align/plot-and-measure/snap-to-perpendicular.html) |
| Snapping panel/popover consolidating the snap options (30.0) | partial | Control bar › Snapping provides quick access through existing view commands and prefs.set; no View › Snapping submenu. Floating-panel parity remains unverified. |
| Customisable rotation snap angle (#973) | missing | Illustrator: Constrain Angle and construction angles only |
@@ -361,6 +361,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); scores and estimates unchanged |
| 2026-10-30 | minor | Align panel menu › Align to Glyph Bounds › Point Text and Area Text align and distribute type by the ink of its glyphs (#4231); the Character and Properties panels' controls for it are not done; scores and estimates unchanged |
| 2026-10-30 | minor | Blob Brush and Eraser Tool Options: Size follows the pen pressure, Fixed or Pressure with a Variation (#4590, #3961); scores and estimates unchanged |
| 2026-10-30 | minor | Blob Brush Tool Options: Size follows the pen pressure, Fixed or Pressure with a Variation (#4590); scores and estimates unchanged |