Part of #394: Appearance of Black › On Screen shows pure CMYK black as RGB black

Preferences › Appearance of Black › On Screen was stored and never read.
Display All Blacks Accurately (the default) shows pure CMYK black, black
ink alone at 100%, through the working CMYK profile, a dark grey, as the
canvas always did. Display All Blacks as Rich Black now shows it on the
canvas as RGB black: fills, strokes, type, gradient stops, effect colours
and pattern tiles, and in CMYK documents drawn by ink planes a pixel
whose inks come out as black ink alone. Rich blacks, black tints and
placed images show the same either way; exports and printing are
unchanged (Printing / Exporting is a separate option).

The renderer gets RenderOptions::rich_black, painted with a new
Ink::RichBlack (screen colours with pure black as RGB black) and applied
by ink::compose. The canvas sets it from blackOnScreen and keeps it in
its cache key, so changing the preference redraws.

A headless frame test reads the canvas raster: K100 shows its profile
grey by default, RGB black with rich, a rich black unchanged, and grey
again with accurate. A render test covers the opaque and ink-plane paths
and checks that rich blacks, tints and images don't change.

Docs: docs/mcp.md (blackOnScreen), docs/target-app-parity.md,
docs/gaps.md, docs/ui-parity.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Aaron GuoandClaude Opus 5.5 authored and bflatastic committed 2026-10-12 14:50:00 +07:00
1 parent ae061eacfb
commit b29e271b4a
11 files changed
+122 -21

No files matched your search

