Document Info: Objects gives the paths' total length (#1958)

Document Info › Objects counts the paths but didn't say how long they are,
which sign and print work reads there (an LED strip along the letters, a
cut line). `document.info` now adds up the length of every path it counts,
the pieces of compound paths included and guides left out, and returns it
as `pathLength` (pt); the Objects section shows it as Path Length in the
document's units, right after the Paths count, so Selection Only gives the
selected paths' length. The label is translated in the catalogs that
cover the menus; pt-br only takes strings the UI itself shows.
This commit is contained in:
Ayberk A.
2026-10-16 04:08:12 +07:00
committed by bflatastic
parent 7135a377f8
commit a86ff7f177
16 changed files with 61 additions and 5 deletions
+43 -3
View File
@@ -18,7 +18,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Document Info",
["Window", "Document Info"],
None,
"{selectionOnly?, category?: document|objects|graphicStyles|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages (only that one in sections and the text), format?: \"text\" (→ {text}: the report Document Info › Save… and Package write)} → {document, objects: {paths, compoundPaths, groups, …}, fonts, images, swatches, graphicStyleNames (with selectionOnly: the styles the selected objects are linked to), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its licence lets it be embedded)}",
"{selectionOnly?, category?: document|objects|graphicStyles|spotColors|patterns|gradients|symbols|fonts|fontDetails|linkedImages|embeddedImages (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, images, swatches, graphicStyleNames (with selectionOnly: the styles the selected objects are linked to), …, sections: [{id, title, rows: [[label, value]]}] (the panel's categories; fontDetails: each font's file and whether its licence lets it be embedded)}",
has_doc,
info
),
@@ -128,7 +128,13 @@ fn sections(d: &Document, info: &Value, fonts: &BTreeSet<super::fonts::UsedFont>
rows
}
"objects" => {
OBJECT_LABELS.iter().filter_map(|(k, label)| Some((label.to_string(), info["objects"][k].as_u64()?.to_string()))).collect()
let mut rows: Vec<(String, String)> =
OBJECT_LABELS.iter().filter_map(|(k, label)| Some((label.to_string(), info["objects"][k].as_u64()?.to_string()))).collect();
// The paths' total length, in the document's units, right after their count.
if let Some(at) = rows.iter().position(|(label, _)| label == "Paths") {
rows.insert(at + 1, ("Path Length".into(), u.format(info["pathLength"].as_f64().unwrap_or(0.0))));
}
rows
}
"graphicStyles" => names("graphicStyleNames"),
"spotColors" => names("spotColors"),
@@ -229,6 +235,9 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
let roots: Vec<&Node> =
if selection_only { st.selection.objects.iter().filter_map(|id| d.node(*id)).collect() } else { d.layers.iter().map(|l| &**l).collect() };
let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
// The paths' total length, as Document Info › Objects shows it (the length of a cut line, a
// strip of LEDs along the letters, #1958).
let mut path_length = 0.0;
let mut fonts = BTreeSet::new();
let mut images: BTreeMap<String, Value> = BTreeMap::new();
let mut image_rows: Vec<ImageRow> = vec![];
@@ -243,7 +252,13 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
NodeKind::Group { clip: true, .. } => Some("clipGroups"),
NodeKind::Group { .. } => Some("groups"),
NodeKind::Path { guide: true, .. } => Some("guides"),
NodeKind::Path { .. } => Some("paths"),
NodeKind::Path { path, .. } => {
let l = path.length();
if l.is_finite() {
path_length += l;
}
Some("paths")
}
NodeKind::Compound { .. } => Some("compoundPaths"),
NodeKind::Text(t) => {
for run in &t.runs {
@@ -308,6 +323,7 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
let mut out = json!({
"document": document_summary(d),
"objects": counts,
"pathLength": path_length,
"fonts": font_names,
"images": images,
"symbols": symbols,
@@ -405,6 +421,30 @@ mod tests {
assert!(i["objects"]["textObjects"].is_null() && i["objects"]["paths"] == 1);
}
/// Document Info › Objects gives the paths' total length in the document's units (#1958): an
/// open line, a closed rectangle's perimeter and a compound path's pieces count, a guide doesn't.
#[test]
fn info_gives_the_paths_total_length() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 400, "units": "Millimeters"})).unwrap();
let line = s.execute("shape.line", &json!({"x1": 0, "y1": 0, "x2": 72, "y2": 0})).unwrap()["id"].clone();
let rect = s.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 10, "height": 20})).unwrap()["id"].clone();
let length = |s: &mut Session, p: Value| s.execute("document.info", &p).unwrap()["pathLength"].as_f64().unwrap();
assert!((length(&mut s, json!({})) - 132.0).abs() < 1e-6);
s.execute("select.set", &json!({"ids": [line]})).unwrap();
assert!((length(&mut s, json!({"selectionOnly": true})) - 72.0).abs() < 1e-6);
let rows = s.execute("document.info", &json!({"selectionOnly": true, "category": "objects"})).unwrap()["sections"][0]["rows"].clone();
assert_eq!(rows, json!([["Paths", "1"], ["Path Length", "25.4 mm"]]));
// A compound path's pieces are paths: their lengths count.
let other = s.execute("shape.rectangle", &json!({"x": 200, "y": 200, "width": 5, "height": 5})).unwrap()["id"].clone();
s.execute("select.set", &json!({"ids": [rect, other]})).unwrap();
s.execute("object.compoundPath.make", &json!({})).unwrap();
assert!((length(&mut s, json!({"selectionOnly": true})) - 80.0).abs() < 1e-6);
// Nothing with no paths, and no length row.
s.execute("select.none", &json!({})).unwrap();
assert_eq!(length(&mut s, json!({"selectionOnly": true})), 0.0);
}
#[test]
fn embedding_permissions_read_from_fs_type() {
assert_eq!(embedding_label(0), "embedding allowed");