Merge pull request #3499 from dotCooCoo/fix/path-create-refuses-closed-given-as-text-with-d

Commands: path.create with path data refuses closed given as text, and the grids refuse a count
This commit is contained in:
BFlat
2026-10-22 08:40:43 +07:00
committed by GitHub
5 changed files with 34 additions and 8 deletions
+7 -3
View File
@@ -428,9 +428,11 @@ fn grid_group(s: &mut Session, label: &str, paths: Vec<PathData>, fill: bool) ->
Ok(json!({ "id": id.0 }))
}
/// A grid's divider count `count` with its skew `key` in percent (0 when absent, clamped to
/// ±[`shapes::GRID_SKEW_MAX`]).
/// A grid's divider count `count` (an error unless it is a number or null; 5 unless it is a JSON
/// integer of 0 or more, so 4.0 and 2.5 give 5; at most 999) with its skew `key` in percent (0 when
/// absent, clamped to ±[`shapes::GRID_SKEW_MAX`]).
fn dividers(p: &Value, count: &str, key: &str, cmd: &str) -> Result<(u32, f64)> {
check_numbers(p, &[count], cmd)?;
let n = p.get(count).and_then(Value::as_u64).unwrap_or(5).min(999) as u32;
let skew = match p.get(key) {
None | Some(Value::Null) => 0.0,
@@ -488,6 +490,8 @@ pub(crate) fn anchor_from_json(v: &Value, cmd: &str) -> Result<Anchor> {
}
fn path_create(s: &mut Session, p: &Value) -> Result<Value> {
// Checked with `d` too, where the path data alone decides whether the path is closed.
let closed = switch_or(p, "closed", false, "path.create")?;
let path = if let Some(d) = str_param(p, "d") {
let bp = vectorcraft_geom::BezPath::from_svg(d).map_err(|e| bad("path.create", format!("bad path data: {e}")))?;
PathData::from_bezpath(&bp)
@@ -502,7 +506,7 @@ fn path_create(s: &mut Session, p: &Value) -> Result<Value> {
if anchors.is_empty() {
return Err(bad("path.create", "need at least one anchor"));
}
PathData::single(SubPath::new(anchors, switch_or(p, "closed", false, "path.create")?))
PathData::single(SubPath::new(anchors, closed))
};
let mut look = s.new_art_look(s.paint.fill.clone(), s.paint.stroke.clone(), s.paint.stroke_width);
// Open paths drawn with a stroke of None would be invisible: mimic Illustrator and keep what the user set.
+17
View File
@@ -1257,6 +1257,23 @@ fn shape_commands_refuse_a_number_or_boolean_given_as_text() {
s.execute("shape.arc", &json!({"x1": 0, "y1": 0, "x2": 50, "y2": 50, "closed": false})).unwrap();
}
/// `path.create` given path data (`d`) refuses a `closed` other than true, false or null, as it does
/// with `anchors`, and draws nothing; with true, false or null it draws the path data, which alone
/// decides whether the path is closed.
#[test]
fn path_create_with_path_data_refuses_closed_given_as_text() {
let mut s = session();
let d = "M0 0 L10 0 L10 10";
let calls = [json!("true"), json!(1), json!([true])].map(|v| not_a_switch("path.create", json!({"d": d, "closed": v}), "closed"));
let wrong = not_refused(&mut s, &calls);
assert!(wrong.is_empty(), "not refused: {wrong:#?}");
for closed in [json!(true), json!(false), json!(null)] {
let id = NodeId(s.execute("path.create", &json!({"d": d, "closed": closed})).unwrap()["id"].as_u64().unwrap());
let NodeKind::Path { path, .. } = &s.doc().unwrap().doc.node(id).unwrap().kind else { panic!("a path") };
assert_eq!((path.subpaths.len(), path.anchor_count(), path.is_closed()), (1, 3, false), "closed: {closed}");
}
}
/// #1824: with anchors selected with Direct Selection, the Transform panel's X/Y/W/H
/// (`object.setBounds`) are the box around those anchors, and only they move or scale.
#[test]
+6 -2
View File
@@ -818,13 +818,17 @@ fn polar_grid_options_compound_fill_and_skew() {
assert!(tips(&inward)[1..].iter().all(|t| t.x < 50.0), "counterclockwise of the top: {:?}", tips(&inward));
}
/// The grid options are checked: a skew is a number and a switch true or false, or the command
/// fails without an undo step.
/// The grid options are checked: a divider count or a skew is a number and a switch true or false,
/// or the command fails without an undo step.
#[test]
fn grid_commands_reject_options_of_the_wrong_type() {
let mut s = session();
let size = json!({"x": 0, "y": 0, "width": 10, "height": 10});
for (cmd, key, value) in [
("shape.rectangularGrid", "rows", json!("3")),
("shape.rectangularGrid", "columns", json!([4])),
("shape.polarGrid", "concentric", json!(true)),
("shape.polarGrid", "radial", json!("6")),
("shape.rectangularGrid", "rowSkew", json!("50")),
("shape.rectangularGrid", "columnSkew", json!(true)),
("shape.rectangularGrid", "frame", json!(1)),
+3 -2
View File
@@ -1010,8 +1010,9 @@ These commands refuse a string or another value that is not a number with `` `ra
naming the key, and change nothing: `radius` in `shape.rectangle`; `sides` and `rotation` in `shape.polygon`;
`radius2`, `points` and `rotation` in `shape.star`; `diameter`, `opacity`, `brightness`, `growth`, `fuzziness`,
`rays`, `longest`, `rayFuzziness`, `rings`, `largest`, `x2`, `y2`, `direction`, `pathLength` and `seed` in
`shape.flare`; `decay` and `segments` in `shape.spiral`; `size` and the `width`, `height` and `fitMinPercent` of
`area` in `text.create`; `size` and `fitMinPercent` in `text.createInPath`; `start`, `end`, `size`, `tracking`,
`shape.flare`; `decay` and `segments` in `shape.spiral`; `rows` and `columns` in `shape.rectangularGrid`; `concentric`
and `radial` in `shape.polarGrid`; `size` and the `width`, `height` and `fitMinPercent` of `area` in `text.create`;
`size` and `fitMinPercent` in `text.createInPath`; `start`, `end`, `size`, `tracking`,
`baselineShift`, `hScale`, `vScale`, `rotation` and `strokeWidth` in `text.setRangeStyle`; `start` and `end` in
`text.editRange` and `text.getRange`; `start`, `hScale`, `vScale` and `rotation` in `text.touchType`; `by`, `start` and
`end` in `type.step`; `by` in `type.size.increase` and `type.size.decrease`; `size` in `type.ruby`; `width`, `height`
+1 -1
View File
@@ -774,7 +774,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
Artboard Options (Move Artwork, Scale Artwork, Scale Strokes & Effects, Scale Corners, Transform Patterns, locked and hidden art and the lock), Move Artboard (Move Artwork, the copy and Transform Patterns), Duplicate Artboards (Transform Patterns) and Duplicate, Copy and Cut Artboard (the art) refuse a value other than true, false or null, such as "true", and Artboard Options and Move Artboard refuse a position, size or move given as text, such as "10"; each leaves the document, its undo history and the clipboard as they were.
Change Case, Smart Punctuation, Find and Replace, Convert To Area Type, Convert To Point Type, Type on a Path Options, the Type on a Path effects and Fill with Placeholder Text record no undo step when they change nothing.
The commands that take `keepModel` (fills and strokes, Appearance rows, Live Paint, Shape Builder fills, gradient stops and freeform points, gradient mesh points, type colors, the Symbol Stainer and the swatch commands) refuse a `keepModel` other than true, false or null, such as "true". Toggle Fill/Stroke Focus (`fill`), Invert, Complement, Apply Last Color and Apply Last Gradient (`stroke`), `paint.setFill` and `paint.setStroke` (`focus`, `none`), and `appearance.setItem`, `livePaint.fill`, `livePaint.strokeEdge`, a `shapeBuilder.merge` `fill`, `swatch.new` and `swatch.edit`'s `paint` (`none`) refuse such a value too. Each leaves the document, its undo history and the defaults for new art as they were.
The shape tools' commands (Rectangle, Polygon, Star, Flare, Spiral and Arc), `path.create`, the type commands (new type, type in a path, character formats, the byte range a text command takes, Touch Type, font size steps, `text.getRange`, Optical Margin Alignment, System Layout, Fractional Widths, Ruby and Area Type Options), the symbol commands (Place, Delete, the Symbol Sprayer and the symbolism tools), the swatch commands, the Appearance panel's commands, the gradient and freeform gradient commands, Shape Builder, Live Paint and Image Trace refuse an option other than true, false or null and a number given as text, such as "true" or "40", and leave the document and its undo history as they were.
The shape tools' commands (Rectangle, Polygon, Star, Flare, Spiral, Arc, Rectangular Grid and Polar Grid), `path.create` (with `anchors` or `d`), the type commands (new type, type in a path, character formats, the byte range a text command takes, Touch Type, font size steps, `text.getRange`, Optical Margin Alignment, System Layout, Fractional Widths, Ruby and Area Type Options), the symbol commands (Place, Delete, the Symbol Sprayer and the symbolism tools), the swatch commands, the Appearance panel's commands, the gradient and freeform gradient commands, Shape Builder, Live Paint and Image Trace refuse an option other than true, false or null and a number given as text, such as "true" or "40", and leave the document and its undo history as they were.
- **Fonts:** a font is found by any of its names (family and style in every language of its name table, legacy family/style pairs, PostScript name), so documents from other apps open with the font they name; Find Font and `text.fonts` say for each font whether it was found exactly, by another name, substituted or missing, and how many glyphs are missing. PDF and `.ai` import brings Japanese text back as live type: CID fonts without a ToUnicode map read through the standard CMaps, vertical columns (WMode 1) as vertical type, letter spacing as tracking; a glyph is live type only when the installed font (in the closest style of its family) draws the same glyph, and text in a font that isn't installed comes in as live type shown in the fallback font until the font is installed (exports that draw it — PDF, EPS, EMF/WMF, raster, SVG outlines or embedded fonts — warn that they wrote the fallback font, and Print and Print Preview warn about the type that prints in it; `textAs: outlines` keeps the PDF's embedded glyphs). Two installed versions of one family and style (a font library keeping an old file beside a new one) both load, the first staying the one the family and style resolve to; PDF and `.ai` import tries each version when the glyphs differ, and type set in a version other than that one names it (`CharStyle::font_version`), which layout, the substituted-glyph highlight, Find Font, Package and Document Info follow. Fonts outside the font folders are listed too: those font services such as Adobe Fonts load on Windows, through DirectWrite, and those apps and font managers register with CoreText on macOS (#579). When a document opens with fonts that aren't available, fonts used only in symbols included, a Missing Fonts dialog lists them after the linked-file questions, with Find Fonts (Type › Find Font on them) and Close. Its Find in Folder… (in Find Font too) searches a folder on several threads, reads only what the font scan reads of font files and skips app packages, the Program Files and ProgramData folders, the system's folders and the app data folders in a home folder; Add Fonts copies the font files the user chooses, suitcase fonts with their resource fork, into VectorCraft's own Fonts folder, which the desktop app and `vectorcraft-cli` read (`text.missingFonts`, `text.findFontFiles`, `text.addFontFiles`). The font scan on Linux also reads the folders fontconfig's configuration lists, such as TeX fonts under `/usr/share/texmf/fonts` (#1548, fixes #1529; not verified on Linux).
A variable font's type gets one slider and field per variation axis under the Character panel's Variable Font button, each named as the font names the axis (Weight, Width), so the type can be set between or past its named instances: the settings stay with the characters (`CharStyle::font_axes`, `text.setFormat {fontAxes}` and `text.setRangeStyle {fontAxes}`, clamped to each axis's range; `text.fontList {family, style}` lists the axes), and layout, outlines, the substituted-glyph highlight and PDF and SVG export (`font-variation-settings`, embedded fonts) use the font instanced at them. Another font or style clears them; legacy `.ai` export writes the style and warns that they are left out.
The font database looks up a name that is neither a loaded nor an installed family among the other names of the loaded and installed fonts once for each style it is asked in, and keeps the answer until fonts are added or rescanned. Laying out a story of 1,000 paragraphs set in a font that is not installed, with 400 more fonts loaded, takes a median of 56 to 71 ms instead of 409 to 507 ms on Windows 11 (`CRAFT_FONTS_DIR=<craft-fonts checkout> cargo test --release -p vectorcraft-text --lib missing_font_story_layout_timing -- --ignored --nocapture`).