diff --git a/crates/engine/src/cmd/docinfo.rs b/crates/engine/src/cmd/docinfo.rs index d73322f3a2..54da0562b1 100644 --- a/crates/engine/src/cmd/docinfo.rs +++ b/crates/engine/src/cmd/docinfo.rs @@ -18,7 +18,7 @@ pub fn specs() -> Vec { "Document Info", ["Window", "Document Info"], None, - "{selectionOnly?, category?: document|objects|graphicStyles|brushes|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages|repeats (only that one in sections and the text), format?: \"text\" (→ {text}: the report Document Info › Save… and Package write)} → {document, objects: {paths, compoundPaths, groups, …}, pathLength: pt (the paths' total length, compound paths' included, guides left out), fonts (the type in opacity masks and in the art of symbols and patterns included; with selectionOnly: the selected type's), images, swatches, graphicStyleNames (with selectionOnly: the styles the selected objects are linked to), brushNames (the brushes the art's drawn strokes use, each once; with selectionOnly: those the selected objects' strokes use), brushes: [{name, type: calligraphic|scatter|art|pattern|bristle|null}] (those brushes with their types; null: not in the Brushes panel), panelBrushNames (the Brushes panel's brushes), repeatObjects: [{id, name, type: radial|grid|mirror, instances, rows?, cols?}] (the Repeat objects, those in groups and other repeats included; with selectionOnly: those in the selection), spotObjects: [{id, name, spotColors}] (the objects filled or stroked with a spot color swatch, as a solid color or in the stops of a gradient that color a part of it above opacity 0 (a stop at opacity 0 counts only where it mixes with a stop above opacity 0 over a stretch of some width), at a tint above 0, and grayscale images colorized with one, each once, by the rules patternObjects follows, with the spot colors they use; Registration and process colors are not spot colors; objects.spots counts them), spotColorNames and spotColors (the spot colors those objects use), patternObjects: [{id, name, patterns}] (the objects filled or stroked with a pattern, each once, those in groups and type whose drawn characters use one included, hidden fills and strokes, fills and strokes at an opacity of 0, strokes with no width, guides, compound path members' own fills and strokes, images' fills and strokes without a shape and characters that draw nothing left out, with the patterns they use, which objects.patterns counts; with selectionOnly: those in the selection), patternNames (the patterns those objects use), gradientObjects: [{id, name, gradients}] (the objects filled or stroked with a gradient, by the same rules, with the gradients they use: a linked gradient swatch's name, else Linear gradient, Radial gradient or Freeform gradient; objects.gradients counts them), gradientNames (the gradients those objects use), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its license lets it be embedded)}. An image linked to a file outside the automation read roots has link null, and its Linked Images row names the file alone. brushNames, brushes, spotObjects, patternObjects and gradientObjects leave out hidden objects, objects at an opacity of 0 and the objects inside a hidden group or layer or one at an opacity of 0 (with selectionOnly too), their characters' paints and colorized images included; objects still counts them as paths, groups and the other kinds", + "{selectionOnly?, category?: document|objects|graphicStyles|brushes|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages|repeats (only that one in sections and the text), format?: \"text\" (→ {text}: the report Document Info › Save… and Package write)} → {document: {…, units (English), unitId: the units' Preferences › Units id (points, millimeters, feetInches, …), rasterEffectsPpi}, objects: {paths, compoundPaths, groups, …}, pathLength: pt (the paths' total length, compound paths' included, guides left out), fonts (the type in opacity masks and in the art of symbols and patterns included; with selectionOnly: the selected type's), fontDetails: [{font, match: exact|style|missingWithFallback|missing, family, style, file, embedding: allowed|editing|previewPrint|forbidden|null}] (each font of fonts, in order: how it was found, the face shown (null when none), that face's file name (null for a built-in face) and what its license allows), images, imageObjects: [{name, width, height, linked, file}] (the image objects in the order Linked Images and Embedded Images list them; file: what a linked image's row shows, null for an embedded one), swatches, graphicStyleNames (with selectionOnly: the styles the selected objects are linked to), brushNames (the brushes the art's drawn strokes use, each once; with selectionOnly: those the selected objects' strokes use), brushes: [{name, type: calligraphic|scatter|art|pattern|bristle|null}] (those brushes with their types; null: not in the Brushes panel), panelBrushNames (the Brushes panel's brushes), repeatObjects: [{id, name, type: radial|grid|mirror, instances, rows?, cols?}] (the Repeat objects, those in groups and other repeats included; with selectionOnly: those in the selection), spotObjects: [{id, name, spotColors}] (the objects filled or stroked with a spot color swatch, as a solid color or in the stops of a gradient that color a part of it above opacity 0 (a stop at opacity 0 counts only where it mixes with a stop above opacity 0 over a stretch of some width), at a tint above 0, and grayscale images colorized with one, each once, by the rules patternObjects follows, with the spot colors they use; Registration and process colors are not spot colors; objects.spots counts them), spotColorNames and spotColors (the spot colors those objects use), patternObjects: [{id, name, patterns}] (the objects filled or stroked with a pattern, each once, those in groups and type whose drawn characters use one included, hidden fills and strokes, fills and strokes at an opacity of 0, strokes with no width, guides, compound path members' own fills and strokes, images' fills and strokes without a shape and characters that draw nothing left out, with the patterns they use, which objects.patterns counts; with selectionOnly: those in the selection), patternNames (the patterns those objects use), gradientObjects: [{id, name, gradients, kinds}] (the objects filled or stroked with a gradient, by the same rules, with the gradients they use: a linked gradient swatch's name, else its kind's name, such as Linear gradient; kinds gives each one's kind, linear|radial|conic|diamond|freeform, when it is named by its kind, else null; objects.gradients counts them), gradientNames (the gradients those objects use), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its license lets it be embedded)}. An image linked to a file outside the automation read roots has link null, and its Linked Images row names the file alone. brushNames, brushes, spotObjects, patternObjects and gradientObjects leave out hidden objects, objects at an opacity of 0 and the objects inside a hidden group or layer or one at an opacity of 0 (with selectionOnly too), their characters' paints and colorized images included; objects still counts them as paths, groups and the other kinds", has_doc, info ), @@ -85,17 +85,65 @@ pub struct Section { /// What a font's `fsType` lets a copy of it do, read as exports read it ([`Embedding`]): the /// most permissive usage bit wins, and bitmap-only fonts may not be embedded. pub fn embedding_label(fs_type: u16) -> &'static str { - if Embedding::from_fs_type(fs_type) == Embedding::Forbidden { - "embedding not allowed" - } else if fs_type & 0x0008 != 0 { - "embedding for editing" - } else if fs_type & 0x0004 != 0 { - "embedding for preview and print" - } else { - "embedding allowed" + match embedding_id(fs_type) { + "forbidden" => "embedding not allowed", + "editing" => "embedding for editing", + "previewPrint" => "embedding for preview and print", + _ => "embedding allowed", } } +/// [`embedding_label`] as the `embedding` value of a `fontDetails` entry: `forbidden`, `editing`, +/// `previewPrint` or `allowed`. +pub fn embedding_id(fs_type: u16) -> &'static str { + if Embedding::from_fs_type(fs_type) == Embedding::Forbidden { + "forbidden" + } else if fs_type & 0x0008 != 0 { + "editing" + } else if fs_type & 0x0004 != 0 { + "previewPrint" + } else { + "allowed" + } +} + +/// A font as Font Details lists it: its `fontDetails` entry and its row. The row gives the file +/// the font is drawn from (`built in` for a face with no file) and whether its license lets it be +/// embedded; for a style the family lacks, the face shown in its place; for a font that is not +/// installed, the face shown instead, or `missing`. The entry holds the same facts as values +/// (`match`: `exact`, `style`, `missingWithFallback` or `missing`), so the panel can show them in +/// the interface language. +fn font_detail(used: &super::fonts::UsedFont) -> (Value, (String, String)) { + let db = vectorcraft_text::FontDb::global(); + let (family, style, version) = used; + let label = super::fonts::font_label(used); + // Found by any of its names (in the version the type names); a style the family lacks shows in + // another. + let resolved = db.resolve(family, style).map(|(f, m)| (db.face_version(family, style, version.as_deref()).unwrap_or(f), m)); + let (entry, detail) = match resolved { + Some((f, m)) if m != vectorcraft_text::FontMatch::Missing => { + let file = f.path().and_then(|p| p.file_name()).map(|n| n.to_string_lossy().into_owned()); + let found = format!("{}; {}", file.as_deref().unwrap_or("built in"), embedding_label(f.fs_type())); + let substituted = m == vectorcraft_text::FontMatch::Style; + let entry = json!({ + "font": label, + "match": if substituted { "style" } else { "exact" }, + "family": f.family, + "style": f.style, + "file": file, + "embedding": embedding_id(f.fs_type()), + }); + (entry, if substituted { format!("substituted: shown in {} {} ({found})", f.family, f.style) } else { found }) + } + Some((f, _)) => ( + json!({ "font": label, "match": "missingWithFallback", "family": f.family, "style": f.style, "file": null, "embedding": null }), + format!("missing: shown in {} {}", f.family, f.style), + ), + None => (json!({ "font": label, "match": "missing", "family": null, "style": null, "file": null, "embedding": null }), "missing".into()), + }; + (entry, (label, detail)) +} + /// An image object as Document Info lists it: (name, width, height, linked file). type ImageRow = (String, u32, u32, Option); @@ -131,14 +179,15 @@ fn repeat_entry(d: &Document, n: &Node, spec: &RepeatSpec) -> (Value, (String, S } /// The Document Info categories of `d` (only `category` when given) from `info` (what -/// `document.info` reports), its fonts, its image objects, the rows of the brushes its art uses, the -/// rows of its Repeat objects and the rows of its objects filled or stroked with a spot color, with a -/// pattern and with a gradient. +/// `document.info` reports), its fonts and their Font Details rows, its image objects, the rows of +/// the brushes its art uses, the rows of its Repeat objects and the rows of its objects filled or +/// stroked with a spot color, with a pattern and with a gradient. #[allow(clippy::too_many_arguments)] fn sections( d: &Document, info: &Value, fonts: &BTreeSet, + font_details: &[(String, String)], images: &[ImageRow], brushes: &[(String, String)], repeats: &[(String, String)], @@ -192,34 +241,7 @@ fn sections( "gradients" => gradient_objects.to_vec(), "symbols" => names("symbols"), "fonts" => fonts.iter().map(|f| (super::fonts::font_label(f), String::new())).collect(), - "fontDetails" => { - let db = vectorcraft_text::FontDb::global(); - fonts - .iter() - .map(|used| { - let (family, style, version) = used; - // Found by any of its names (in the version the type names); a style the - // family lacks shows in another. - let resolved = - db.resolve(family, style).map(|(f, m)| (db.face_version(family, style, version.as_deref()).unwrap_or(f), m)); - let detail = match resolved { - Some((f, m)) if m != vectorcraft_text::FontMatch::Missing => { - let file = - f.path().and_then(|p| p.file_name()).map_or_else(|| "built in".into(), |n| n.to_string_lossy().into_owned()); - let found = format!("{file}; {}", embedding_label(f.fs_type())); - if m == vectorcraft_text::FontMatch::Style { - format!("substituted: shown in {} {} ({found})", f.family, f.style) - } else { - found - } - } - Some((f, _)) => format!("missing: shown in {} {}", f.family, f.style), - None => "missing".into(), - }; - (super::fonts::font_label(used), detail) - }) - .collect() - } + "fontDetails" => font_details.to_vec(), "linkedImages" => { images.iter().filter_map(|(name, w, h, link)| Some((name.clone(), format!("{w} × {h} px — {}", link.as_ref()?)))).collect() } @@ -277,6 +299,39 @@ fn gradient_label(p: &Paint) -> Option { } } +/// Where [`gradient_label`]'s name for gradient paint `p` comes from: `swatch:` for a +/// gradient linked to a swatch, else `kind:` (`linear`, `radial`, `conic`, `diamond` or +/// `freeform`). The two prefixes keep a swatch's name apart from a kind's name it reads like. +fn gradient_source(p: &Paint) -> Option { + match p { + Paint::Gradient(g) => Some(match g.swatch.as_deref().filter(|s| !s.is_empty()) { + Some(swatch) => format!("swatch:{swatch}"), + None => format!("kind:{}", g.gradient.kind.label().to_ascii_lowercase()), + }), + _ => None, + } +} + +/// For each of `names` (an object's [`gradient_label`]s), the kind it names when only a kind +/// gives that name: a name one of `sources` (the object's [`gradient_source`]s) gives as a kind's +/// and none gives as a swatch's, else null. A swatch named like a kind keeps its name even where +/// a gradient of that kind is listed under the same name. +fn gradient_kinds(names: &[String], sources: &[String]) -> Vec { + names + .iter() + .map(|name| { + if sources.iter().any(|s| s.strip_prefix("swatch:") == Some(name.as_str())) { + return Value::Null; + } + sources + .iter() + .filter_map(|s| s.strip_prefix("kind:")) + .find(|k| vectorcraft_color::GradientKind::parse(k).is_some_and(|kind| *name == format!("{} gradient", kind.label()))) + .map_or(Value::Null, |k| json!(k)) + }) + .collect() +} + /// The names of `d`'s spot color swatches: the swatches whose prints go to a plate of their own /// ([`vectorcraft_doc::inks::inks`]). A name held by several swatches is the first one's, as /// [`Document::swatch`] finds it, and the Registration swatch prints on every plate, so it is not @@ -486,9 +541,11 @@ fn node_and_drawn<'a>(d: &'a Document, path: &[usize]) -> Option<(&'a Node, bool /// The spot colors, the patterns and the gradients (`spots`: [`spot_swatches`]) that object `n` /// paints, each once: those of its own fills and strokes ([`own_paints`], `members` included), of /// its drawn characters and, for a grayscale or 1-bit image, of the color it is colorized with. -fn object_paints(d: &Document, spots: &HashSet<&str>, members: &HashSet<*const Node>, n: &Node) -> [Vec; 3] { +/// The fourth list gives where each gradient comes from: a swatch or a kind ([`gradient_source`]). +fn object_paints(d: &Document, spots: &HashSet<&str>, members: &HashSet<*const Node>, n: &Node) -> [Vec; 4] { let (spot, pattern, gradient) = (|p: &Paint| spot_labels(spots, p), one_label(pattern_label), one_label(gradient_label)); - let labels: [&PaintNames<'_>; 3] = [&spot, &pattern, &gradient]; + let gradient_from = one_label(gradient_source); + let labels: [&PaintNames<'_>; 4] = [&spot, &pattern, &gradient, &gradient_from]; let painted_runs = painted_runs(d, spots, n); let mut used = labels.map(|label| own_paints(d, n, members, painted_runs.as_ref(), label)); // The characters' own paints: an inline graphic draws no outline and does not take its run's @@ -654,8 +711,10 @@ fn info(s: &mut Session, p: &Value) -> Result { if let Some(k) = k { *counts.entry(k).or_default() += 1; } - // The spot colors, the patterns and the gradients this object paints, each once; none when it does not draw. - let [used_spots, used_patterns, used_gradients] = if drawn { object_paints(d, &spot_swatches, &members, n) } else { Default::default() }; + // The spot colors, the patterns and the gradients this object paints, each once, and where each gradient comes from; + // none when it does not draw. + let [used_spots, used_patterns, used_gradients, used_sources] = + if drawn { object_paints(d, &spot_swatches, &members, n) } else { Default::default() }; // Objects counts the objects that Spot Color Objects, Pattern Objects and Gradient Objects list. if !used_spots.is_empty() { let name = n.display_name(); @@ -675,7 +734,8 @@ fn info(s: &mut Session, p: &Value) -> Result { let name = n.display_name(); gradients.extend(used_gradients.iter().cloned()); gradient_rows.push((name.clone(), used_gradients.join(", "))); - gradient_objects.push(json!({ "id": n.id.0, "name": name, "gradients": used_gradients })); + let kinds = gradient_kinds(&used_gradients, &used_sources); + gradient_objects.push(json!({ "id": n.id.0, "name": name, "gradients": used_gradients, "kinds": kinds })); *counts.entry("gradients").or_default() += 1; } if n.extras().mask.is_some() { @@ -710,6 +770,14 @@ fn info(s: &mut Session, p: &Value) -> Result { fonts.extend(super::fonts::art_texts_of(d).into_iter().flat_map(|(_, t, _)| t.runs.iter().map(|r| super::fonts::font_of(&r.style)))); } let font_names: Vec = fonts.iter().map(super::fonts::font_label).collect(); + let (font_entries, font_rows): (Vec, Vec<(String, String)>) = fonts.iter().map(font_detail).unzip(); + // The image objects in walk order, as Linked Images and Embedded Images list them: `file` is a + // linked image's row value (its path, or its file name alone when the request may not read it), + // and null for an embedded one. + let image_objects: Vec = image_rows + .iter() + .map(|(name, w, h, file)| json!({ "name": name, "width": w, "height": h, "linked": file.is_some(), "file": file })) + .collect(); // Each brush the art uses with its type, found by name in the Brushes panel as the renderer finds it (the first of two // brushes with one name); a brush the panel does not hold draws as a plain stroke and has no type. A row names the type // as the New Brush dialog does (`Art Brush`), and the panel translates these values when it shows them. @@ -727,7 +795,9 @@ fn info(s: &mut Session, p: &Value) -> Result { "objects": counts, "pathLength": path_length, "fonts": font_names, + "fontDetails": font_entries, "images": images, + "imageObjects": image_objects, "symbols": symbols, "spotColors": spot, "spotColorNames": spot, @@ -749,7 +819,7 @@ fn info(s: &mut Session, p: &Value) -> Result { }); // The document's name: its file's, else its title. out["document"]["name"] = json!(st.title()); - let sections = sections(d, &out, &fonts, &image_rows, &brush_rows, &repeat_rows, &spot_rows, &pattern_rows, &gradient_rows, category); + let sections = sections(d, &out, &fonts, &font_rows, &image_rows, &brush_rows, &repeat_rows, &spot_rows, &pattern_rows, &gradient_rows, category); if text { return Ok(json!({ "text": report_text(&st.title(), selection_only, §ions) })); } @@ -763,6 +833,7 @@ fn document_summary(d: &Document) -> Value { "title": d.title, "colorMode": match d.color_mode { ColorMode::Rgb => "RGB", ColorMode::Cmyk => "CMYK" }, "units": d.units.label(), + "unitId": d.units.key(), "artboards": d.artboards.iter().map(|a| json!({ "name": a.name, "width": a.rect.width() / u, "height": a.rect.height() / u })).collect::>(), "rasterEffectsPpi": d.raster_effects_ppi, }) @@ -1289,11 +1360,11 @@ mod tests { assert_eq!( i["gradientObjects"], json!([ - {"id": a, "name": "Both", "gradients": ["Ramp", "Linear gradient"]}, - {"id": b, "name": "", "gradients": ["Radial gradient"]}, - {"id": f, "name": "", "gradients": ["Freeform gradient"]}, - {"id": t, "name": "Type", "gradients": ["Ramp"]}, - {"id": bar, "name": "Bar", "gradients": ["Ramp"]}, + {"id": a, "name": "Both", "gradients": ["Ramp", "Linear gradient"], "kinds": [null, "linear"]}, + {"id": b, "name": "", "gradients": ["Radial gradient"], "kinds": ["radial"]}, + {"id": f, "name": "", "gradients": ["Freeform gradient"], "kinds": ["freeform"]}, + {"id": t, "name": "Type", "gradients": ["Ramp"], "kinds": [null]}, + {"id": bar, "name": "Bar", "gradients": ["Ramp"], "kinds": [null]}, ]) ); assert_eq!(i["gradientNames"], json!(["Freeform gradient", "Linear gradient", "Radial gradient", "Ramp"])); @@ -1800,6 +1871,112 @@ mod tests { assert_eq!(embedding_label(0x0003), "embedding not allowed"); assert_eq!(embedding_label(0x0006), "embedding for preview and print"); assert_eq!(embedding_label(0x000c), "embedding for editing"); + // `fontDetails` names each permission by id. + assert_eq!( + [0, 0x0002, 0x0104, 0x0008, 0x0200, 0x000c].map(embedding_id), + ["allowed", "forbidden", "previewPrint", "editing", "forbidden", "editing"] + ); + } + + /// `fontDetails` gives each Font Details row's facts as values, in the rows' order: how the + /// font was found, the face shown, its file (null for a built-in face) and its embedding + /// permission. The rows and the report stay in English. + #[test] + fn info_exposes_structured_font_details_without_changing_english_rows() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap(); + s.execute("text.create", &json!({"x": 10, "y": 20, "text": "Hi"})).unwrap(); + s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Heavy", "font": "Source Sans 3", "style": "Black Wide"})).unwrap(); + s.execute("text.create", &json!({"x": 10, "y": 80, "text": "Gone", "font": "Qx Absent Family"})).unwrap(); + let i = s.execute("document.info", &json!({"category": "fontDetails"})).unwrap(); + let (entries, rows) = (i["fontDetails"].as_array().unwrap(), i["sections"][0]["rows"].as_array().unwrap()); + assert_eq!(entries.iter().map(|e| e["font"].clone()).collect::>(), rows.iter().map(|r| r[0].clone()).collect::>()); + let at = |prefix: &str| entries.iter().position(|e| e["font"].as_str().unwrap().starts_with(prefix)).unwrap(); + let regular = at("Source Sans 3 Regular"); + assert_eq!( + entries[regular], + json!({"font": "Source Sans 3 Regular", "match": "exact", "family": "Source Sans 3", "style": "Regular", "file": null, "embedding": "allowed"}) + ); + assert_eq!(rows[regular][1], "built in; embedding allowed"); + let (wide, e) = (at("Source Sans 3 Black Wide"), &entries[at("Source Sans 3 Black Wide")]); + assert_eq!((e["match"].as_str(), e["family"].as_str()), (Some("style"), Some("Source Sans 3")), "{e}"); + let file = e["file"].as_str().unwrap_or("built in"); + let fs_type = [("allowed", 0), ("forbidden", 0x0002), ("previewPrint", 0x0004), ("editing", 0x0008)] + .into_iter() + .find_map(|(id, bits)| (e["embedding"] == id).then_some(bits)) + .unwrap(); + let found = format!("{file}; {}", embedding_label(fs_type)); + assert_eq!(rows[wide][1], format!("substituted: shown in Source Sans 3 {} ({found})", e["style"].as_str().unwrap())); + let (gone, e) = (at("Qx Absent Family"), &entries[at("Qx Absent Family")]); + assert!(e["file"].is_null() && e["embedding"].is_null(), "{e}"); + match e["match"].as_str() { + Some("missingWithFallback") => { + assert_eq!(rows[gone][1], format!("missing: shown in {} {}", e["family"].as_str().unwrap(), e["style"].as_str().unwrap())) + } + Some("missing") => assert_eq!((rows[gone][1].as_str(), e["family"].is_null()), (Some("missing"), true)), + m => panic!("{m:?}"), + } + let text = s.execute("document.info", &json!({"category": "fontDetails", "format": "text"})).unwrap()["text"].as_str().unwrap().to_string(); + assert!(text.contains("Source Sans 3 Regular: built in; embedding allowed\n"), "{text}"); + } + + /// `document.unitId` names the document's units as Preferences › Units does, beside the English + /// `units`, and `imageObjects` lists the image objects in the order Embedded Images and Linked + /// Images list them, with the file a linked one shows (null for an embedded one). + #[test] + fn info_exposes_ordered_images_and_unit_id() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap(); + s.edit("Units", |d, _| { + d.units = vectorcraft_doc::Unit::Millimeters; + Ok(()) + }) + .unwrap(); + for x in [0.0, 50.0] { + let r = s.execute("shape.rectangle", &json!({"x": x, "y": 0, "width": 20, "height": 10})).unwrap()["id"].clone(); + s.execute("select.set", &json!({"ids": [r]})).unwrap(); + s.execute("object.rasterize", &json!({"ppi": 72})).unwrap(); + } + let i = s.execute("document.info", &json!({})).unwrap(); + assert_eq!((i["document"]["units"].as_str(), i["document"]["unitId"].as_str()), (Some("Millimeters"), Some("millimeters"))); + let section = |id: &str| i["sections"].as_array().unwrap().iter().find(|s| s["id"] == id).unwrap()["rows"].as_array().unwrap().clone(); + assert!(section("document").iter().any(|r| *r == json!(["Units", "Millimeters"]))); + let embedded = section("embeddedImages"); + let objects = i["imageObjects"].as_array().unwrap(); + assert_eq!(objects.len(), 2, "{objects:?}"); + for (o, r) in objects.iter().zip(&embedded) { + assert_eq!((o["linked"].clone(), o["file"].clone(), o["name"].clone()), (json!(false), Value::Null, r[0].clone()), "{o}"); + assert_eq!(r[1], format!("{} × {} px", o["width"], o["height"])); + } + assert!(section("linkedImages").is_empty()); + } + + /// A `gradientObjects` entry's `kinds` gives the kind of each gradient listed by its kind + /// rather than by a swatch, and null for a swatch's name, even one that reads like a kind and + /// one an object's gradient of that kind shares; a type object's characters count as its own. + #[test] + fn gradient_kinds_tell_kinds_from_swatch_names() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap(); + let ramp = |kind: &str| json!({"kind": kind, "stops": [{"offset": 0, "color": "#000"}, {"offset": 1, "color": "#fff"}]}); + s.execute("swatch.new", &json!({"name": "Radial gradient", "gradient": ramp("linear")})).unwrap(); + let r = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone(); + s.execute("paint.setFill", &json!({"ids": [r], "swatch": "Radial gradient"})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [r], "gradient": ramp("conic")})).unwrap(); + let t = s.execute("text.create", &json!({"x": 10, "y": 40, "text": "Hi"})).unwrap()["id"].clone(); + s.execute("paint.setFill", &json!({"ids": [t], "gradient": ramp("diamond")})).unwrap(); + // The swatch's name and a radial gradient's kind name are listed once, as the name. + let both = s.execute("shape.rectangle", &json!({"x": 20, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone(); + s.execute("paint.setFill", &json!({"ids": [both], "swatch": "Radial gradient"})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [both], "gradient": ramp("radial")})).unwrap(); + let i = s.execute("document.info", &json!({})).unwrap(); + let entry = |id: &Value| i["gradientObjects"].as_array().unwrap().iter().find(|e| e["id"] == *id).unwrap().clone(); + assert_eq!( + (entry(&r)["gradients"].clone(), entry(&r)["kinds"].clone()), + (json!(["Radial gradient", "Conic gradient"]), json!([null, "conic"])) + ); + assert_eq!((entry(&t)["gradients"].clone(), entry(&t)["kinds"].clone()), (json!(["Diamond gradient"]), json!(["diamond"]))); + assert_eq!((entry(&both)["gradients"].clone(), entry(&both)["kinds"].clone()), (json!(["Radial gradient"]), json!([null]))); } #[test] diff --git a/crates/engine/src/tests/package.rs b/crates/engine/src/tests/package.rs index 2e57ed048c..e61c4bb4a4 100644 --- a/crates/engine/src/tests/package.rs +++ b/crates/engine/src/tests/package.rs @@ -534,6 +534,16 @@ fn the_document_info_report_has_every_category() { let i = run(&mut s, "document.info", json!({"category": "linkedImages"})); assert_eq!((i["sections"].as_array().unwrap().len(), i["sections"][0]["id"].as_str()), (1, Some("linkedImages"))); assert_eq!(i["sections"][0]["rows"].as_array().unwrap().len(), 2); + // `imageObjects` lists the linked images in the rows' order with the files they show, and the + // embedded one with no file. + let linked: Vec = i["imageObjects"].as_array().unwrap().iter().filter(|o| o["linked"] == true).cloned().collect(); + for (o, r) in linked.iter().zip(i["sections"][0]["rows"].as_array().unwrap()) { + let shown = format!("{} × {} px — {}", o["width"], o["height"], o["file"].as_str().unwrap()); + assert_eq!((&o["name"], shown.as_str()), (&r[0], r[1].as_str().unwrap())); + } + assert_eq!(linked.len(), 2); + let embedded: Vec<&Value> = i["imageObjects"].as_array().unwrap().iter().filter(|o| o["linked"] == false).collect(); + assert_eq!((embedded.len(), &embedded[0]["file"], &embedded[0]["width"]), (1, &Value::Null, &json!(600))); let all = run(&mut s, "document.info", json!({})); assert_eq!(all["sections"].as_array().unwrap().len(), 13); let i = run(&mut s, "document.info", json!({"category": "brushes"})); diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index a6bacfc1d7..9d15012784 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -688,7 +688,7 @@ fn message_at(lang: Lang, s: &str, depth: usize) -> String { /// Fill `{name}` placeholders in one pass: a value that holds `{name}` text stays as it is. /// Unknown placeholders are left as written. `{{` and `}}` are a literal `{` and `}`. -fn fill_once(template: &str, args: &[(&str, &str)]) -> String { +pub(crate) fn fill_once(template: &str, args: &[(&str, &str)]) -> String { let mut rest = template; let mut out = String::with_capacity(rest.len()); while let Some(open) = rest.find(['{', '}']) { diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index b6601fa7b8..4a79f37e72 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2120,6 +2120,29 @@ font-technology Variable Променливи Bristle Brush Четка с косъм Pattern Brush Четка с мостра missing from the Brushes panel липсва в панела „Четки“ + built in вграден + embedding not allowed вграждането не е разрешено + embedding for editing вграждане за редактиране + embedding for preview and print вграждане за преглед и печат + embedding allowed вграждането е разрешено + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) заместен: показан с {family} {style} ({detail}) + missing: shown in {family} {style} липсва: показан с {family} {style} + missing липсва + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Линеен градиент + Radial gradient Радиален градиент + Conic gradient Конусен градиент + Diamond gradient Ромбовиден градиент + Freeform gradient Градиент със свободна форма +@plural {n} instance|{n} instances {n} екземпляр|{n} екземпляра +@plural {n} row|{n} rows {n} ред|{n} реда +@plural {n} column|{n} columns {n} колона|{n} колони Select art to make a scatter, art or pattern brush. Изберете графика, за да създадете разпръсната четка, артистична четка или четка с мостра. Brush Art Графика на четка New Calligraphic Brush Нова калиграфска четка diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 11544c6115..5982685036 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -3356,6 +3356,29 @@ theme Appearance Izgled Bristle Brush Četkica s čekinjama Pattern Brush Četkica uzorka missing from the Brushes panel nedostaje u panelu Četkice + built in ugrađen + embedding not allowed ugrađivanje nije dozvoljeno + embedding for editing ugrađivanje za uređivanje + embedding for preview and print ugrađivanje za pregled i štampu + embedding allowed ugrađivanje dozvoljeno + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) zamijenjen: prikazan fontom {family} {style} ({detail}) + missing: shown in {family} {style} nedostaje: prikazan fontom {family} {style} + missing nedostaje + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Linearni gradijent + Radial gradient Radijalni gradijent + Conic gradient Konusni gradijent + Diamond gradient Rombni gradijent + Freeform gradient Gradijent slobodnog oblika +@plural {n} instance|{n} instances {n} instanca|{n} instance|{n} instanci +@plural {n} row|{n} rows {n} red|{n} reda|{n} redova +@plural {n} column|{n} columns {n} kolona|{n} kolone|{n} kolona Select art to make a scatter, art or pattern brush. Odaberite crtež da napravite raspršenu, umjetničku četkicu ili četkicu uzorka. Brush Art Crtež četkice New Symbol Novi simbol diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 9aa3b7f4b7..9dfc5458b5 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -3115,6 +3115,29 @@ digits Malayalam Malaiàlam Pattern Borders Vores de motiu Pattern Brush Pinzell de motiu missing from the Brushes panel absent del tauler Pinzells + built in integrat + embedding not allowed incrustació no permesa + embedding for editing incrustació per a l’edició + embedding for preview and print incrustació per a la visualització i la impressió + embedding allowed incrustació permesa + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) substituït: es mostra amb {family} {style} ({detail}) + missing: shown in {family} {style} absent: es mostra amb {family} {style} + missing absent + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppp + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Degradat lineal + Radial gradient Degradat radial + Conic gradient Degradat cònic + Diamond gradient Degradat de rombe + Freeform gradient Degradat de forma lliure +@plural {n} instance|{n} instances {n} instància|{n} instàncies +@plural {n} row|{n} rows {n} fila|{n} files +@plural {n} column|{n} columns {n} columna|{n} columnes Pattern Brush Options Opcions del pinzell de motiu Pattern Editing Edició de motius Pattern Options Opcions del motiu diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9f15f6471e..edaad350a0 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3406,6 +3406,29 @@ theme Appearance Motiv Bristle Brush Štětinový štětec Pattern Brush Vzorkový štětec missing from the Brushes panel chybí v panelu Štětce + built in vestavěné + embedding not allowed vkládání není povoleno + embedding for editing vkládání pro úpravy + embedding for preview and print vkládání pro náhled a tisk + embedding allowed vkládání povoleno + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) nahrazeno: zobrazeno písmem {family} {style} ({detail}) + missing: shown in {family} {style} chybí: zobrazeno písmem {family} {style} + missing chybí + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Lineární přechod + Radial gradient Kruhový přechod + Conic gradient Kuželový přechod + Diamond gradient Kosočtverečný přechod + Freeform gradient Volný přechod +@plural {n} instance|{n} instances {n} instance|{n} instance|{n} instancí +@plural {n} row|{n} rows {n} řádek|{n} řádky|{n} řádků +@plural {n} column|{n} columns {n} sloupec|{n} sloupce|{n} sloupců Select art to make a scatter, art or pattern brush. Vyberte kresbu, abyste mohli vytvořit rozptylový, obrazový nebo vzorkový štětec. Brush Art Kresba štětce New Symbol Nový symbol diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index b783c1ca59..10848dd3f7 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3139,6 +3139,29 @@ theme Appearance Udseende Bristle Brush Børstepensel Pattern Brush Mønsterpensel missing from the Brushes panel mangler i panelet Pensler + built in indbygget + embedding not allowed indlejring ikke tilladt + embedding for editing indlejring til redigering + embedding for preview and print indlejring til visning og udskrivning + embedding allowed indlejring tilladt + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) erstattet: vist med {family} {style} ({detail}) + missing: shown in {family} {style} mangler: vist med {family} {style} + missing mangler + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Lineær gradient + Radial gradient Radial gradient + Conic gradient Konisk gradient + Diamond gradient Diamantgradient + Freeform gradient Freeform-gradient +@plural {n} instance|{n} instances {n} forekomst|{n} forekomster +@plural {n} row|{n} rows {n} række|{n} rækker +@plural {n} column|{n} columns {n} kolonne|{n} kolonner Select art to make a scatter, art or pattern brush. Markér grafik for at oprette en spredningspensel, grafikpensel eller mønsterpensel. Brush Art Penselgrafik New Symbol Nyt symbol diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index dc9398e988..cd6a428631 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2154,6 +2154,29 @@ file-filter Templates Vorlagen Bristle Brush Borstenpinsel Pattern Brush Musterpinsel missing from the Brushes panel fehlt im Bedienfeld „Pinsel“ + built in integriert + embedding not allowed Einbetten nicht erlaubt + embedding for editing Einbetten zum Bearbeiten + embedding for preview and print Einbetten für Vorschau und Druck + embedding allowed Einbetten erlaubt + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) ersetzt: angezeigt in {family} {style} ({detail}) + missing: shown in {family} {style} fehlt: angezeigt in {family} {style} + missing fehlt + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Linearer Verlauf + Radial gradient Radialer Verlauf + Conic gradient Konischer Verlauf + Diamond gradient Rautenverlauf + Freeform gradient Freiformverlauf +@plural {n} instance|{n} instances {n} Instanz|{n} Instanzen +@plural {n} row|{n} rows {n} Zeile|{n} Zeilen +@plural {n} column|{n} columns {n} Spalte|{n} Spalten Select art to make a scatter, art or pattern brush. Wählen Sie eine Grafik aus, um einen Spezial-, Bild- oder Musterpinsel zu erstellen. Brush Art Pinselgrafik New Calligraphic Brush Neuer Kalligrafiepinsel diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index efa233c29d..fc76658c72 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1827,6 +1827,29 @@ font-technology Variable Μεταβλητές Bristle Brush Πινέλο με τρίχες Pattern Brush Πινέλο μοτίβου missing from the Brushes panel λείπει από τον πίνακα Πινέλα + built in ενσωματωμένη + embedding not allowed η ενσωμάτωση δεν επιτρέπεται + embedding for editing ενσωμάτωση για επεξεργασία + embedding for preview and print ενσωμάτωση για προεπισκόπηση και εκτύπωση + embedding allowed η ενσωμάτωση επιτρέπεται + {file}; {embedding} {file}· {embedding} + substituted: shown in {family} {style} ({detail}) αντικαταστάθηκε: εμφανίζεται με {family} {style} ({detail}) + missing: shown in {family} {style} λείπει: εμφανίζεται με {family} {style} + missing λείπει + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Γραμμικό ντεγκραντέ + Radial gradient Ακτινικό ντεγκραντέ + Conic gradient Κωνικό ντεγκραντέ + Diamond gradient Ρομβοειδές ντεγκραντέ + Freeform gradient Ντεγκραντέ ελεύθερης μορφής +@plural {n} instance|{n} instances {n} παρουσία|{n} παρουσίες +@plural {n} row|{n} rows {n} γραμμή|{n} γραμμές +@plural {n} column|{n} columns {n} στήλη|{n} στήλες Select art to make a scatter, art or pattern brush. Επιλέξτε σχέδιο για να δημιουργήσετε πινέλο διασποράς, σχεδίου ή μοτίβου. Brush Art Σχέδιο πινέλου New Layer… Νέο επίπεδο… diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 3657548359..c7c9c8bc1e 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2192,6 +2192,29 @@ font-technology Variable Variables Bristle Brush Pincel de cerdas Pattern Brush Pincel de motivo missing from the Brushes panel falta en el panel Pinceles + built in integrada + embedding not allowed incrustación no permitida + embedding for editing incrustación para edición + embedding for preview and print incrustación para vista previa e impresión + embedding allowed incrustación permitida + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) sustituida: se muestra con {family} {style} ({detail}) + missing: shown in {family} {style} falta: se muestra con {family} {style} + missing falta + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppp + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Degradado lineal + Radial gradient Degradado radial + Conic gradient Degradado cónico + Diamond gradient Degradado de rombo + Freeform gradient Degradado libre +@plural {n} instance|{n} instances {n} instancia|{n} instancias +@plural {n} row|{n} rows {n} fila|{n} filas +@plural {n} column|{n} columns {n} columna|{n} columnas Select art to make a scatter, art or pattern brush. Selecciona una ilustración para crear un pincel de dispersión, de arte o de motivo. Brush Art Ilustración del pincel New Calligraphic Brush Nuevo pincel caligráfico diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index e9fa1bf2cd..126e7854f9 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3345,6 +3345,29 @@ theme Appearance Ulkoasu Bristle Brush Harjassivellin Pattern Brush Kuviosivellin missing from the Brushes panel puuttuu Siveltimet-paneelista + built in sisäänrakennettu + embedding not allowed upottaminen ei sallittu + embedding for editing upottaminen muokkausta varten + embedding for preview and print upottaminen esikatselua ja tulostusta varten + embedding allowed upottaminen sallittu + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) korvattu: näytetään fontilla {family} {style} ({detail}) + missing: shown in {family} {style} puuttuu: näytetään fontilla {family} {style} + missing puuttuu + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Lineaarinen liukuväri + Radial gradient Säteittäinen liukuväri + Conic gradient Kartiomainen liukuväri + Diamond gradient Vinoneliöliukuväri + Freeform gradient Vapaamuotoinen liukuväri +@plural {n} instance|{n} instances {n} esiintymä|{n} esiintymää +@plural {n} row|{n} rows {n} rivi|{n} riviä +@plural {n} column|{n} columns {n} sarake|{n} saraketta Select art to make a scatter, art or pattern brush. Valitse kuvitus, jotta voit luoda sirote-, taide- tai kuviosiveltimen. Brush Art Siveltimen kuvitus New Symbol Uusi symboli diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index bd19d76544..3941bf0497 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2184,6 +2184,29 @@ font-technology Variable Variables Bristle Brush Forme Soies Pattern Brush Forme de motif missing from the Brushes panel absente du panneau Formes + built in intégrée + embedding not allowed incorporation interdite + embedding for editing incorporation pour la modification + embedding for preview and print incorporation pour l’aperçu et l’impression + embedding allowed incorporation autorisée + {file}; {embedding} {file}, {embedding} + substituted: shown in {family} {style} ({detail}) remplacée, affichée en {family} {style} ({detail}) + missing: shown in {family} {style} absente, affichée en {family} {style} + missing absente + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppp + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Dégradé linéaire + Radial gradient Dégradé radial + Conic gradient Dégradé conique + Diamond gradient Dégradé en losange + Freeform gradient Dégradé de forme libre +@plural {n} instance|{n} instances {n} occurrence|{n} occurrences +@plural {n} row|{n} rows {n} ligne|{n} lignes +@plural {n} column|{n} columns {n} colonne|{n} colonnes Select art to make a scatter, art or pattern brush. Sélectionnez une illustration pour créer une forme diffuse, artistique ou de motif. Brush Art Illustration de la forme New Calligraphic Brush Nouvelle forme calligraphique diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 04737d08e1..bf2f223fc0 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -3397,6 +3397,29 @@ antiAlias Sharp शार्प Pattern Borders पैटर्न सीमा Pattern Brush पैटर्न ब्रश missing from the Brushes panel ब्रश पैनल में अनुपलब्ध + built in अंतर्निहित + embedding not allowed एम्बेडिंग की अनुमति नहीं + embedding for editing संपादन के लिए एम्बेडिंग + embedding for preview and print पूर्वावलोकन और प्रिंट के लिए एम्बेडिंग + embedding allowed एम्बेडिंग की अनुमति है + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) प्रतिस्थापित: {family} {style} में दिखाया गया ({detail}) + missing: shown in {family} {style} अनुपलब्ध: {family} {style} में दिखाया गया + missing अनुपलब्ध + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient लीनियर ग्रेडिएंट + Radial gradient रेडियल ग्रेडिएंट + Conic gradient कोनिक ग्रेडिएंट + Diamond gradient डायमंड ग्रेडिएंट + Freeform gradient फ्रीफॉर्म ग्रेडिएंट +@plural {n} instance|{n} instances {n} इंस्टेंस|{n} इंस्टेंस +@plural {n} row|{n} rows {n} पंक्ति|{n} पंक्तियाँ +@plural {n} column|{n} columns {n} कॉलम|{n} कॉलम Pattern Brush Options पैटर्न ब्रश ऑप्शन्स Pattern Editing पैटर्न संपादन Pattern Options पैटर्न ऑप्शन्स diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index eed0d0dae3..969b923a1c 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3009,6 +3009,29 @@ file-filter Templates Sablonok Pattern Mintázat Pattern Brush Mintázatecset missing from the Brushes panel hiányzik az Ecsetek panelről + built in beépített + embedding not allowed beágyazás nem engedélyezett + embedding for editing beágyazás szerkesztéshez + embedding for preview and print beágyazás előnézethez és nyomtatáshoz + embedding allowed beágyazás engedélyezett + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) helyettesítve: megjelenítés ezzel: {family} {style} ({detail}) + missing: shown in {family} {style} hiányzik: megjelenítés ezzel: {family} {style} + missing hiányzik + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Lineáris színátmenet + Radial gradient Körkörös színátmenet + Conic gradient Kúpos színátmenet + Diamond gradient Rombusz színátmenet + Freeform gradient Szabad formájú színátmenet +@plural {n} instance|{n} instances {n} példány +@plural {n} row|{n} rows {n} sor +@plural {n} column|{n} columns {n} oszlop Pattern Brush Options Mintázatecset beállításai Pattern Editing Mintázatszerkesztés Pattern Options Mintázat beállításai diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 1f24d72913..55cff9798e 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3139,6 +3139,29 @@ theme Appearance Tampilan Bristle Brush Kuas bristle Pattern Brush Kuas pola missing from the Brushes panel tidak ada di panel Kuas + built in bawaan + embedding not allowed penyematan tidak diizinkan + embedding for editing penyematan untuk pengeditan + embedding for preview and print penyematan untuk pratinjau dan cetak + embedding allowed penyematan diizinkan + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) diganti: ditampilkan dengan {family} {style} ({detail}) + missing: shown in {family} {style} tidak ada: ditampilkan dengan {family} {style} + missing tidak ada + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Gradien linear + Radial gradient Gradien radial + Conic gradient Gradien kerucut + Diamond gradient Gradien wajik + Freeform gradient Gradien bentuk bebas +@plural {n} instance|{n} instances {n} instans +@plural {n} row|{n} rows {n} baris +@plural {n} column|{n} columns {n} kolom Select art to make a scatter, art or pattern brush. Pilih karya seni untuk membuat kuas sebar, seni, atau pola. Brush Art Karya seni kuas New Symbol Simbol baru diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 428eb07581..ea10433ebd 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2230,6 +2230,29 @@ file-filter Templates Modelli Bristle Brush Pennello a setole Pattern Brush Pennello motivo missing from the Brushes panel mancante nel pannello Pennelli + built in integrato + embedding not allowed incorporamento non consentito + embedding for editing incorporamento per la modifica + embedding for preview and print incorporamento per anteprima e stampa + embedding allowed incorporamento consentito + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) sostituito: mostrato con {family} {style} ({detail}) + missing: shown in {family} {style} mancante: mostrato con {family} {style} + missing mancante + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Sfumatura lineare + Radial gradient Sfumatura radiale + Conic gradient Sfumatura conica + Diamond gradient Sfumatura a rombo + Freeform gradient Sfumatura libera +@plural {n} instance|{n} instances {n} istanza|{n} istanze +@plural {n} row|{n} rows {n} riga|{n} righe +@plural {n} column|{n} columns {n} colonna|{n} colonne Select art to make a scatter, art or pattern brush. Seleziona la grafica per creare un pennello a dispersione, artistico o motivo. Brush Art Grafica del pennello New Calligraphic Brush Nuovo pennello calligrafico diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 661cd88f00..ca80ba3a83 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2439,6 +2439,29 @@ antiAlias Sharp シャープ Bristle Brush 剛毛ブラシ Pattern Brush パターンブラシ missing from the Brushes panel ブラシパネルにありません + built in 内蔵 + embedding not allowed 埋め込み不可 + embedding for editing 編集用に埋め込み可 + embedding for preview and print プレビューと印刷用に埋め込み可 + embedding allowed 埋め込み可 + {file}; {embedding} {file}、{embedding} + substituted: shown in {family} {style} ({detail}) 代替: {family} {style} で表示 ({detail}) + missing: shown in {family} {style} 見つかりません: {family} {style} で表示 + missing 見つかりません + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}、{instances} + {kind}, {rows} × {cols} {kind}、{rows} × {cols} + {grid}, {instances} {grid}、{instances} + Linear gradient 線形グラデーション + Radial gradient 円形グラデーション + Conic gradient 円錐グラデーション + Diamond gradient ひし形グラデーション + Freeform gradient フリーグラデーション +@plural {n} instance|{n} instances インスタンス {n} 個 +@plural {n} row|{n} rows {n} 行 +@plural {n} column|{n} columns {n} 列 Select art to make a scatter, art or pattern brush. 散布ブラシ、アートブラシ、パターンブラシを作成するには、アートを選択します。 Brush Art ブラシのアート New Calligraphic Brush 新規絵筆ブラシ diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 43be303ed6..0778b2e405 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -2982,6 +2982,29 @@ theme Appearance იერსახე Bristle Brush ჯაგრის ფუნჯი Pattern Brush პატერნის ფუნჯი missing from the Brushes panel ფუნჯების პანელში არ არის + built in ჩაშენებული + embedding not allowed ჩაშენება არ არის დაშვებული + embedding for editing ჩაშენება რედაქტირებისთვის + embedding for preview and print ჩაშენება გადახედვისა და ბეჭდვისთვის + embedding allowed ჩაშენება დაშვებულია + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) ჩანაცვლებულია: ნაჩვენებია შრიფტით {family} {style} ({detail}) + missing: shown in {family} {style} აკლია: ნაჩვენებია შრიფტით {family} {style} + missing აკლია + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient წრფივი გრადიენტი + Radial gradient რადიალური გრადიენტი + Conic gradient კონუსური გრადიენტი + Diamond gradient რომბისებრი გრადიენტი + Freeform gradient თავისუფალი ფორმის გრადიენტი +@plural {n} instance|{n} instances {n} ეგზემპლარი +@plural {n} row|{n} rows {n} მწკრივი +@plural {n} column|{n} columns {n} სვეტი Select art to make a scatter, art or pattern brush. მონიშნეთ ნახატი გაფანტვის, მხატვრული ან პატერნის ფუნჯის შესაქმნელად. Brush Art ფუნჯის ნახატი New Symbol ახალი სიმბოლო diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 5fde360321..d0e5750000 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2429,6 +2429,29 @@ antiAlias Sharp 날카롭게 Bristle Brush 강모 브러시 Pattern Brush 패턴 브러시 missing from the Brushes panel 브러시 패널에 없음 + built in 내장 + embedding not allowed 포함 허용 안 됨 + embedding for editing 편집용 포함 + embedding for preview and print 미리 보기 및 인쇄용 포함 + embedding allowed 포함 허용 + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) 대체됨: {family} {style}(으)로 표시({detail}) + missing: shown in {family} {style} 없음: {family} {style}(으)로 표시 + missing 없음 + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient 선형 그레이디언트 + Radial gradient 방사형 그레이디언트 + Conic gradient 원뿔형 그레이디언트 + Diamond gradient 다이아몬드 그레이디언트 + Freeform gradient 자유 형태 그레이디언트 +@plural {n} instance|{n} instances 인스턴스 {n}개 +@plural {n} row|{n} rows {n}행 +@plural {n} column|{n} columns {n}열 Select art to make a scatter, art or pattern brush. 흩뿌리기, 아트 또는 패턴 브러시를 만들려면 아트웍을 선택하세요. Brush Art 브러시 아트 New Calligraphic Brush 새 캘리그래피 브러시 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 6babee27ff..0d4ceaf795 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3656,6 +3656,29 @@ theme Appearance Vormgeving Bristle Brush Borstelpenseel Pattern Brush Patroonpenseel missing from the Brushes panel ontbreekt in het deelvenster Penselen + built in ingebouwd + embedding not allowed insluiten niet toegestaan + embedding for editing insluiten voor bewerken + embedding for preview and print insluiten voor voorvertoning en afdrukken + embedding allowed insluiten toegestaan + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) vervangen: weergegeven in {family} {style} ({detail}) + missing: shown in {family} {style} ontbreekt: weergegeven in {family} {style} + missing ontbreekt + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Lineair verloop + Radial gradient Radiaal verloop + Conic gradient Conisch verloop + Diamond gradient Ruitverloop + Freeform gradient Vrijvormverloop +@plural {n} instance|{n} instances {n} exemplaar|{n} exemplaren +@plural {n} row|{n} rows {n} rij|{n} rijen +@plural {n} column|{n} columns {n} kolom|{n} kolommen Select art to make a scatter, art or pattern brush. Selecteer illustratie om een verspreidings-, art- of patroonpenseel te maken. Brush Art Penseelillustratie New Symbol Nieuw symbool diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 6e10cef90d..7911a22071 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3285,6 +3285,29 @@ theme Appearance Wygląd Bristle Brush Pędzel włosiankowy Pattern Brush Pędzel wzorka missing from the Brushes panel brak w panelu Pędzle + built in wbudowany + embedding not allowed osadzanie niedozwolone + embedding for editing osadzanie do edycji + embedding for preview and print osadzanie do podglądu i drukowania + embedding allowed osadzanie dozwolone + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) zastąpiony: wyświetlany krojem {family} {style} ({detail}) + missing: shown in {family} {style} brak: wyświetlany krojem {family} {style} + missing brak + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Gradient liniowy + Radial gradient Gradient promienisty + Conic gradient Gradient stożkowy + Diamond gradient Gradient rombowy + Freeform gradient Gradient dowolny +@plural {n} instance|{n} instances {n} wystąpienie|{n} wystąpienia|{n} wystąpień +@plural {n} row|{n} rows {n} wiersz|{n} wiersze|{n} wierszy +@plural {n} column|{n} columns {n} kolumna|{n} kolumny|{n} kolumn Select art to make a scatter, art or pattern brush. Zaznacz kompozycję, aby utworzyć pędzel rozproszony, artystyczny lub wzorka. Brush Art Kompozycja pędzla New Symbol Nowy symbol diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index e871e4b15b..c2349359ed 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1801,6 +1801,29 @@ antiAlias Sharp Nítido Bristle Brush Pincel de Cerdas Pattern Brush Pincel de Padrão missing from the Brushes panel ausente do painel Pincéis + built in integrada + embedding not allowed incorporação não permitida + embedding for editing incorporação para edição + embedding for preview and print incorporação para visualização e impressão + embedding allowed incorporação permitida + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) substituída: exibida em {family} {style} ({detail}) + missing: shown in {family} {style} ausente: exibida em {family} {style} + missing ausente + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppp + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Gradiente linear + Radial gradient Gradiente radial + Conic gradient Gradiente cônico + Diamond gradient Gradiente losango + Freeform gradient Gradiente de forma livre +@plural {n} instance|{n} instances {n} instância|{n} instâncias +@plural {n} row|{n} rows {n} linha|{n} linhas +@plural {n} column|{n} columns {n} coluna|{n} colunas Select art to make a scatter, art or pattern brush. Selecione arte para criar um pincel de dispersão, de arte ou de padrão. Brush Art Arte do Pincel New Calligraphic Brush Novo Pincel Caligráfico diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index d8e2ebc995..f878461da5 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2217,6 +2217,29 @@ file-filter Templates Шаблоны Bristle Brush Щетинная кисть Pattern Brush Узорная кисть missing from the Brushes panel отсутствует в панели «Кисти» + built in встроенный + embedding not allowed внедрение запрещено + embedding for editing внедрение для редактирования + embedding for preview and print внедрение для просмотра и печати + embedding allowed внедрение разрешено + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) заменен: показан шрифтом {family} {style} ({detail}) + missing: shown in {family} {style} отсутствует: показан шрифтом {family} {style} + missing отсутствует + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Линейный градиент + Radial gradient Радиальный градиент + Conic gradient Конический градиент + Diamond gradient Ромбовидный градиент + Freeform gradient Произвольный градиент +@plural {n} instance|{n} instances {n} экземпляр|{n} экземпляра|{n} экземпляров +@plural {n} row|{n} rows {n} строка|{n} строки|{n} строк +@plural {n} column|{n} columns {n} столбец|{n} столбца|{n} столбцов Select art to make a scatter, art or pattern brush. Выделите рисунок, чтобы создать кисть-распыление, художественную или узорную кисть. Brush Art Рисунок кисти New Calligraphic Brush Новая каллиграфическая кисть diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 06e60356d7..70efcf795f 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2131,6 +2131,29 @@ font-technology Variable Variabla Bristle Brush Borstpensel Pattern Brush Mönsterpensel missing from the Brushes panel saknas i panelen Penslar + built in inbyggt + embedding not allowed inbäddning inte tillåten + embedding for editing inbäddning för redigering + embedding for preview and print inbäddning för förhandsvisning och utskrift + embedding allowed inbäddning tillåten + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) ersatt: visas med {family} {style} ({detail}) + missing: shown in {family} {style} saknas: visas med {family} {style} + missing saknas + {width} × {height} px {width} × {height} px + {size} — {file} {size} – {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Linjär gradient + Radial gradient Radiell gradient + Conic gradient Konisk gradient + Diamond gradient Rombgradient + Freeform gradient Fri gradient +@plural {n} instance|{n} instances {n} instans|{n} instanser +@plural {n} row|{n} rows {n} rad|{n} rader +@plural {n} column|{n} columns {n} kolumn|{n} kolumner Select art to make a scatter, art or pattern brush. Markera grafik för att skapa en spridnings-, konst- eller mönsterpensel. Brush Art Penselgrafik New Calligraphic Brush Ny kalligrafisk pensel diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index d42c434330..6c9deb97d0 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -463,6 +463,46 @@ fn document_info_labels_are_in_complete_catalogs() { } } +/// The values Document Info makes and shows in the interface language: the font states and +/// embedding permissions, the templates of the font, image, raster effects and Repeat object +/// values, the names of gradients named by their kind, and the counted instances, rows and columns. +#[test] +fn document_info_values_are_in_complete_catalogs() { + const VALUES: [&str; 20] = [ + "built in", + "embedding not allowed", + "embedding for editing", + "embedding for preview and print", + "embedding allowed", + "{file}; {embedding}", + "substituted: shown in {family} {style} ({detail})", + "missing: shown in {family} {style}", + "missing", + "{width} × {height} px", + "{size} — {file}", + "{n} ppi", + "{kind}, {instances}", + "{kind}, {rows} × {cols}", + "{grid}, {instances}", + "Linear gradient", + "Radial gradient", + "Conic gradient", + "Diamond gradient", + "Freeform gradient", + ]; + const COUNTED: [(&str, &str); 3] = [("{n} instance", "{n} instances"), ("{n} row", "{n} rows"), ("{n} column", "{n} columns")]; + let gradient_kinds: Vec = vectorcraft_color::GradientKind::ALL.iter().map(|k| format!("{} gradient", k.label())).collect(); + assert!(gradient_kinds.iter().all(|k| VALUES.contains(&k.as_str())), "{gradient_kinds:?}"); + for lang in LANGUAGES.iter().filter(|lang| lang.complete_menus) { + for value in VALUES { + assert!(lang.catalog().plain(value).is_some(), "{} is missing {value}", lang.code); + } + for (one, other) in COUNTED { + assert!(lang.catalog().plural(one, other, 0).is_some(), "{} is missing the row for {other}", lang.code); + } + } +} + /// The Character panel's Anti-aliasing Method label and its choices, with the Sharp method's /// own `antiAlias` row apart from the plain "Sharp" corner row. #[test] diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 0c3ba33d6c..ff88601736 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6137,6 +6137,29 @@ theme Appearance Görünüm New Brush Yeni Fırça Pattern Brush Desen Fırçası missing from the Brushes panel Fırçalar panelinde yok + built in yerleşik + embedding not allowed gömmeye izin verilmiyor + embedding for editing düzenleme için gömme + embedding for preview and print önizleme ve yazdırma için gömme + embedding allowed gömmeye izin veriliyor + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) yerine konan: {family} {style} ile gösteriliyor ({detail}) + missing: shown in {family} {style} eksik: {family} {style} ile gösteriliyor + missing eksik + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Doğrusal degrade + Radial gradient Radyal degrade + Conic gradient Konik degrade + Diamond gradient Elmas degrade + Freeform gradient Serbest biçimli degrade +@plural {n} instance|{n} instances {n} örnek +@plural {n} row|{n} rows {n} satır +@plural {n} column|{n} columns {n} sütun Pick Color From: Rengi Buradan Al: Scatter Brush Serpme Fırçası Select art to make a scatter, art or pattern brush. Bir serpme, resim veya desen fırçası oluşturmak için çizim seçin. diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 9a5be65929..a7b3ea2c2e 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2222,6 +2222,29 @@ file-filter Templates Шаблони Bristle Brush Щетинний пензель Pattern Brush Візерунковий пензель missing from the Brushes panel відсутній у панелі «Пензлі» + built in вбудований + embedding not allowed вбудовування заборонено + embedding for editing вбудовування для редагування + embedding for preview and print вбудовування для перегляду й друку + embedding allowed вбудовування дозволено + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) замінено: показано шрифтом {family} {style} ({detail}) + missing: shown in {family} {style} відсутній: показано шрифтом {family} {style} + missing відсутній + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Лінійний градієнт + Radial gradient Радіальний градієнт + Conic gradient Конічний градієнт + Diamond gradient Ромбоподібний градієнт + Freeform gradient Довільний градієнт +@plural {n} instance|{n} instances {n} екземпляр|{n} екземпляри|{n} екземплярів +@plural {n} row|{n} rows {n} рядок|{n} рядки|{n} рядків +@plural {n} column|{n} columns {n} стовпець|{n} стовпці|{n} стовпців Select art to make a scatter, art or pattern brush. Виділіть малюнок, щоб створити пензель-розпорошення, художній або візерунковий пензель. Brush Art Малюнок пензля New Calligraphic Brush Новий каліграфічний пензель diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index cca93b0f98..518d89e4b6 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2826,6 +2826,29 @@ digits Hindi Hindi Pattern Họa tiết Pattern Brush Cọ họa tiết missing from the Brushes panel không có trong bảng Cọ + built in tích hợp sẵn + embedding not allowed không cho phép nhúng + embedding for editing nhúng để chỉnh sửa + embedding for preview and print nhúng để xem trước và in + embedding allowed cho phép nhúng + {file}; {embedding} {file}; {embedding} + substituted: shown in {family} {style} ({detail}) đã thay thế: hiển thị bằng {family} {style} ({detail}) + missing: shown in {family} {style} thiếu: hiển thị bằng {family} {style} + missing thiếu + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind}, {instances} + {kind}, {rows} × {cols} {kind}, {rows} × {cols} + {grid}, {instances} {grid}, {instances} + Linear gradient Chuyển sắc tuyến tính + Radial gradient Chuyển sắc hướng tâm + Conic gradient Chuyển sắc hình nón + Diamond gradient Chuyển sắc hình thoi + Freeform gradient Chuyển sắc tự do +@plural {n} instance|{n} instances {n} phiên bản +@plural {n} row|{n} rows {n} hàng +@plural {n} column|{n} columns {n} cột Pattern Brush Options Tùy chọn cọ họa tiết Pattern Editing Chỉnh sửa họa tiết Pattern Options Tùy chọn họa tiết diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b368697e19..26fb9e0c1b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2087,6 +2087,29 @@ file-filter Templates 模板 Bristle Brush 鬃毛画笔 Pattern Brush 图案画笔 missing from the Brushes panel “画笔”面板中没有 + built in 内置 + embedding not allowed 不允许嵌入 + embedding for editing 允许嵌入以供编辑 + embedding for preview and print 允许嵌入以供预览和打印 + embedding allowed 允许嵌入 + {file}; {embedding} {file};{embedding} + substituted: shown in {family} {style} ({detail}) 已替换:以 {family} {style} 显示 ({detail}) + missing: shown in {family} {style} 缺失:以 {family} {style} 显示 + missing 缺失 + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind},{instances} + {kind}, {rows} × {cols} {kind},{rows} × {cols} + {grid}, {instances} {grid},{instances} + Linear gradient 线性渐变 + Radial gradient 径向渐变 + Conic gradient 锥形渐变 + Diamond gradient 菱形渐变 + Freeform gradient 自由渐变 +@plural {n} instance|{n} instances {n} 个实例 +@plural {n} row|{n} rows {n} 行 +@plural {n} column|{n} columns {n} 列 Select art to make a scatter, art or pattern brush. 选择图稿以创建散布画笔、艺术画笔或图案画笔。 Brush Art 画笔图稿 New Calligraphic Brush 新建书法画笔 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 3c89d5e2f0..5685430cc8 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3295,6 +3295,29 @@ file-filter Templates 範本 Bristle Brush 鬃毛筆刷 Pattern Brush 圖樣筆刷 missing from the Brushes panel 「筆刷」面板中沒有 + built in 內建 + embedding not allowed 不允許內嵌 + embedding for editing 允許內嵌以供編輯 + embedding for preview and print 允許內嵌以供預覽和列印 + embedding allowed 允許內嵌 + {file}; {embedding} {file};{embedding} + substituted: shown in {family} {style} ({detail}) 已替代:以 {family} {style} 顯示 ({detail}) + missing: shown in {family} {style} 遺失:以 {family} {style} 顯示 + missing 遺失 + {width} × {height} px {width} × {height} px + {size} — {file} {size} — {file} + {n} ppi {n} ppi + {kind}, {instances} {kind},{instances} + {kind}, {rows} × {cols} {kind},{rows} × {cols} + {grid}, {instances} {grid},{instances} + Linear gradient 線性漸層 + Radial gradient 放射狀漸層 + Conic gradient 錐形漸層 + Diamond gradient 菱形漸層 + Freeform gradient 自由漸層 +@plural {n} instance|{n} instances {n} 個實例 +@plural {n} row|{n} rows {n} 列 +@plural {n} column|{n} columns {n} 欄 Select art to make a scatter, art or pattern brush. 選取圖稿以製作散佈筆刷、藝術筆刷或圖樣筆刷。 Brush Art 筆刷圖稿 New Calligraphic Brush 新增書法筆刷 diff --git a/crates/ui-egui/src/panels/doc_info.rs b/crates/ui-egui/src/panels/doc_info.rs index 1d4db19979..64144a2150 100644 --- a/crates/ui-egui/src/panels/doc_info.rs +++ b/crates/ui-egui/src/panels/doc_info.rs @@ -72,16 +72,15 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { if rows.is_empty() { widgets::dim_label(ui, tl!("None")); } - let named = named_rows(s["id"].as_str().unwrap_or_default(), rows); + let id = s["id"].as_str().unwrap_or_default(); + let named = named_rows(id, rows); for (k, r) in rows.iter().enumerate() { let (label, value) = (r[0].as_str().unwrap_or_default(), r[1].as_str().unwrap_or_default()); - let label = super::label_or_name(label, !named.contains(&k)); if value.is_empty() { - widgets::dim_name(ui, label); - } else if s["id"] == "brushes" { - row(ui, label, brush_value(value).into()); + widgets::dim_name(ui, super::label_or_name(label, !named.contains(&k))); } else { - row(ui, label, value.into()); + let shown = shown_value(&i, id, k, (label, value), named.contains(&k)).unwrap_or_else(|| value.to_string()); + row(ui, super::label_or_name(label, !named.contains(&k)), shown); } } } @@ -102,6 +101,127 @@ fn brush_value(value: &str) -> &str { } } +/// Row `k` of Document Info section `id` (`label` and `value` as `document.info` gives them, the +/// label a name when `named`) with its value in the interface language, built from what +/// `document.info` (`info`) gives as values: the +/// units, the raster effects resolution, each brush's type, each font's file, match and +/// embedding permission, each image's size and file, each Repeat object's kind and counts, and the +/// kinds of the gradients no swatch names. Names (fonts, files, faces, swatches) stay as they are. +/// None when the row's value is shown as `document.info` gives it, or the values do not match the +/// row. +fn shown_value(info: &Value, id: &str, k: usize, (label, value): (&str, &str), named: bool) -> Option { + let entry = |list: &str| info[list].get(k).filter(|e| e["name"] == label || e["font"] == label); + match id { + "document" if !named => match label { + "Units" => { + let unit = info["document"]["unitId"].as_str()?; + vectorcraft_doc::Unit::EAST_ASIAN.into_iter().find(|u| u.key() == unit).map(|u| tl!(u.label()).to_string()) + } + "Raster Effects" => { + let ppi = info["document"]["rasterEffectsPpi"].as_f64()?; + Some(fill(tl!("{n} ppi"), &[("n", &ppi.to_string())])) + } + _ => None, + }, + "brushes" => Some(brush_value(value).to_string()), + "fontDetails" => font_detail_value(entry("fontDetails")?), + "linkedImages" | "embeddedImages" => { + let linked = id == "linkedImages"; + let image = info["imageObjects"].as_array()?.iter().filter(|o| o["linked"] == linked).nth(k).filter(|o| o["name"] == label)?; + image_value(image) + } + "repeats" => repeat_value(entry("repeatObjects")?), + "gradients" => gradient_value(entry("gradientObjects")?), + _ => None, + } +} + +/// [`crate::i18n::fill_once`]: a name that holds `{…}` text stays as it is. +fn fill(template: &str, args: &[(&str, &str)]) -> String { + crate::i18n::fill_once(template, args) +} + +/// A `fontDetails` entry as Font Details shows it: the font's file (or that it is built in) and +/// what its license allows, the face shown in place of a style the family lacks, the face shown in +/// place of a font that is not installed, or that it is missing. +fn font_detail_value(e: &Value) -> Option { + let found = || { + let embedding = match e["embedding"].as_str()? { + "forbidden" => tl!("embedding not allowed"), + "editing" => tl!("embedding for editing"), + "previewPrint" => tl!("embedding for preview and print"), + "allowed" => tl!("embedding allowed"), + _ => return None, + }; + let file = e["file"].as_str().unwrap_or(tl!("built in")); + Some(fill(tl!("{file}; {embedding}"), &[("file", file), ("embedding", embedding)])) + }; + let face = || Some((e["family"].as_str()?, e["style"].as_str()?)); + match e["match"].as_str()? { + "exact" => found(), + "style" => { + let ((family, style), detail) = (face()?, found()?); + Some(fill(tl!("substituted: shown in {family} {style} ({detail})"), &[("family", family), ("style", style), ("detail", &detail)])) + } + "missingWithFallback" => { + let (family, style) = face()?; + Some(fill(tl!("missing: shown in {family} {style}"), &[("family", family), ("style", style)])) + } + "missing" => Some(tl!("missing").to_string()), + _ => None, + } +} + +/// An `imageObjects` entry as Linked Images and Embedded Images show it: its size in pixels, and +/// for a linked image the file it shows. +fn image_value(o: &Value) -> Option { + let size = fill(tl!("{width} × {height} px"), &[("width", &o["width"].as_u64()?.to_string()), ("height", &o["height"].as_u64()?.to_string())]); + if o["linked"] == true { Some(fill(tl!("{size} — {file}"), &[("size", &size), ("file", o["file"].as_str()?)])) } else { Some(size) } +} + +/// A `repeatObjects` entry as Repeat Objects shows it: its kind and the number of instances it +/// draws, or for a grid its rows and columns, followed by the number it draws when that is fewer. +fn repeat_value(e: &Value) -> Option { + let kind = match e["type"].as_str()? { + "radial" => tl!("Radial Repeat"), + "grid" => tl!("Grid Repeat"), + "mirror" => tl!("Mirror Repeat"), + _ => return None, + }; + let n = e["instances"].as_u64()?; + let instances = crate::i18n::tn(n, "{n} instance", "{n} instances"); + if e["type"] != "grid" { + return Some(fill(tl!("{kind}, {instances}"), &[("kind", kind), ("instances", &instances)])); + } + let (rows, cols) = (e["rows"].as_u64()?, e["cols"].as_u64()?); + let (shown_rows, shown_cols) = (crate::i18n::tn(rows, "{n} row", "{n} rows"), crate::i18n::tn(cols, "{n} column", "{n} columns")); + let grid = fill(tl!("{kind}, {rows} × {cols}"), &[("kind", kind), ("rows", &shown_rows), ("cols", &shown_cols)]); + Some(if n < rows.saturating_mul(cols) { fill(tl!("{grid}, {instances}"), &[("grid", &grid), ("instances", &instances)]) } else { grid }) +} + +/// A `gradientObjects` entry as Gradient Objects shows it: its gradients, a swatch's name as it +/// is and a gradient named by its kind in the interface language. +fn gradient_value(e: &Value) -> Option { + let (names, kinds) = (e["gradients"].as_array()?, e["kinds"].as_array()?); + if names.len() != kinds.len() { + return None; + } + let shown: Option> = names + .iter() + .zip(kinds) + .map(|(name, kind)| match kind.as_str() { + None => name.as_str(), + Some("linear") => Some(tl!("Linear gradient")), + Some("radial") => Some(tl!("Radial gradient")), + Some("conic") => Some(tl!("Conic gradient")), + Some("diamond") => Some(tl!("Diamond gradient")), + Some("freeform") => Some(tl!("Freeform gradient")), + Some(_) => name.as_str(), + }) + .collect(); + Some(shown?.join(", ")) +} + /// The rows of Document Info section `id` labelled by a name (an artboard, a style, a swatch, a /// font, an image, a repeat object) rather than an interface label: every row of the lists, and in Document the /// artboards, which follow the Artboards count. Names are shown as they are. @@ -337,4 +457,130 @@ mod tests { let report = app.session.execute("document.info", &json!({"category": "brushes", "format": "text"})).unwrap()["text"].to_string(); assert!(report.contains("Charcoal: Art Brush"), "{report}"); } + + /// Section `id`'s rows as the panel shows them for `info`: each label and its shown value. + fn shown_rows(info: &Value, id: &str) -> Vec<(String, String)> { + let section = info["sections"].as_array().unwrap().iter().find(|s| s["id"] == id).unwrap_or_else(|| panic!("no {id}")); + let rows = section["rows"].as_array().unwrap(); + let named = named_rows(id, rows); + rows.iter() + .enumerate() + .map(|(k, r)| { + let (label, value) = (r[0].as_str().unwrap(), r[1].as_str().unwrap()); + (label.to_string(), shown_value(info, id, k, (label, value), named.contains(&k)).unwrap_or_else(|| value.to_string())) + }) + .collect() + } + + /// The values Document Info makes (the units, the raster effects resolution, each font's file + /// and state, each image's size, each Repeat object's kind and counts, and gradients named by + /// their kind) show in the interface language, from the same `document.info` after a language + /// change; font, face, file and swatch names stay as they are, also with Selection Only, and + /// `document.info` and the saved report stay in English. + #[test] + fn values_follow_interface_language_and_keep_names() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 400, "units": "Millimeters"})).unwrap(); + app.session.execute("text.create", &json!({"x": 10, "y": 40, "text": "Hi"})).unwrap(); + app.session.execute("text.create", &json!({"x": 10, "y": 80, "text": "Heavy", "font": "Source Sans 3", "style": "Black Wide"})).unwrap(); + app.session.execute("text.create", &json!({"x": 10, "y": 120, "text": "Gone", "font": "Qx Absent Family"})).unwrap(); + let rect = |app: &mut VectorcraftApp, x: f64| { + app.session.execute("shape.rectangle", &json!({"x": x, "y": 200, "width": 20, "height": 10})).unwrap()["id"].clone() + }; + let ramp = |kind: &str| json!({"kind": kind, "stops": [{"offset": 0, "color": "#000"}, {"offset": 1, "color": "#fff"}]}); + app.session.execute("swatch.new", &json!({"name": "Linear gradient", "gradient": ramp("radial")})).unwrap(); + let glow = rect(&mut app, 0.0); + app.session.execute("paint.setFill", &json!({"ids": [glow], "swatch": "Linear gradient"})).unwrap(); + app.session.execute("paint.setStroke", &json!({"ids": [glow], "gradient": ramp("radial")})).unwrap(); + app.session.execute("layer.setProps", &json!({"id": glow, "name": "Glow"})).unwrap(); + let star = rect(&mut app, 50.0); + app.session.execute("object.repeat.radial", &json!({"ids": [star], "instances": 6})).unwrap(); + let tile = rect(&mut app, 100.0); + app.session.execute("object.repeat.grid", &json!({"ids": [tile], "rows": 2, "cols": 1})).unwrap(); + let pic = rect(&mut app, 150.0); + app.session.execute("select.set", &json!({"ids": [pic]})).unwrap(); + app.session.execute("object.rasterize", &json!({"ppi": 72})).unwrap(); + app.session.execute("select.set", &json!({"ids": []})).unwrap(); + let info = app.session.execute("document.info", &json!({})).unwrap(); + let value = |rows: &[(String, String)], label: &str| { + rows.iter().find(|(l, _)| l == label).unwrap_or_else(|| panic!("no {label} in {rows:?}")).1.clone() + }; + let english = |id: &str| shown_rows(&info, id); + assert_eq!((value(&english("document"), "Units"), value(&english("document"), "Raster Effects")), ("Millimeters".into(), "72 ppi".into())); + assert_eq!(value(&english("fontDetails"), "Source Sans 3 Regular"), "built in; embedding allowed"); + assert_eq!(value(&english("gradients"), "Glow"), "Linear gradient, Radial gradient"); + let repeats = english("repeats"); + assert_eq!((repeats[0].1.as_str(), repeats[1].1.as_str()), ("Radial Repeat, 6 instances", "Grid Repeat, 2 rows × 1 column")); + // Every value the panel builds reads as `document.info`'s English row. + for s in info["sections"].as_array().unwrap() { + let id = s["id"].as_str().unwrap(); + let rows: Vec<(String, String)> = + s["rows"].as_array().unwrap().iter().map(|r| (r[0].as_str().unwrap().into(), r[1].as_str().unwrap().into())).collect(); + assert_eq!(shown_rows(&info, id), rows, "{id}"); + } + let Some(de) = crate::i18n::Lang::from_code("de") else { panic!("no German catalog") }; + crate::i18n::set_current(de); + let german = |id: &str| shown_rows(&info, id); + let (document, fonts, images, gradients, repeats) = + (german("document"), german("fontDetails"), german("embeddedImages"), german("gradients"), german("repeats")); + let painted = crate::tests_labels::painted_text(&mut app, show); + app.session.execute("select.set", &json!({"ids": [glow]})).unwrap(); + let selected = shown_rows(&app.session.execute("document.info", &json!({"selectionOnly": true})).unwrap(), "gradients"); + let written: std::rc::Rc>> = Default::default(); + let w = written.clone(); + app.services.write = Some(Box::new(move |_: &str, b: &[u8]| { + w.borrow_mut().extend_from_slice(b); + Ok(()) + })); + app.run("docInfo.save", json!({"path": "/tmp/info.txt", "category": "gradients", "selectionOnly": true})).unwrap(); + crate::i18n::set_current(crate::i18n::Lang::EN); + let saved = String::from_utf8(written.borrow().clone()).unwrap(); + assert_eq!((value(&document, "Units"), value(&document, "Raster Effects")), ("Millimeter".into(), "72 ppi".into())); + assert!(painted.contains("Millimeter") && !painted.contains("Millimeters"), "{painted}"); + assert_eq!(value(&fonts, "Source Sans 3 Regular"), "integriert; Einbetten erlaubt"); + let wide = value(&fonts, "Source Sans 3 Black Wide"); + assert!(wide.starts_with("ersetzt: angezeigt in Source Sans 3 ") && wide.ends_with("Einbetten erlaubt)"), "{wide}"); + let gone = fonts.iter().find(|(l, _)| l.starts_with("Qx Absent Family")).unwrap_or_else(|| panic!("{fonts:?}")).1.clone(); + assert!(gone == "fehlt" || gone.starts_with("fehlt: angezeigt in "), "{gone}"); + assert_eq!(images.len(), 1); + assert!(images[0].1.ends_with(" px") && images[0].1.contains(" × "), "{images:?}"); + // A swatch named like a kind keeps its name; the gradient named by its kind is translated. + assert_eq!(value(&gradients, "Glow"), "Linear gradient, Radialer Verlauf"); + assert_eq!(value(&selected, "Glow"), "Linear gradient, Radialer Verlauf"); + assert_eq!((repeats[0].1.as_str(), repeats[1].1.as_str()), ("Radiale Wiederholung, 6 Instanzen", "Rasterwiederholung, 2 Zeilen × 1 Spalte")); + assert!(saved.contains("(the selection only)") && saved.contains("\nGRADIENT OBJECTS\nGlow: Linear gradient, Radial gradient\n"), "{saved}"); + let report = app.session.execute("document.info", &json!({"format": "text"})).unwrap()["text"].as_str().unwrap().to_string(); + assert!(report.contains("Units: Millimeters\n") && report.contains("Radial Repeat, 6 instances"), "{report}"); + } + + /// A Repeat object's counts take each language's plural forms, and a grid that draws fewer + /// instances than its rows and columns give is followed by the number it draws. + #[test] + fn repeat_value_uses_locale_plural_forms_and_drawn_count() { + let radial = |n: u64| json!({"name": "R", "type": "radial", "instances": n}); + let grid = json!({"name": "G", "type": "grid", "rows": 500, "cols": 500, "instances": 125_000}); + let in_lang = |code: &str, e: &Value| { + let Some(lang) = crate::i18n::Lang::from_code(code) else { panic!("no {code} catalog") }; + crate::i18n::set_current(lang); + let shown = repeat_value(e); + crate::i18n::set_current(crate::i18n::Lang::EN); + shown.unwrap_or_else(|| panic!("{e}")) + }; + assert_eq!(in_lang("en", &radial(1)), "Radial Repeat, 1 instance"); + assert_eq!(in_lang("en", &grid), "Grid Repeat, 500 rows × 500 columns, 125000 instances"); + assert_eq!( + [1, 3, 5, 21].map(|n| in_lang("ru", &radial(n))), + [ + "Радиальный повтор, 1 экземпляр", + "Радиальный повтор, 3 экземпляра", + "Радиальный повтор, 5 экземпляров", + "Радиальный повтор, 21 экземпляр" + ] + ); + assert_eq!(in_lang("ru", &grid), "Повтор сеткой, 500 строк × 500 столбцов, 125000 экземпляров"); + assert_eq!([1, 2].map(|n| in_lang("ja", &radial(n))), ["ラジアルリピート、インスタンス 1 個", "ラジアルリピート、インスタンス 2 個"]); + // A full grid shows its rows and columns alone; an unknown kind is shown as `document.info` gives it. + assert_eq!(in_lang("de", &json!({"type": "grid", "rows": 1, "cols": 3, "instances": 3})), "Rasterwiederholung, 1 Zeile × 3 Spalten"); + assert_eq!(repeat_value(&json!({"type": "spiral", "instances": 2})), None); + } } diff --git a/docs/mcp.md b/docs/mcp.md index af84abbbab..912ac32562 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -7249,7 +7249,14 @@ result carries the same files as a zip (`{name: ".zip", dataBase64}`, entr document, objects, graphicStyles, brushes, spotColors, patterns, gradients, symbols, fonts, fontDetails, linkedImages, embeddedImages, repeats), `fonts` (the fonts the document's type uses, the type in opacity masks and in the art of its symbols and pattern swatches included, which the fonts and fontDetails categories list; with `selectionOnly`, the -fonts of the selected type), `brushNames` (the brushes the art's strokes use, each once, hidden strokes, strokes at an opacity of 0, strokes with no +fonts of the selected type), `fontDetails` (`[{font, match, family, style, file, embedding}]`: each font of `fonts`, +in order, as the fontDetails category's rows give it: `match` is `exact`, `style` (a style the family lacks, shown in +`family` `style`), `missingWithFallback` (not installed, shown in `family` `style`) or `missing`; `file` is the face's +file name, null for a built-in face; `embedding` is `allowed`, `editing`, `previewPrint` or `forbidden`, null when no +face is found), `imageObjects` (`[{name, width, height, linked, file}]`: the image objects in the order the +linkedImages and embeddedImages categories list them, with the file a linked image's row shows and null for an +embedded one), `document.unitId` (the document's units as Preferences › Units names them, such as `millimeters`, beside +the English `document.units`), `brushNames` (the brushes the art's strokes use, each once, hidden strokes, strokes at an opacity of 0, strokes with no paint or width, guides, paths with no segment, type that draws no characters, a group's or layer's own strokes, a compound path member's own strokes and an image's strokes without a shape left out; with `selectionOnly`, the brushes the selected objects' strokes use), `brushes` (`[{name, type}]`: those brushes with their types, `calligraphic`, `scatter`, `art`, @@ -7262,9 +7269,11 @@ whose drawn characters use a pattern included, hidden fills and strokes, fills a member's own fills and strokes, an image's fills and strokes that no Convert to Shape or Outline Object effect gives a shape, and characters that draw nothing (spaces and line breaks without an underline or strikethrough) left out, with the patterns it uses, which the patterns category lists as Pattern Objects and `objects.patterns` counts; with `selectionOnly`, those in the selection), -`patternNames` (the patterns those objects use), `gradientObjects` (`[{id, name, gradients}]`: every object filled or +`patternNames` (the patterns those objects use), `gradientObjects` (`[{id, name, gradients, kinds}]`: every object filled or stroked with a gradient, by the same rules as `patternObjects`, with the gradients it uses: the name of the gradient -swatch a gradient is linked to, else `Linear gradient`, `Radial gradient` or `Freeform gradient`; the gradients category +swatch a gradient is linked to, else its kind's name, such as `Linear gradient`, `Radial gradient` or `Freeform gradient`; +`kinds` gives, for each of `gradients`, the kind (`linear`, `radial`, `conic`, `diamond` or `freeform`) of a gradient +named by its kind and null for a swatch's name; the gradients category lists them as Gradient Objects and `objects.gradients` counts them; gradient swatches no art uses are not listed; with `selectionOnly`, those in the selection), `gradientNames` (the gradients those objects use), `spotObjects` (`[{id, name, spotColors}]`: every object filled or stroked with a spot color swatch, as a solid color or in the diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 01bd2440d6..26681e5b7f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1513,6 +1513,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Document Info's Brushes, Spot Color Objects, Pattern Objects and Gradient Objects categories leave out hidden objects, objects at an opacity of 0 and the objects inside a hidden group or layer or one at an opacity of 0, their characters' paints and colorized images included. With Selection Only an object selected inside such a group or layer is left out too, and Objects still counts every object as before. This is VectorCraft's own rule. Test: `cmd::docinfo::tests::info_skips_paints_on_hidden_and_clear_objects_and_their_members` (vectorcraft-engine). Document Info's Save… writes the category the panel shows, with Selection Only applied, as the [Document Info panel](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/document-info-panel.html) page describes ("Exports the selected category's visible information to a text file"). With All Categories shown it writes every category, which is VectorCraft's own rule (`docInfo.save {category}`). Test: `panels::doc_info::tests::save_writes_the_category_the_panel_shows`. Document Info's category menu and headings show every category title in the interface language, Font Details, Linked Images and Embedded Images included, and the Objects category shows every count label in it too: Compound Paths, Text Objects, Placed Documents, Symbol Instances, Gradient Meshes, Blends, Envelopes, Intertwine Groups, Opacity Masks and Objects with Effects. The Document category's Pattern Swatches row and the Objects category's Path Length row are translated in every catalog that has every menu label. `document.info` and the saved report keep the English labels. Showing these labels in the interface language is VectorCraft's own rule. Test: `i18n::tests::document_info_labels_are_in_complete_catalogs` (vectorcraft-ui-egui). + Document Info shows the values it makes in the interface language too: the Units, the Raster Effects resolution, each font's file, substitution and embedding permission (or that it is built in or missing), each image's size and linked file, each Repeat object's kind with its instances, rows and columns in the language's plural forms, and gradients named by their kind. Font, face, file, swatch and other names stay as they are, a gradient swatch named like a kind included, and `document.info`, Save… and Package's report keep the English values (`document.unitId`, `fontDetails`, `imageObjects`, `gradientObjects` `kinds`). Showing these values in the interface language is VectorCraft's own rule. Tests: `panels::doc_info::tests::values_follow_interface_language_and_keep_names`, `repeat_value_uses_locale_plural_forms_and_drawn_count`, `i18n::tests::document_info_values_are_in_complete_catalogs` (vectorcraft-ui-egui), `cmd::docinfo::tests::info_exposes_structured_font_details_without_changing_english_rows`, `info_exposes_ordered_images_and_unit_id` and `gradient_kinds_tell_kinds_from_swatch_names` (vectorcraft-engine). The Brushes panel menu's Select All Unused selects the brushes that are not used in the document, as the [Brushes](https://web.archive.org/web/20241230205225/https://helpx.adobe.com/illustrator/using/brushes.html) page (archived copy) describes. A brush counts as used when a stroke paints with it in the art (opacity masks included), in symbols, graph designs, pattern tiles or graphic styles, or in the art of a used brush (`brush.unused`). A rename in Brush Options and Delete Brush change every stroke that paints with the brush: in the layers and opacity masks, symbols, graph designs, pattern tiles, graphic styles and the art of other brushes. A rename gives those strokes the new name, and Delete Brush makes them plain strokes, in one undo step. A scatter, art or pattern brush dragged from the Brushes panel onto an empty part of the canvas places a copy of its art there to edit, as the [Modify brushes](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/apply-and-edit-strokes/modify-brushes.html) page describes (`brush.placeArt`). The copy is centered where it is dropped. A pattern brush comes out as its tiles in a row, 10 pt apart, each in a group named for its tile, with an unpainted rectangle at the back of a tile that has a repeat frame. The centering and the tile layout are VectorCraft's own rules. Evidence: `tests::brushsym::place_art_puts_a_copy_of_the_brush_art_where_it_is_dropped`, `tests::brushsym::pattern_brushes_place_their_tiles_in_a_row` (vectorcraft-engine), `canvas::tests::a_brush_with_art_dropped_off_the_art_places_a_copy_of_its_art_there` (vectorcraft-ui-egui).