+2 -2
View File
@@ -684,8 +684,8 @@ impl Renderer {
let channels = match f.ink {
Ink::Cmy => effects::pixel::Channels::CmyPlane,
Ink::K => effects::pixel::Channels::KPlane,
Ink::Display if f.doc.color_mode == vectorcraft_doc::ColorMode::Cmyk => effects::pixel::Channels::Cmyk,
Ink::Display => effects::pixel::Channels::Rgb,
Ink::Display | Ink::RichBlack if f.doc.color_mode == vectorcraft_doc::ColorMode::Cmyk => effects::pixel::Channels::Cmyk,
Ink::Display | Ink::RichBlack => effects::pixel::Channels::Rgb,
};
// De-Interlace's field lines: the rows of the document's raster grid (1 to 2400 ppi).
let ppi = Some(f.doc.raster_effects_ppi).filter(|v| v.is_finite()).unwrap_or(72.0).clamp(1.0, 2400.0);
+19 -9
View File
@@ -40,13 +40,16 @@ pub(crate) enum Ink {
Cmy,
/// The complemented K ink, as a grey.
K,
/// Screen colours, with pure CMYK black as RGB black ([`RenderOptions::rich_black`]).
RichBlack,
}
impl Ink {
/// The three values painted for colour `c`.
pub(crate) fn rgb(self, c: &Color) -> [f32; 3] {
match self {
Ink::Display => c.to_rgb(),
match (self, *c) {
(Ink::RichBlack, Color::Cmyk { c, m, y, k }) if is_pure_black([c, m, y, k]) => [0.0; 3],
(Ink::Display | Ink::RichBlack, _) => c.to_rgb(),
_ => self.plane(inks(c)),
}
}
@@ -65,7 +68,7 @@ impl Ink {
fn fixed8(self, [r, g, b, a]: [u8; 4]) -> [u8; 4] {
match self {
Ink::Display => [r, g, b, a],
Ink::Display | Ink::RichBlack => [r, g, b, a],
_ => {
let lab = lab::srgb_to_lab([r, g, b].map(|v| v as f32 / 255.0));
let [r, g, b] = self.plane(inks(&Color::Lab { l: lab.l, a: lab.a, b: lab.b })).map(q);
@@ -83,6 +86,11 @@ fn q(v: f32) -> u8 {
(v.clamp(0.0, 1.0) * 255.0).round() as u8
}
/// Black ink alone at 100%: what Appearance of Black calls pure CMYK black.
fn is_pure_black([c, m, y, k]: [f32; 4]) -> bool {
c <= 0.0 && m <= 0.0 && y <= 0.0 && k >= 1.0
}
/// Whether `doc` is drawn by ink planes: a CMYK document that shows transparency, unless drawn
/// in Outline view or Separations Preview (which paints plates).
pub(crate) fn blends_in_cmyk(doc: &Document, opts: &RenderOptions) -> bool {
@@ -168,11 +176,12 @@ fn cached<T>(slot: &Cached<T>, build: impl FnOnce(&Cms) -> T) -> (Arc<T>, u64) {
}
/// The picture of a CMYK document from its ink planes (premultiplied RGBA8 drawn with
/// [`Ink::Cmy`] and [`Ink::K`]): each pixel's inks shown through the working CMYK profile. Where
/// only the frame's `background` shows it keeps its exact screen colour.
pub(crate) fn compose(cmy: &[u8], k: &[u8], background: Option<[u8; 4]>) -> Vec<u8> {
/// [`Ink::Cmy`] and [`Ink::K`]): each pixel's inks shown through the working CMYK profile (pure
/// black as RGB black with [`RenderOptions::rich_black`]). Where only the `background` of `opts`
/// shows it keeps its exact screen colour.
pub(crate) fn compose(cmy: &[u8], k: &[u8], opts: &RenderOptions) -> Vec<u8> {
let (lut, _) = cached(&DISPLAY, InkLut::build);
let bg = background.map(|b| (Ink::Cmy.fixed8(b), Ink::K.fixed8(b), b));
let bg = opts.background.map(|b| (Ink::Cmy.fixed8(b), Ink::K.fixed8(b), b));
let mut out = Vec::with_capacity(cmy.len());
// Runs of one colour convert once.
let mut last: Option<([u8; 8], [u8; 4])> = None;
@@ -185,7 +194,8 @@ pub(crate) fn compose(cmy: &[u8], k: &[u8], background: Option<[u8; 4]>) -> Vec<
_ => {
let un = |v: u8, a: u8| if a == 0 { 1.0 } else { (v as f32 / a as f32).min(1.0) };
let a = p[3];
let rgb = lut.apply(planes_cmyk([un(p[0], a), un(p[1], a), un(p[2], a)], un(q[0], q[3])));
let inks = planes_cmyk([un(p[0], a), un(p[1], a), un(p[2], a)], un(q[0], q[3]));
let rgb = if opts.rich_black && is_pure_black(inks) { [0.0; 3] } else { lut.apply(inks) };
let pm = |v: f32| (v.clamp(0.0, 1.0) * a as f32).round() as u8;
let v = [pm(rgb[0]), pm(rgb[1]), pm(rgb[2]), a];
last = Some((key, v));
@@ -245,7 +255,7 @@ pub(crate) fn cmyk_pixmap(blob: &ImageBlob, lut: &InkLut) -> Option<Pixmap> {
/// Image pixels `pm` (cached as `key`, from `blob` when it is a document's image as it is) as
/// painted with `ink`, cached with the images, and their key.
pub(crate) fn ink_image(key: &ImageKey, pm: &Arc<Pixmap>, ink: Ink, blob: Option<&ImageBlob>, touch: bool) -> (ImageKey, Arc<Pixmap>) {
if ink == Ink::Display {
if matches!(ink, Ink::Display | Ink::RichBlack) {
return (key.clone(), pm.clone());
}
let (luts, generation) = cached(&SEPARATE, |cms| {
+7 -2
View File
@@ -127,6 +127,9 @@ pub struct RenderOptions {
/// (box-filtered from the full image, kept with the decoded images), so the full image needn't
/// stay in memory or be sampled. Off, images always sample their full pixels (exports).
pub image_mips: bool,
/// Appearance of Black's Rich Black: pure CMYK black (black ink alone at 100%) shows as RGB
/// black. Off, it shows through the CMYK profile, a dark grey, as other colours do.
pub rich_black: bool,
}
/// The opacity of the art around an isolated group or layer.
@@ -201,6 +204,7 @@ impl Default for RenderOptions {
precise: false,
supersample: false,
image_mips: false,
rich_black: false,
}
}
}
@@ -549,7 +553,8 @@ impl Renderer {
let inv = view.inverse();
let visible = inv.transform_rect_bbox(Rect::new(0.0, 0.0, w as f64, h as f64));
let px = 1.0 / view.determinant().abs().sqrt().max(1e-12);
let frame = Frame { mt: threads > 0, doc, view, visible, px, opts, ink: Ink::Display };
let screen = if opts.rich_black { Ink::RichBlack } else { Ink::Display };
let frame = Frame { mt: threads > 0, doc, view, visible, px, opts, ink: screen };
self.begin_frame();
// View Opacity Mask: the mask's coverage in greyscale instead of the artwork.
if let Some(m) = opts.mask_view.and_then(|id| doc.node(id)).and_then(|n| n.extras().mask.as_deref()) {
@@ -564,7 +569,7 @@ impl Renderer {
let stats = self.stats;
let k = self.draw_frame(&Frame { ink: Ink::K, ..frame }, w, h);
self.stats = stats;
if inks { ink::amounts(&cmy, &k) } else { ink::compose(&cmy, &k, opts.background) }
if inks { ink::amounts(&cmy, &k) } else { ink::compose(&cmy, &k, opts) }
} else {
self.draw_frame(&frame, w, h)
};
+1 -1
View File
@@ -38,7 +38,7 @@ struct TileEntry {
struct PatternCache {
/// Per ink ([`Ink`] as index), each within [`lru::PATTERN_BUDGET`], least recently used first out.
tiles: [FrameCache<String, TileEntry>; 3],
tiles: [FrameCache<String, TileEntry>; 4],
renderer: Option<Box<Renderer>>,
depth: u32,
}
+31
View File
@@ -131,3 +131,34 @@ fn placed_images_are_separated_not_blackened() {
let [r, g, b] = cms.cmyk_to_srgb(cms.srgb_to_cmyk([1.0, 0.0, 0.0], cms.settings().intent), false).map(|v| (v * 255.0).round() as u8);
assert!(near(got, [r, g, b, 255], 4), "{got:?} ≠ {:?}", [r, g, b]);
}
/// Appearance of Black's Rich Black (`rich_black`, #394): pure CMYK black (black ink alone at
/// 100%) shows as RGB black, in an opaque CMYK document (screen colours) and in one drawn by ink
/// planes (Multiply); a rich black, a black tint and placed images show as they do without it.
#[test]
fn rich_black_shows_black_ink_alone_as_rgb_black() {
let k = |k| Color::cmyk(0.0, 0.0, 0.0, k);
let centre_with = |d: &Document, rich_black| {
let opts = RenderOptions { background: Some([255; 4]), skip_templates: true, rich_black, ..Default::default() };
Renderer::new().render_region_with(d, d.artboards[0].rect, 1.0, &opts).pixel(10, 10)
};
for blend in [Blend::Normal, Blend::Multiply] {
let black = doc(ColorMode::Cmyk, None, k(1.0), blend, 1.0);
let accurate = centre_with(&black, false);
assert!(near(accurate, shown([0.0, 0.0, 0.0, 1.0]), 1) && accurate[0] > 10, "{blend:?}: accurate {accurate:?}");
assert_eq!(centre_with(&black, true), [0, 0, 0, 255], "{blend:?}: rich");
for c in [Color::cmyk(0.6, 0.4, 0.4, 1.0), k(0.5)] {
let d = doc(ColorMode::Cmyk, None, c, blend, 1.0);
assert_eq!(centre_with(&d, true), centre_with(&d, false), "{blend:?}: {c:?}");
}
}
let mut d = Document::new_with_mode(20.0, 20.0, ColorMode::Cmyk);
let img = image::RgbaImage::from_pixel(20, 20, image::Rgba([255, 0, 0, 255]));
let mut png = vec![];
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
d.images.insert("red".into(), ImageBlob::new("image/png", png));
let im = ImageObject { key: "red".into(), width: 20, height: 20, xf: Affine::IDENTITY, link: None, placement: Default::default() };
let n = Node::new(d.alloc_id(), NodeKind::Image(Box::new(im)));
add(&mut d, n);
assert_eq!(centre_with(&d, true), centre_with(&d, false), "an image shows as it is");
}
+49
View File
@@ -254,6 +254,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
pixel,
smooth_images,
isolated: st.isolation.map(|id| id.0),
// Appearance of Black › On Screen: Display All Blacks as Rich Black.
rich_black: app.session.prefs.black_on_screen == "rich",
};
// Placed documents' bitmaps are being made: draw again when they are ready.
if vectorcraft_render::placed_document::busy() {
@@ -307,6 +309,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
isolated: key.isolated.map(vectorcraft_doc::NodeId),
// Images far smaller on screen than they are sample their mip levels (#1244).
image_mips: true,
rich_black: key.rich_black,
..opts
};
// Light documents render synchronously (no lag vs overlays); heavy ones go to the worker.
@@ -2827,6 +2830,52 @@ mod tests {
assert!(smooth(&mut app), "Pixel Preview, on");
}
/// Appearance of Black › On Screen (#394): Display All Blacks Accurately (the default) shows
/// pure CMYK black through the CMYK profile, a dark grey; Display All Blacks as Rich Black
/// shows it as RGB black. A rich black shows the same either way.
#[test]
fn appearance_of_black_on_screen_shows_pure_black_as_rgb_black() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300, "colorMode": "cmyk"})).unwrap();
app.canvas.worker_started = true; // render on this thread
// Larger than the view: the middle of the art's raster is its fill.
let id = app.run("shape.rectangle", json!({"x": -5000, "y": -5000, "width": 10000, "height": 10000})).unwrap()["id"].clone();
let fill = |app: &mut VectorcraftApp, cmyk: [u8; 4]| {
let [c, m, y, k] = cmyk;
app.run("paint.setFill", json!({"ids": [id], "color": {"c": c, "m": m, "y": y, "k": k}})).unwrap();
};
let ctx = egui::Context::default();
// The middle pixel of the art's raster uploaded by one frame, None when it was not re-rendered.
let middle = |app: &mut VectorcraftApp| -> Option<[u8; 4]> {
let (_, mut delta) = frame_output(app, &ctx);
let art = app.canvas.texture.as_ref().unwrap().id();
let px = delta.set.iter().find(|e| e.0.eq(&art)).and_then(|e| e.1.last()).map(|d| {
let egui::ImageData::Color(img) = &d.image;
let [w, h] = img.size;
img.pixels[h / 2 * w + w / 2].to_array()
});
delta.clear();
px
};
let shown = |cmyk: [f32; 4]| vectorcraft_color::Color::cmyk(cmyk[0], cmyk[1], cmyk[2], cmyk[3]).to_rgba8(1.0);
let near = |a: [u8; 4], b: [u8; 4]| a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1);
fill(&mut app, [0, 0, 0, 100]);
let accurate = middle(&mut app).expect("the first frame renders the art");
assert!(near(accurate, shown([0.0, 0.0, 0.0, 1.0])), "accurate: through the CMYK profile, {accurate:?}");
assert!(accurate[..3].iter().all(|v| *v > 10), "accurate: a dark grey, {accurate:?}");
app.run("prefs.set", json!({"key": "blackOnScreen", "value": "rich"})).unwrap();
assert_eq!(middle(&mut app).expect("the preference change re-renders"), [0, 0, 0, 255], "rich: RGB black");
fill(&mut app, [60, 40, 40, 100]);
let rich = middle(&mut app).expect("re-rendered");
assert!(near(rich, shown([0.6, 0.4, 0.4, 1.0])), "a rich black is not pure black: {rich:?}");
fill(&mut app, [0, 0, 0, 100]);
app.run("prefs.set", json!({"key": "blackOnScreen", "value": "accurate"})).unwrap();
assert!(near(middle(&mut app).expect("re-rendered"), accurate), "accurate again");
}
/// Guides & Grid › Show Pixel Grid (Above 600% Zoom) (#394): in Pixel Preview at 600% zoom and
/// above, a line at every document pixel over the art; none below 600%, out of Pixel Preview,
/// or with the option off.
+3
View File
@@ -310,6 +310,9 @@ pub struct CacheKey {
pub smooth_images: bool,
/// Isolation mode's group or layer ([`vectorcraft_render::RenderOptions::isolated`]).
pub isolated: Option<u64>,
/// Appearance of Black › On Screen › Display All Blacks as Rich Black
/// ([`vectorcraft_render::RenderOptions::rich_black`]).
pub rich_black: bool,
}
#[derive(Clone, Copy, Debug, Default)]
+2 -2
View File
@@ -125,8 +125,8 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
## G12 Preferences and panels that don't act yet
- **Missing:** ~17 Preferences options stored but not acted on (#394: Highlight Alternate Glyphs, Open
Documents As Tabs, Real-time Drawing, hyphenation language, Appearance of Black, devices…), Properties panel per
- **Missing:** ~16 Preferences options stored but not acted on (#394: Highlight Alternate Glyphs, Open
Documents As Tabs, Real-time Drawing, hyphenation language, Appearance of Black for output, devices…), Properties panel per
context, context menus beyond the canvas, Layers panel row menus and Collect for Export, SVG Interactivity.
- **Impact:** settings that silently do nothing erode trust.
- **Estimate:** 13–20 h.
+4 -1
View File
@@ -1615,7 +1615,10 @@ control channel's `ui.drag` scrubs), `showHomeScreen` (on by default: the Home s
off, an empty window, and the Home button or `app.home` still shows the screen), `autoCollapseIconPanels` (off by
default: on, a click away from a panel popped out of the icon column puts it away) and `displayPrintSize` (off by
default: View › Actual Size / `view.actualSize` is one document point per screen point; on, one document inch fills
96 screen points, an inch at the system's reference density whatever the display scaling).
96 screen points, an inch at the system's reference density whatever the display scaling). `blackOnScreen`
(Appearance of Black › On Screen, `accurate` by default) sets how the canvas shows pure CMYK black, black ink alone
at 100%: `accurate` shows it through the CMYK profile, a dark grey, and `rich` shows it as RGB black. Rich blacks,
black tints and images show the same either way, and exports are unchanged.
`gridlineEvery` (72 pt by default) and `gridSubdivisions` (8 by default) set the document grid that View › Show Grid
draws, and View › Snap to Grid snaps to its subdivisions. Changing either sets the grid of every open document.
+3 -3
View File
@@ -87,7 +87,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de
| Export for Screens, Asset Export, slices, Save for Web | 3 | 85% | anti-aliasing ghost borders (#983), TIFF in Export for Screens (30.2) | 2–4 | estimated |
| Print, colour management, separations, flattener | 3 | 80% | print preview fidelity | 2–4 | estimated |
| Automation (Actions, Variables, scripting, batch) | 3 | 60% | a scripting surface (Illustrator: JavaScript/ExtendScript), Batch, image and graph variables, dataset import/export | 8–14 | estimated |
| UI chrome (panels, Properties, workspaces, preferences) | 6 | 80% | Properties per context, ~17 Preferences options stored but unused (#394), panels outside the window, SVG Interactivity, context menus beyond the canvas, accessibility | 13–20 | estimated; macOS save Format menu implemented (#866), score and hours unchanged; Eyedropper cursor and precise setting checked (#1269), OS visual QA unverified |
| UI chrome (panels, Properties, workspaces, preferences) | 6 | 80% | Properties per context, ~16 Preferences options stored but unused (#394), panels outside the window, SVG Interactivity, context menus beyond the canvas, accessibility | 13–20 | estimated; macOS save Format menu implemented (#866), score and hours unchanged; Eyedropper cursor and precise setting checked (#1269), OS visual QA unverified |
| Libraries, Links, Package | 2 | 82% | shared libraries, linked graphics, gradients and patterns as library items (no cloud by design) | 2–3 | estimated |
| Generative AI (Text to Vector, Generative Recolor, Shape Fill, Concept to Vector, Prompt to Edit, Rewrite Text) | 2 | 0% | a pluggable provider and the panels; needs an owner decision on models | 30–60 | measured (absent) |
| **Feature depth** | **105** | **~75%** | | **~250–420** | |
@@ -326,7 +326,7 @@ about 1–1.5 agent-hours each, so the other rows (estimated on the older scale)
| Export for Screens, Asset Export, slices, Save for Web | 3 | 85% | polish only (all four are done) | 2–4 |
| Print, colour management, separations, flattener | 3 | 80% | print preview fidelity (flattener presets in PDF and print, overprint in PDF and composite print, Print dialog, presets, PostScript, marks, separations, Print Tiling, print overprint/flattener/bitmap options and printer profile are done) | 1–2 |
| Automation | 3 | 65% | scripting surface, batch (Variables: text/visibility variables bound to objects, an object may carry several, dataset rows applied in one undo step, panel and Window › Variables with dialogs; image/graph kinds and dataset import/export open) | 6–10 |
| UI chrome (panels, contextual Properties, workspaces, prefs) | 6 | 80% | macOS native menu dismissal fixed (M14.8); Variables, Links, Asset Export, CSS Properties, Attributes and the canvas context menu are done; remaining: SVG Interactivity, Properties per context, Consolidate All Windows, floating panels outside the app window and docked into any group, context menus beyond the canvas, about 17 Preferences options stored but not yet acted on (#394: Highlight Alternate Glyphs, Open Documents As Tabs, Real-time Drawing, hyphenation language, Appearance of Black, devices…) | 13–20 |
| UI chrome (panels, contextual Properties, workspaces, prefs) | 6 | 80% | macOS native menu dismissal fixed (M14.8); Variables, Links, Asset Export, CSS Properties, Attributes and the canvas context menu are done; remaining: SVG Interactivity, Properties per context, Consolidate All Windows, floating panels outside the app window and docked into any group, context menus beyond the canvas, about 16 Preferences options stored but not yet acted on (#394: Highlight Alternate Glyphs, Open Documents As Tabs, Real-time Drawing, hyphenation language, Appearance of Black for output, devices…) | 13–20 |
| Libraries, Links, Package | 2 | 82% | the Libraries panel is local only (no cloud by design: libraries are files on this machine) and has no shared libraries, linked graphics or gradients and patterns as items yet; its graphics, colours and text styles (#745) and its search, groups and library export/import as `.vclibrary` files (#926) are done; Links and Package are done; placed VectorCraft documents are done (linked by default, vectors in every output, previews, Break Link, Expand, Edit Original, refresh on save) | 2–3 |
| **Feature parity** | **103** | **~75%** | | **~210–330** |
| Interaction-fidelity pass (side by side with Illustrator: every tool modifier, cursor, dialog, Properties context) | | | | 60–90 |
@@ -401,7 +401,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Native macOS save sheets offer a localised Format menu (#866), built from the request's existing format filters. It selects the suggested filename's format and updates the suffix when changed, including legacy `.drawcraft` names. `apps/vectorcraft/src/mac_save_format.rs` has filename regressions; an actual native SVG save, selection of PDF and cancellation without changing the saved document were verified.
- Save / Don't Save / Cancel before closing or quitting with unsaved documents (tabs, Close, Close All, Quit, window close).
- Four brightness themes, persistent preferences.
- Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). About 17 stored options still do nothing yet (the rest of #394: Highlight Alternate Glyphs, Identify Objects By, East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Appearance of Black, Touch WorkspLine truncated
- Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RendeLine truncated
- Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423).
- Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once.
- Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels. Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off.
+1 -1
View File
@@ -167,7 +167,7 @@ Layers panel.
| SVG Interactivity, Retype, Comments, Version History panels | missing | the last two are cloud features (out of scope) |
| Workspaces (save, reset, switch) | done | |
| macOS save sheet: choose the format from a menu | done | #866; `apps/vectorcraft/src/mac_save_format.rs`; current suffix selects the initial format, changing it updates the filename; native SVG save and cancel after changing to PDF checked |
| Preferences: every option acts | partial | ~17 stored but unused (#394) |
| Preferences: every option acts | partial | ~16 stored but unused (#394) |
## Look and feel