//! The tool catalogue: Acrobat's "All tools" information architecture, as data. //! //! Every UI (the egui shell today, anything tomorrow) builds its tool panel, palette and menus //! from this table; nothing is hard-coded in the toolkit. Each item names the command it will //! dispatch and the milestone (plan/execution-plan.md) in which it ships, so the UI can be honest //! about what works today. //! //! Source of the inventory: plan/acrobat/02-ui-ux.md ยง2 (observed in Acrobat Pro 26.002). /// Where a tool or item stands. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Availability { /// Works in this build. Ready, /// Planned; ships in the named milestone. Planned(&'static str), /// Cloud-only in Acrobat; PdfCraft offers an optional pluggable provider instead. Provider, } #[derive(Clone, Copy, Debug)] pub struct ToolItem { pub label: &'static str, pub icon: &'static str, pub command: &'static str, pub availability: Availability, /// Further commands of the same tool, shown as a drop-down beside the entry: Acrobat's /// Watermark control offers Add, Update and Remove. The first is `command` itself. A UI /// enables each one by its own command's needs, so Update and Remove stay greyed out until /// the document has that kind of mark. Empty for a plain entry. pub actions: &'static [&'static str], } #[derive(Clone, Copy, Debug)] pub struct ToolSection { pub title: &'static str, pub items: &'static [ToolItem], } #[derive(Clone, Copy, Debug)] pub struct ToolGroup { pub id: &'static str, pub label: &'static str, pub icon: &'static str, /// Icon tint, as Acrobat colour-codes its tool families. pub hue: [u8; 3], pub badge: Option<&'static str>, pub availability: Availability, pub sections: &'static [ToolSection], } const fn item(label: &'static str, icon: &'static str, command: &'static str, availability: Availability) -> ToolItem { ToolItem { label, icon, command, availability, actions: &[] } } /// An entry that opens a menu of `actions` (the first one is its `command`). const fn menu_item(label: &'static str, icon: &'static str, actions: &'static [&'static str]) -> ToolItem { let command = match actions { [first, ..] => *first, [] => "", }; ToolItem { label, icon, command, availability: Ready, actions } } use Availability::{Planned, Provider, Ready}; const RED: [u8; 3] = [0xE0, 0x3E, 0x3E]; const PURPLE: [u8; 3] = [0x8E, 0x4E, 0xE6]; const BLUE: [u8; 3] = [0x3A, 0x6F, 0xE8]; const GREEN: [u8; 3] = [0x2D, 0x9D, 0x5B]; const ORANGE: [u8; 3] = [0xE8, 0x8A, 0x1A]; const TEAL: [u8; 3] = [0x14, 0x9C, 0xA8]; const PINK: [u8; 3] = [0xD6, 0x3B, 0x8F]; pub static TOOL_GROUPS: &[ToolGroup] = &[ ToolGroup { id: "export", label: "Export a PDF", icon: "file-output", hue: BLUE, badge: None, availability: Ready, sections: &[ToolSection { title: "Export to", items: &[ item("Microsoft Word (.docx)", "file-text", "export.docx", Ready), item("Spreadsheet (.xlsx)", "grid-3x3", "export.xlsx", Planned("M10")), item("Presentation (.pptx)", "presentation", "export.pptx", Ready), item("Image (PNG)", "image", "export.image", Ready), item("Export all images", "image", "export.all_images", Ready), item("HTML web page", "file-symlink", "export.html", Ready), item("Rich Text Format (.rtf)", "file-text", "export.rtf", Ready), item("Text (plain)", "type", "export.text", Ready), item("PostScript / EPS", "file-down", "export.ps", Planned("M10")), ], }], }, ToolGroup { id: "fill_sign", label: "Fill & Sign", icon: "pen-line", hue: PURPLE, badge: None, availability: Ready, sections: &[ToolSection { title: "Fill", items: &[ item("Add text", "type", "sign.fill.text", Ready), item("Checkmark", "check", "sign.fill.check", Ready), item("Cross", "x", "sign.fill.cross", Ready), item("Dot", "circle-dot", "sign.fill.dot", Ready), item("Line", "minus", "sign.fill.line", Ready), item("Date", "clock-3", "sign.fill.date", Ready), item("Signature", "signature", "sign.fill.signature", Ready), item("Initials", "signature", "sign.fill.initials", Ready), item("Change signature", "signature", "sign.fill.signature.change", Ready), item("Change initials", "signature", "sign.fill.initials.change", Ready), ], }], }, ToolGroup { id: "edit", label: "Edit a PDF", icon: "file-pen-line", hue: PINK, badge: None, availability: Planned("M7"), sections: &[ ToolSection { title: "Modify page", items: &[ item("Edit text & images", "text-cursor-input", "edit.edit_text", Ready), item("Rotate pages", "rotate-cw", "page.rotate", Ready), item("Insert pages", "file-plus-2", "page.insert", Ready), item("Delete pages", "trash-2", "page.delete", Ready), item("Extract pages", "file-output", "page.extract", Ready), item("Organize pages", "layout-grid", "page.organize", Ready), ], }, ToolSection { title: "Add content", items: &[ item("Text", "type", "edit.text", Ready), item("Image", "image-plus", "edit.image", Ready), menu_item("Header and footer", "heading", &["edit.header_footer", "edit.header_footer.update", "edit.header_footer.remove"]), menu_item("Watermark", "stamp", &["edit.watermark", "edit.watermark.update", "edit.watermark.remove"]), item("Link", "link-2", "edit.link", Ready), item("Bates numbering", "hash", "edit.bates", Ready), menu_item("Background", "palette", &["edit.background", "edit.background.update", "edit.background.remove"]), item("Attach file", "paperclip", "edit.attach", Planned("M12")), ], }, ], }, ToolGroup { id: "create", label: "Create a PDF", icon: "file-plus-2", hue: RED, badge: None, availability: Ready, sections: &[ToolSection { title: "Create from", items: &[ item("Single file", "file-input", "create.file", Ready), item("Multiple files", "files", "create.multiple", Ready), item("Separate PDFs", "files", "create.multiple_separate", Ready), item("Images", "image", "create.images", Ready), item("Clipboard", "copy-plus", "create.clipboard", Ready), item("Blank page", "file-plus-2", "create.blank", Ready), ], }], }, ToolGroup { id: "combine", label: "Combine files", icon: "files", hue: BLUE, badge: None, availability: Ready, sections: &[ToolSection { title: "Combine", items: &[item("Add files to combine", "files", "page.combine", Ready)] }], }, ToolGroup { id: "organize", label: "Organize pages", icon: "layout-grid", hue: GREEN, badge: None, availability: Ready, sections: &[ ToolSection { title: "Page options", items: &[ item("Page grid", "layout-grid", "page.organize", Ready), item("Rotate", "rotate-cw", "page.rotate", Ready), item("Delete", "trash-2", "page.delete", Ready), item("Extract", "file-output", "page.extract", Ready), item("Insert", "file-plus-2", "page.insert", Ready), item("Replace", "replace", "page.replace", Ready), item("Split", "scissors", "page.split", Ready), ], }, ToolSection { title: "More", items: &[ item("Set page boxes", "square-dashed-mouse-pointer", "page.boxes", Ready), item("Crop pages", "crop", "page.crop", Ready), item("Duplicate pages", "copy-plus", "page.duplicate", Ready), item("Reverse pages", "arrow-up-down", "page.reverse", Ready), item("Page labels", "tag", "page.number", Ready), item("Page transitions", "presentation", "page.transitions", Planned("M4")), ], }, ], }, ToolGroup { id: "comment", label: "Add comments", icon: "message-square-text", hue: ORANGE, badge: None, availability: Ready, sections: &[ ToolSection { title: "Markup", items: &[ item("Sticky note", "sticky-note", "comment.note", Ready), item("Highlight text", "highlighter", "comment.highlight", Ready), item("Underline", "underline", "comment.underline", Ready), item("Strikethrough", "strikethrough", "comment.strikeout", Ready), item("Squiggly underline", "spline", "comment.squiggly", Ready), item("Text box", "type", "comment.freetext", Ready), ], }, ToolSection { title: "Drawing", items: &[ item("Draw freehand", "pencil", "comment.ink", Ready), item("Rectangle", "square", "comment.square", Ready), item("Oval", "circle", "comment.circle", Ready), item("Line", "minus", "comment.line", Ready), item("Arrow", "move-right", "comment.arrow", Ready), item("Cloud", "cloud", "comment.cloud", Planned("M5")), item("Stamp", "stamp", "comment.stamp", Planned("M5")), ], }, ToolSection { title: "Review", items: &[ item("Comment list", "message-square-text", "comment.list", Ready), item("Flatten comments", "layers", "comment.flatten", Ready), ], }, ], }, ToolGroup { id: "scan", label: "Scan & OCR", icon: "scan-text", hue: GREEN, badge: None, availability: Ready, sections: &[ToolSection { title: "Recognize text", items: &[ item("In this file", "scan-text", "ocr.recognize", Ready), item("In multiple files", "files", "ocr.recognize_batch", Ready), item("Enhance scanned file", "sparkles", "ocr.enhance", Planned("M10")), item("Enhance camera image", "camera", "ocr.enhance_camera", Ready), item("Correct recognized text", "text-select", "ocr.correct", Planned("M10")), ], }], }, ToolGroup { id: "protect", label: "Protect a PDF", icon: "shield-check", hue: BLUE, badge: None, availability: Planned("M8"), sections: &[ ToolSection { title: "Protect", items: &[ item("Protect with password", "lock", "protect.password", Ready), item("Remove hidden information", "eye-off", "protect.remove_hidden", Ready), ], }, ToolSection { title: "Advanced options", items: &[ item("Encrypt with certificate", "file-lock-2", "protect.certificate", Planned("M8")), item("Security properties", "shield-check", "protect.properties", Ready), item("Remove security", "lock-open", "protect.remove", Ready), ], }, ], }, ToolGroup { id: "redact", label: "Redact a PDF", icon: "rectangle-horizontal", hue: RED, badge: None, availability: Ready, sections: &[ToolSection { title: "Redact", items: &[ item("Redact text and images", "rectangle-horizontal", "redact.mark", Ready), item("Redact pages", "file-x", "redact.pages", Ready), item("Find text and redact", "file-search", "redact.search", Ready), item("Set properties", "settings-2", "redact.properties", Ready), item("Sanitize document", "sparkles", "redact.sanitize", Ready), ], }], }, ToolGroup { id: "compress", label: "Compress a PDF", icon: "file-down", hue: RED, badge: None, availability: Ready, sections: &[ToolSection { title: "Optimize", items: &[ item("Reduce file size", "file-down", "optimize.reduce", Ready), item("Advanced optimization", "settings-2", "optimize.advanced", Ready), item("Audit space usage", "gauge", "optimize.audit", Planned("M11")), ], }], }, ToolGroup { id: "form", label: "Prepare a form", icon: "text-cursor-input", hue: PURPLE, badge: None, availability: Ready, sections: &[ ToolSection { title: "Fields", items: &[ item("Field list", "list", "form.fields", Ready), item("Detect form fields", "scan", "form.detect", Ready), item("Clear form", "eraser", "form.clear", Ready), item("Field properties", "settings-2", "form.field.properties", Ready), item("Tab order by rows", "rows-3", "form.tab_order.row", Ready), item("Tab order by columns", "columns-3", "form.tab_order.column", Ready), item("Flatten form fields", "layers", "form.flatten", Ready), item("Import data", "file-input", "form.import_data", Ready), item("Export data", "file-output", "form.export_data", Ready), ], }, ToolSection { title: "Add form components", items: &[ item("Text field", "text-cursor-input", "form.add.text", Ready), item("Checkbox", "check-circle-2", "form.add.checkbox", Ready), item("Radio button", "circle", "form.add.radio", Ready), item("Drop-down list", "chevron-down", "form.add.combo", Ready), item("List box", "list", "form.add.list", Ready), item("Button", "square", "form.add.button", Ready), item("Image field", "image", "form.add.image", Ready), item("Date field", "clock-3", "form.add.date", Ready), item("Digital signature", "signature", "form.add.signature", Ready), ], }, ], }, ToolGroup { id: "stamp", label: "Add a stamp", icon: "stamp", hue: PURPLE, badge: None, availability: Ready, sections: &[ToolSection { title: "Stamps", items: &[item("Stamp palette", "stamp", "comment.stamp", Ready)] }], }, ToolGroup { id: "certificate", label: "Use a certificate", icon: "badge-check", hue: TEAL, badge: None, availability: Ready, sections: &[ToolSection { title: "Certificates", items: &[ item("Digitally sign", "signature", "sign.digital", Ready), item("Timestamp", "clock-3", "sign.timestamp", Planned("M9")), item("Validate all signatures", "badge-check", "sign.validate", Ready), item("Certify (visible signature)", "badge-check", "sign.certify", Ready), item("Certify (invisible signature)", "badge-check", "sign.certify_invisible", Ready), ], }], }, ToolGroup { id: "print_production", label: "Use print production", icon: "printer", hue: PURPLE, badge: None, availability: Planned("M11"), sections: &[ToolSection { title: "Print production", items: &[ item("Output preview", "layers", "prepress.output_preview", Planned("M11")), item("Preflight", "file-check", "preflight.run", Planned("M11")), item("Convert colors", "palette", "prepress.convert_colors", Planned("M11")), item("Set page boxes", "square-dashed-mouse-pointer", "page.boxes", Ready), item("Add printer marks", "ruler", "prepress.marks", Planned("M11")), item("Fix hairlines", "minus", "prepress.hairlines", Planned("M11")), ], }], }, ToolGroup { id: "measure", label: "Measure objects", icon: "ruler", hue: PINK, badge: None, availability: Ready, sections: &[ToolSection { title: "Measure", items: &[ item("Distance", "ruler", "measure.distance", Ready), item("Perimeter", "ruler", "measure.perimeter", Ready), item("Area", "ruler", "measure.area", Ready), item("Drawing scale", "ruler", "measure.scale", Ready), item("Measurement information", "ruler", "measure.info", Ready), item("Snapping", "ruler", "measure.snap", Ready), item("Export measurements", "file-output", "measure.export", Ready), item("Geospatial location", "compass", "measure.geo", Planned("M12")), ], }], }, ToolGroup { id: "compare", label: "Compare files", icon: "git-compare", hue: PINK, badge: None, availability: Ready, sections: &[ToolSection { title: "Compare", items: &[item("Select files to compare", "git-compare", "doc.compare", Ready)] }], }, ToolGroup { id: "actions", label: "Use guided actions", icon: "list-checks", hue: PURPLE, badge: None, availability: Ready, sections: &[ToolSection { title: "Actions list", items: &[ item("Action Wizard", "list-checks", "actions.wizard", Ready), item("Make accessible", "accessibility", "actions.make_accessible", Planned("M13")), item("Prepare for distribution", "send", "actions.distribution", Ready), item("Optimize scanned documents", "scan-text", "actions.optimize_scans", Ready), item("Archive documents", "file-check", "actions.archive", Planned("M13")), ], }], }, ToolGroup { id: "accessibility", label: "Prepare for accessibility", icon: "accessibility", hue: PURPLE, badge: None, availability: Ready, sections: &[ToolSection { title: "Accessibility", items: &[ item("Automatically tag PDF", "tag", "a11y.autotag", Ready), item("Tag form fields", "tag", "a11y.autotag_fields", Ready), item("Change reading options", "book-open", "a11y.reading_options", Ready), item("Check for accessibility", "accessibility", "a11y.check", Ready), item("Find untagged content", "search", "a11y.find_untagged", Ready), item("Open accessibility report", "file-text", "a11y.report", Ready), item("Add alternate text", "image", "a11y.alt_text", Ready), item("Tags panel", "tag", "a11y.tags", Ready), item("Fix reading order", "list-ordered", "a11y.reading_order", Planned("M12")), ], }], }, ToolGroup { id: "standards", label: "Apply PDF standards", icon: "file-check", hue: RED, badge: None, availability: Ready, sections: &[ToolSection { title: "Standards", items: &[ item("Save as PDF/A", "file-check", "standards.pdfa", Ready), item("Save as PDF/X", "file-check", "standards.pdfx", Planned("M11")), item("Save as PDF/UA", "accessibility", "standards.pdfua", Planned("M11")), item("Preflight", "file-check", "preflight.run", Planned("M11")), ], }], }, ToolGroup { id: "search_index", label: "Add search index", icon: "file-search", hue: GREEN, badge: None, availability: Planned("M12"), sections: &[ToolSection { title: "Index", items: &[item("Manage embedded index", "file-search", "search.index", Planned("M12"))] }], }, ToolGroup { id: "javascript", label: "Use JavaScript", icon: "braces", hue: BLUE, badge: None, availability: Planned("M6"), sections: &[ToolSection { title: "JavaScript", items: &[ item("Console", "braces", "js.console", Planned("M6")), item("Document JavaScripts", "file-text", "js.document", Planned("M6")), item("Document actions", "list-checks", "js.actions", Planned("M6")), ], }], }, ToolGroup { id: "ai", label: "AI assistant", icon: "sparkles", hue: PINK, badge: Some("Optional"), availability: Provider, sections: &[ToolSection { title: "Bring your own model (off by default)", items: &[ item("Summarize", "sparkles", "ai.summary", Provider), item("Ask about this document", "message-circle-reply", "ai.ask", Provider), item("Translate", "languages", "ai.translate", Provider), ], }], }, ]; pub fn group(id: &str) -> Option<&'static ToolGroup> { TOOL_GROUPS.iter().find(|g| g.id == id) } #[cfg(test)] mod tests { use super::*; use std::collections::HashSet; #[test] fn ids_are_unique_and_every_group_has_items() { let mut seen = HashSet::new(); for g in TOOL_GROUPS { assert!(seen.insert(g.id), "duplicate group id {}", g.id); assert!(g.sections.iter().any(|s| !s.items.is_empty()), "{} has no items", g.id); } } #[test] fn commands_are_namespaced() { for g in TOOL_GROUPS { for s in g.sections { for i in s.items { assert!(i.command.contains('.'), "{} lacks a namespace", i.command); } } } } #[test] fn menu_entries_list_registered_commands_starting_with_their_own() { let mut menus = 0; for g in TOOL_GROUPS { for s in g.sections { for i in s.items { if i.actions.is_empty() { continue; } menus += 1; assert_eq!(i.actions[0], i.command, "{}: the first action is the entry's command", i.label); for id in i.actions { assert!(crate::commands::command(id).is_some(), "{id} is not a registered command"); } } } } // Watermark, Header and footer, Background: Add / Update / Remove each (Acrobat's menu). assert!(menus >= 3); } #[test] fn every_update_and_remove_command_is_reachable_from_a_tool_entry() { let reachable: HashSet<&str> = TOOL_GROUPS.iter().flat_map(|g| g.sections.iter().flat_map(|s| s.items.iter())).flat_map(|i| i.actions.iter().copied()).collect(); for id in crate::commands::COMMANDS .iter() .map(|c| c.id) .filter(|id| id.starts_with("edit.") && (id.ends_with(".update") || id.ends_with(".remove"))) { assert!(reachable.contains(id), "{id} is in no tool menu"); } } }