10121 lines
388 KiB
TOML
10121 lines
388 KiB
TOML
# PdfCraft parity checklist vs Acrobat Pro (offline feature set). Generated from plan docs; maintained by hand.
|
||
# Validated and reported by `cargo xtask parity`. Schema: see the [schema] table.
|
||
# Status rules: `shipped` = usable today with at least one cited test; `partial` = part exists (notes say what is
|
||
# missing); `planned` = not started; `na` = Adobe-cloud-only (notes name the local or pluggable alternative).
|
||
# `commands` lists registry ids from crates/engine/src/commands.rs only; `tools` lists crates/automation/src/tools.rs names.
|
||
[schema]
|
||
version = 1
|
||
statuses = ["planned", "partial", "shipped", "na"]
|
||
tiers = ["P0", "P1", "P2", "P3", "N/A"]
|
||
areas = { A = "core", B = "view", C = "edit", D = "organize", E = "comment", F = "form", G = "protect", H = "sign", I = "ocr", J = "create", K = "optimize", L = "print", M = "a11y", N = "misc" }
|
||
|
||
# ==================================================================================================
|
||
# A. Core document model and fidelity
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "core.open-pdf"
|
||
title = "Open PDF files"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/automation/tests/automation.rs::open_inspect_render_and_find", "crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/ui.rs::files_and_quit_from_the_operating_system"]
|
||
notes = "File ▸ Open, the command line (Linux and Windows file associations), the Windows MSI's \"Edit with PdfCraft\" entry on PDF files' File Explorer right-click menu (#682; several selected PDFs open as tabs of one window), and on macOS Finder, Open With and the Dock (Apple events; the bundle declares PDF as a document type). Not yet: a first-level Windows 11 menu entry (needs package identity) or Combine from the file manager (#585)"
|
||
|
||
[[feature]]
|
||
id = "core.repair-broken-files"
|
||
title = "Open damaged files by reconstructing the cross-reference table"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/cos/src/document.rs::damaged_xref_is_reconstructed", "crates/cos/src/parser.rs::indirect_stream_with_wrong_length_recovers", "crates/cos/src/document.rs::self_referential_length_does_not_overflow"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.malformed-input-safety"
|
||
title = "Reject garbage or hostile input without crashing"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::garbage_input_is_rejected_without_panicking", "crates/cos/src/parser.rs::garbage_never_panics", "crates/render/src/raster.rs::missing_page_and_garbage_input_fail_gracefully"]
|
||
notes = "fuzzed nightly (M1.10)"
|
||
|
||
[[feature]]
|
||
id = "core.repair-log"
|
||
title = "Record repairs and show them to the user"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::damaged_files_say_they_were_repaired"]
|
||
notes = "a notice says the file was repaired; Document Properties ▸ Advanced lists the repairs; doc_info reports them"
|
||
|
||
[[feature]]
|
||
id = "core.xref-streams"
|
||
title = "Read cross-reference streams and compressed object streams"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/cos/tests/writer.rs::incremental_saves_stack_on_an_xref_stream_file", "crates/cos/tests/writer.rs::page_tree_loops_in_object_streams_do_not_crash_readers"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.hybrid-xref"
|
||
title = "Hybrid-reference files (/XRefStm) and quirky xref subsections"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/cos/src/document.rs::hybrid_reference_files_read_the_xref_stream_too"]
|
||
notes = "/XRefStm entries supplement the classic table and replace its free entries for objects in object streams; each section is merged as a unit so older revisions only fill gaps"
|
||
|
||
[[feature]]
|
||
id = "core.revision-chain"
|
||
title = "Follow /Prev revision chains with loop protection"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_revisions"]
|
||
evidence = ["crates/cos/src/document.rs::looping_revision_chains_stop", "crates/cos/src/document.rs::revision_ends_split_incremental_updates"]
|
||
notes = "/Prev chains followed newest first with a visited set (loops stop and are logged); revisions listed and opened (doc_revisions, doc_open_revision)"
|
||
|
||
[[feature]]
|
||
id = "core.stream-filters"
|
||
title = "Standard stream filters (Flate, LZW, ASCII85, ASCIIHex, RunLength, predictors)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/filters/src/lib.rs::chain_three_filters_with_predictor", "crates/filters/src/lzw.rs::spec_example", "crates/filters/src/predictor.rs::png_hand_vectors"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.image-filters"
|
||
title = "Image codecs in the filters crate (DCT, JPX, JBIG2, CCITT)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "decoded through the hayro bootstrap for display; own codecs are M1.1/M1.2"
|
||
|
||
[[feature]]
|
||
id = "core.decompression-limits"
|
||
title = "Bounded decompression (filter bomb protection)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/filters/src/flate.rs::bomb_is_bounded", "crates/filters/src/lib.rs::limit_applies_to_intermediate_buffers"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.open-rc4"
|
||
title = "Open RC4-encrypted documents (40/128-bit)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/crypt/src/tests.rs::rc4_reference_vectors", "crates/cos/tests/encryption.rs::every_algorithm_round_trips_and_hides_the_content"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.open-aes"
|
||
title = "Open AES-128 and AES-256 encrypted documents (R4-R6)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/crypt/src/tests.rs::aes_cbc_nist_sp800_38a_vectors", "crates/cos/tests/encryption.rs::every_algorithm_round_trips_and_hides_the_content"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.password-prompt"
|
||
title = "Ask for the user or owner password, with retry"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::password_prompt_opens_and_security_tab_reports_the_details", "crates/crypt/src/tests.rs::empty_user_password_opens_without_asking"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.unicode-passwords"
|
||
title = "Unicode passwords normalised with SASLprep"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/crypt/src/tests.rs::r6_passwords_are_saslprepped"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.owner-password-unlock"
|
||
title = "Owner password lifts document restrictions"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["protect.unlock"]
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/engine/src/tests.rs::restricted_documents_refuse_changes_unless_opened_by_the_owner", "crates/engine/src/tests.rs::owner_password_of_older_revisions_opens_the_viewer_too", "crates/crypt/src/tests.rs::owner_authentication_recovers_the_user_password", "crates/engine/src/tests.rs::the_permissions_password_unlocks_an_open_document", "crates/ui-egui/tests/editing.rs::the_security_notice_unlocks_with_the_permissions_password", "crates/ui-egui/tests/editing.rs::remove_security_asks_for_the_permissions_password_first"]
|
||
notes = "at open (the password prompt takes either password), or after opening a restricted file that needed no password (#995, #1077): Unlock… on the security notice and in Properties ▸ Security, and changing security asks for it first. Not yet: an automation tool to unlock an open document (reopen with doc_open and the password instead)"
|
||
|
||
[[feature]]
|
||
id = "core.permissions-enforced"
|
||
title = "Honour document permissions (print, modify, assemble, copy)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::restricted_documents_show_a_notice_and_block_page_changes", "crates/crypt/src/tests.rs::permission_bits_follow_table_22", "crates/engine/src/tests.rs::combining_protected_files_is_refused_clearly"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.crypt-filters"
|
||
title = "Crypt filters (Identity, /StmF, /StrF)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/crypt/src/tests.rs::identity_crypt_filters_leave_data_alone"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.attachment-only-encryption"
|
||
title = "Attachment-only encryption (/EFF) opens without a password"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M1"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "two corpus files still ask for a password (M1.6)"
|
||
|
||
[[feature]]
|
||
id = "core.unencrypted-metadata"
|
||
title = "Leave XMP metadata readable when /EncryptMetadata is false"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/cos/tests/encryption.rs::unencrypted_metadata_is_left_readable"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.unsupported-handlers"
|
||
title = "Clear message for unsupported security handlers"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/crypt/src/tests.rs::unsupported_handlers_are_reported"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.open-public-key"
|
||
title = "Open certificate-encrypted (public-key handler) documents"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.incremental-save"
|
||
title = "Save as an incremental update (original bytes kept, atomic write)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.save"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::save_writes_an_incremental_update_and_clears_dirty", "crates/engine/src/tests.rs::save_is_incremental_stamps_mod_date_and_rebases", "crates/organize/src/tests.rs::successive_incremental_saves_stack_revisions", "crates/automation/src/lib.rs::staging_never_writes_through_a_file_planted_at_its_name", "crates/ui-egui/src/editing.rs::saving_never_writes_through_a_file_planted_at_the_staging_name"]
|
||
notes = "The atomic write stages to a new, randomly named file created with create_new, so a file or link planted at the staging path is never written through (desktop app, automation and CLI)."
|
||
|
||
[[feature]]
|
||
id = "core.save-as-full-rewrite"
|
||
title = "Save as a new file with a full rewrite and garbage collection"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.save_as"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/organize/src/tests.rs::full_save_drops_unreachable_objects", "crates/engine/src/tests.rs::full_save_is_readable_and_smaller_after_deletes"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.object-stream-compression"
|
||
title = "Compress objects into object streams behind an xref stream on full save"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.save_as"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/cos/tests/writer.rs::full_save_packs_objects_into_object_streams", "crates/cos/tests/writer.rs::qpdf_accepts_object_stream_output", "crates/cos/tests/writer.rs::objects_referenced_by_the_encrypt_dictionary_stay_outside_object_streams"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.encrypted-save"
|
||
title = "Save encrypted documents re-encrypted under the same keys"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.save"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/cos/tests/encryption.rs::incremental_edits_of_encrypted_documents_stay_encrypted", "crates/engine/src/tests.rs::encrypted_documents_open_edit_and_save_encrypted"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.copy-on-write-fidelity"
|
||
title = "Preserve untouched objects byte-for-byte (copy-on-write edits)"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/cos/src/document.rs::reads_classic_revision_and_edits_are_copy_on_write"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.write-validity"
|
||
title = "Written files pass qpdf --check"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/cos/tests/encryption.rs::qpdf_agrees", "crates/cos/tests/writer.rs::qpdf_accepts_object_stream_output"]
|
||
notes = "skipped when qpdf is absent"
|
||
|
||
[[feature]]
|
||
id = "core.deterministic-save"
|
||
title = "Deterministic full-save mode"
|
||
area = "A"
|
||
tier = "P2"
|
||
milestone = "M1"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "M1.9 option"
|
||
|
||
[[feature]]
|
||
id = "core.linearization"
|
||
title = "Linearized save (Fast Web View)"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.revision-list"
|
||
title = "List document revisions from the xref chain"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_revisions"]
|
||
evidence = ["crates/cos/src/document.rs::revision_ends_split_incremental_updates", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "revision ends from the xref chain (linearized first-page sections fold into their revision); listed in Document Properties ▸ Advanced and by doc_revisions with the signatures covering each"
|
||
|
||
[[feature]]
|
||
id = "core.view-revision"
|
||
title = "View an earlier revision as a read-only snapshot"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_open_revision"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::an_earlier_revision_opens_from_document_properties", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "opens the revision's bytes as a new unsaved document named '<name> (revision n)'"
|
||
|
||
[[feature]]
|
||
id = "core.lazy-loading"
|
||
title = "Lazy loading of very large (GB) files within a memory budget"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "ByteSource (M1.5); files are buffered whole today. Viewing holds the file once and editing twice: edits let go of the previous working file before writing the next (a 1 GiB file peaked at three copies after a few edits, now two). Saving still peaks at three. Reading from disk on demand waits on the renderer (hayro parses one contiguous buffer, gap 2)."
|
||
|
||
[[feature]]
|
||
id = "core.pdf20-utf8-strings"
|
||
title = "PDF 2.0 UTF-8 text strings"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M2"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.pdf20-associated-files"
|
||
title = "PDF 2.0 associated files (/AF)"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M2"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.pdf20-dpart"
|
||
title = "PDF 2.0 document parts (DPart)"
|
||
area = "A"
|
||
tier = "P2"
|
||
milestone = "M2"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.pdf20-namespaces"
|
||
title = "PDF 2.0 structure namespaces"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.arlington-validation"
|
||
title = "Structural validation against the Arlington PDF model"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M2"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.undo-redo"
|
||
title = "Undo and redo every document edit"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["edit.undo", "edit.redo"]
|
||
tools = ["edit_undo", "edit_redo"]
|
||
evidence = ["crates/engine/src/tests.rs::undo_and_redo_restore_exact_states", "crates/ui-egui/tests/editing.rs::organize_delete_then_undo_and_redo_with_keys"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.undo-labels"
|
||
title = "Edit menu names the step to undo or redo"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["edit.undo", "edit.redo"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::edit_menu_names_the_step_to_undo", "crates/engine/src/commands.rs::enablement_follows_the_document_state"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.undo-after-save"
|
||
title = "Undo still works after saving"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["edit.undo"]
|
||
tools = ["edit_undo"]
|
||
evidence = ["crates/engine/src/tests.rs::undo_still_works_after_save"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.atomic-batch-edits"
|
||
title = "Multi-step edits are one undo step and all-or-nothing"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/engine/src/tests.rs::batch_is_one_undo_step_and_all_or_nothing", "crates/engine/src/tests.rs::failed_edit_changes_nothing"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.history-panel"
|
||
title = "History panel with named snapshots"
|
||
area = "A"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.autosave"
|
||
title = "Autosave changed documents periodically"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/engine/src/tests.rs::autosave_snapshots_only_changed_documents", "crates/ui-egui/tests/recovery.rs::saving_or_closing_clears_the_entry_and_nothing_is_written_for_clean_documents"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.crash-recovery"
|
||
title = "Offer to recover unsaved work after a crash"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/recovery.rs::a_crashed_session_is_offered_and_recovered_unsaved", "crates/ui-egui/tests/recovery.rs::discarding_removes_the_autosaves", "crates/engine/src/tests.rs::recovered_documents_reopen_unsaved_at_their_original_path"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.encrypted-autosave"
|
||
title = "Autosaves of encrypted documents stay encrypted"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/recovery.rs::encrypted_documents_are_autosaved_encrypted_and_recovered_with_the_password"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.web-recovery"
|
||
title = "Crash recovery in the web app"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.unsaved-changes-prompt"
|
||
title = "Ask before closing or quitting with unsaved changes"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.close"]
|
||
tools = ["doc_close"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_asks_and_cancel_keeps_it", "crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_can_save_first", "crates/ui-egui/tests/editing.rs::quitting_with_unsaved_changes_asks_for_each_document", "crates/ui-egui/tests/editing.rs::save_prompt_stays_inside_the_screen_for_a_long_filename"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.dirty-indicator"
|
||
title = "Unsaved-changes marker on document tabs"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::organize_delete_then_undo_and_redo_with_keys", "crates/engine/src/tests.rs::edits_update_view_data_and_mark_dirty"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.close-document"
|
||
title = "Close a document"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["file.close"]
|
||
tools = ["doc_close"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::clean_tabs_close_without_asking"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "core.close-all"
|
||
title = "Close all documents"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["file.close_all"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::close_all_asks_only_for_changed_documents"]
|
||
notes = "clean documents close; each changed one asks"
|
||
|
||
[[feature]]
|
||
id = "core.revert"
|
||
title = "Revert to the last saved version"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.revert"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::revert_after_confirming", "crates/engine/src/tests.rs::revert_goes_back_to_the_saved_version"]
|
||
notes = "with confirmation; clears undo, as Acrobat"
|
||
|
||
[[feature]]
|
||
id = "core.reload-changed-files"
|
||
title = "Reload a document when its file changes on disk"
|
||
area = "A"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/engine/src/tests.rs::reload_reads_the_new_file_into_the_same_document", "crates/ui-egui/tests/reload.rs::a_rebuilt_file_reloads_in_place_keeping_the_page_and_zoom", "crates/ui-egui/tests/reload.rs::a_half_written_file_waits_until_the_pdf_is_whole", "crates/ui-egui/tests/reload.rs::saving_does_not_reload_or_lose_undo", "crates/ui-egui/tests/reload.rs::unsaved_changes_are_kept_until_reload_is_chosen", "crates/ui-egui/tests/reload.rs::a_deleted_file_leaves_the_document_open", "crates/ui-egui/tests/reload.rs::nothing_reloads_with_the_preference_off", "crates/ui-egui/tests/reload.rs::a_shorter_new_version_drops_what_pointed_into_the_old_one"]
|
||
notes = "Desktop, on by default (Preferences ▸ Documents and view, #431), for LaTeX/Typst builds: a worker thread checks each open file's metadata every 250 ms and reads a change once it has settled and ends in %%EOF; a clean document reloads in place (same tab, page, zoom, scroll; old page images stay until new ones are drawn), one with unsaved changes gets a Reload notice; PdfCraft's own saves never reload. Measured on Windows: one check 11 µs per file, reload of a 417-page 0.9 MB pdflatex file 16 ms. Not yet: OS change notifications (polling adds up to 250 ms), the web build; a reload clears undo history, like Revert."
|
||
|
||
[[feature]]
|
||
id = "core.signed-save-policy"
|
||
title = "Force incremental saves for signed documents"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.save"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards"]
|
||
notes = "saves of signed documents are always incremental: full rewrites are refused, and edits that would need one (applying redactions, changing security, sanitizing) are refused before they change anything"
|
||
|
||
[[feature]]
|
||
id = "core.drag-drop-open"
|
||
title = "Open files by drag and drop"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::dropping_a_pdf_on_the_window_opens_it"]
|
||
notes = "desktop: the dropped file opens in a tab (by path, or its bytes); web: read asynchronously"
|
||
|
||
[[feature]]
|
||
id = "core.recent-files"
|
||
title = "Local recent-files list"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["file.open_recent", "file.clear_recent"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::persistence_round_trips_and_tolerates_garbage", "crates/ui-egui/tests/commands.rs::the_open_recent_menu_lists_files_and_opens_one", "crates/ui-egui/tests/commands.rs::the_open_recent_menu_is_disabled_while_the_list_is_empty", "crates/ui-egui/tests/commands.rs::clear_recent_files_at_the_foot_of_open_recent_empties_the_list", "crates/ui-egui/tests/commands.rs::clear_recent_files_runs_from_the_palette_and_is_saved", "crates/ui-egui/tests/ui.rs::home_removes_one_recent_file_or_clears_them_all"]
|
||
notes = "never reads other apps' lists; File ▸ Open Recent lists them (#290), an already-open file focuses its tab instead of reopening it; Clear Recent Files at the foot of Open Recent, on Home and in the palette, and a remove button on each Home row (#430); missing: the row's remove button shows on pointer hover only (keyboard users clear the whole list)"
|
||
|
||
[[feature]]
|
||
id = "core.reopen-last-session"
|
||
title = "Reopen the files from the last session at startup"
|
||
area = "A"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::the_last_session_reopens_its_files_pages_and_active_tab", "crates/ui-egui/tests/editing.rs::quitting_with_unsaved_changes_still_remembers_every_file", "crates/ui-egui/tests/editing.rs::the_last_session_is_off_by_default_and_skips_missing_and_duplicate_files", "crates/ui-egui/src/last_session.rs::malformed_sessions_are_skipped_and_capped"]
|
||
notes = "Desktop, off by default (Preferences ▸ Documents and view, #442): the files open when PdfCraft last closed reopen at the page each showed, with the same tab in front; files named on the command line open after them, in front; files that are gone, and documents the Recovery dialog offers, are skipped. Quitting with unsaved changes still remembers every tab. Not yet: zoom, layout and scroll position within the page (#418); only the first encrypted file asks for its password (later ones stay closed), and a file opened after it dismisses that prompt; a save that fails partway through a quit can leave the remembered list from that quit; never-saved documents and the web build have no paths to reopen."
|
||
|
||
[[feature]]
|
||
id = "core.usage-rights-preserve"
|
||
title = "Preserve usage-rights (UR3) signatures from Reader-extended files"
|
||
area = "A"
|
||
tier = "P3"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "preserve only"
|
||
|
||
# ==================================================================================================
|
||
# B. Viewing and navigation
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "view.render-pages"
|
||
title = "Render pages (text, vectors, images)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["page_render"]
|
||
evidence = ["crates/render/src/raster.rs::renders_a_blue_rectangle", "crates/automation/tests/automation.rs::open_inspect_render_and_find", "crates/render/src/raster.rs::user_unit_scales_the_page_and_everything_on_it"]
|
||
notes = "hayro bootstrap renderer; own DisplayList devices in M2.6. Pages with /UserUnit are shown at their size in points, as Acrobat reads the value"
|
||
|
||
[[feature]]
|
||
id = "view.render-shadings-patterns"
|
||
title = "Shadings, gradients and tiling patterns"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::self_referencing_tiling_pattern_terminates"]
|
||
notes = "via hayro bootstrap"
|
||
|
||
[[feature]]
|
||
id = "view.render-transparency"
|
||
title = "Blend modes, soft masks and transparency groups"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::soft_mask_without_group_colour_space_is_applied"]
|
||
notes = "via hayro bootstrap"
|
||
|
||
[[feature]]
|
||
id = "view.render-knockout"
|
||
title = "Knockout transparency groups"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "full rewrites of signed documents are refused; saves stay incremental"
|
||
|
||
[[feature]]
|
||
id = "view.render-type3"
|
||
title = "Type 3 fonts"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "hayro bootstrap with vendored patches"
|
||
|
||
[[feature]]
|
||
id = "view.render-cid-fonts"
|
||
title = "CID fonts and CMaps incl. CJK without ToUnicode"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::huge_cid_width_ranges_terminate", "crates/render/src/raster.rs::non_embedded_japanese_cid_fonts_draw_with_a_craft_fonts_face", "crates/render/src/raster.rs::mincho_substitute_covers_half_width_katakana"]
|
||
notes = "CID-to-Unicode tables are M2.4; a non-embedded Adobe-Japan1 font is drawn in a craft-fonts Japanese face (Mincho or Gothic by name; BIZ UDMincho for Mincho, which covers half-width katakana) when built with craft-fonts; non-embedded GB1, CNS1 and Korea1 fonts still draw nothing (no allowed face)"
|
||
|
||
[[feature]]
|
||
id = "view.render-image-codecs"
|
||
title = "JBIG2, JPEG 2000, CCITT and JPEG images"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "hayro bootstrap"
|
||
|
||
[[feature]]
|
||
id = "view.render-annotation-appearances"
|
||
title = "Annotation and widget appearances incl. appearance states"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::widget_appearance_states", "crates/render/src/raster.rs::highlights_without_an_appearance_are_drawn", "crates/engine/src/tests.rs::comments_without_appearances_are_drawn_but_not_saved"]
|
||
notes = "comments without /AP (FreeText, Ink, notes, stamps, shapes, markup) are drawn from their dictionaries in the page view, page_render and page images, never saved (a read-only document still gets the renderer's Highlight fallback only); Link borders without /AP and printing are not covered yet"
|
||
|
||
[[feature]]
|
||
id = "view.render-error-isolation"
|
||
title = "Per-page error isolation (a bad page never takes down the app)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::missing_page_and_garbage_input_fail_gracefully", "crates/render/src/raster.rs::scale_is_clamped_for_huge_pages", "crates/render/src/raster.rs::watchdog_skips_a_stuck_page_and_keeps_rendering"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.render-priority"
|
||
title = "Render visible pages first, then neighbours and thumbnails"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::inline_pool_renders_in_priority_order"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.deep-zoom-tiles"
|
||
title = "Tiled rendering keeps deep zoom sharp"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::tiles_match_full_render", "crates/render/src/raster.rs::effective_scale_never_exceeds_the_caps"]
|
||
notes = "capped at 8192 px per side (M3.3)"
|
||
|
||
[[feature]]
|
||
id = "view.layout-continuous"
|
||
title = "Single page continuous scrolling"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.continuous"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::continuous_layout_stacks_pages_vertically", "crates/ui-egui/tests/view.rs::page_display_commands_switch_layouts", "crates/ui-egui/tests/view.rs::default_page_display_is_used_for_new_documents", "crates/ui-egui/src/canvas.rs::fit_page_and_height_hold_still_while_mixed_page_sizes_scroll_past"]
|
||
notes = "the default for PDFs that don't ask for a layout (Preferences ▸ Default page display); pages stack with no snapping. Fit page and Fit height fit the largest page, so the zoom holds still across mixed page sizes"
|
||
|
||
[[feature]]
|
||
id = "view.layout-single-page"
|
||
title = "Single page view"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.layout.single"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::single_page_layout_shows_one_page_at_a_time", "crates/ui-egui/tests/view.rs::single_page_wheel_pans_instead_when_zoomed_in", "crates/ui-egui/tests/view.rs::wheel_turns_one_page_per_notch_and_per_trackpad_swipe", "crates/ui-egui/tests/view.rs::rail_page_display_button_switches_layouts", "crates/ui-egui/tests/view.rs::view_menu_page_display_uses_the_same_commands"]
|
||
notes = "when the whole page fits, each wheel notch turns a page and each trackpad swipe turns one, momentum included; zoomed in, the wheel pans and never turns the page. Switched from the View menu, the rail's Page display button or the palette"
|
||
|
||
[[feature]]
|
||
id = "view.layout-two-up"
|
||
title = "Two-page view"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.layout.two_up"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::two_up_layout_places_pages_side_by_side", "crates/ui-egui/tests/viewer.rs::two_page_view_steps_a_spread_at_a_time"]
|
||
notes = "next/previous page step a spread at a time"
|
||
|
||
[[feature]]
|
||
id = "view.layout-two-up-scrolling"
|
||
title = "Two-page scrolling"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme"]
|
||
notes = "the two-page view scrolls continuously"
|
||
|
||
[[feature]]
|
||
id = "view.cover-page"
|
||
title = "Show cover page in two-page view"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.layout.cover"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme", "crates/ui-egui/tests/view.rs::page_display_commands_switch_layouts", "crates/ui-egui/tests/viewer.rs::page_down_steps_every_spread_when_several_fit_on_screen"]
|
||
notes = "View ▸ Page display, the rail's Page display button or the palette; disabled outside two-page view; next / previous page step one spread at a time even when several spreads fit on screen"
|
||
|
||
[[feature]]
|
||
id = "view.page-gaps"
|
||
title = "Show or hide gaps between pages"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.auto-scroll"
|
||
title = "Automatic scrolling"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::middle_click_autoscroll_latches_has_a_dead_zone_and_scrolls_both_directions", "crates/ui-egui/tests/control.rs::organize_pages_supports_autoscroll_without_selecting_or_reordering_pages", "crates/ui-egui/tests/control.rs::moving_before_middle_button_release_keeps_scrolling_without_starting_page_tools", "crates/ui-egui/tests/control.rs::farther_from_the_click_scrolls_faster_in_the_viewer_and_page_grid", "crates/ui-egui/tests/control.rs::autoscroll_uses_the_initial_click_position_when_input_arrives_in_one_frame", "crates/ui-egui/tests/control.rs::autoscroll_uses_elapsed_frame_time_and_preserves_fractional_motion_in_both_views", "crates/ui-egui/tests/control.rs::middle_button_pans_while_held_outside_linux", "crates/ui-egui/tests/comments.rs::a_middle_drag_with_a_drawing_tool_scrolls_instead_of_drawing"]
|
||
notes = "Linux-only middle-button vertical autoscroll in the document and Organize Pages; Windows and macOS pan 1:1 while the middle button is held (Grabbing cursor, no latch). On every target the middle button never reaches the page tools, so it scrolls instead of drawing, selecting, cropping or moving. Click to toggle, release keeps scrolling on, and a Chromium-style nonlinear curve increases speed with vertical screen distance from the initial click. A 15-point dead zone pauses immediately. Continuous redraws use elapsed frame time and preserve fractional movement, tested at 30/60/120/144 fps. Cancels on another middle click, Escape, other pointer/wheel input, focus loss, tab changes and dialogs. Driven headlessly through the opt-in UI control channel (ui.click/ui.move/ui.drag). Keyboard-started timed scrolling is not implemented."
|
||
|
||
[[feature]]
|
||
id = "view.zoom-in-out"
|
||
title = "Zoom in and out around the cursor (keys, wheel, pinch)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::zoom_keeps_the_point_under_the_cursor_still", "crates/ui-egui/tests/ui.rs::trackpad_pinch_zooms_the_pdf_under_the_pointer"]
|
||
notes = "A trackpad pinch zooms the PDF beneath the pointer in either direction, keeping the same page point in place. The viewer handles the platform's zoom gesture where it is available."
|
||
|
||
[[feature]]
|
||
id = "view.zoom-to-percent"
|
||
title = "Zoom to a chosen percentage"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::selection_works_in_every_view_rotation"]
|
||
notes = "PdfCraft draws a percentage on its fixed 96-dpi basis: 96/72 logical pixels per PDF point at 100%. Acrobat draws it at its Page Display resolution, with logical pixels per PDF point = ppi / 72 × zoom (checked black-box with Custom resolutions of 96, 110 and 120 ppi on one display), so the same percentage can differ in size. Acrobat's Use System Setting resolution is not reproduced. Tracked with misc.prefs-page-display"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-actual-size"
|
||
title = "Actual size"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::actual_size_fit_width_and_fit_page"]
|
||
notes = "Actual size is 100% on PdfCraft's fixed 96-dpi basis: 96/72 logical pixels per PDF point. Acrobat's Actual Size uses its Page Display resolution (ppi / 72 logical pixels per PDF point at 100%), which PdfCraft does not offer yet, so the two can draw a page at different sizes. Tracked with misc.prefs-page-display"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-page"
|
||
title = "Fit page"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.fit_one_page"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::actual_size_fit_width_and_fit_page", "crates/ui-egui/tests/view.rs::rail_page_display_menu_offers_acrobats_view_choices", "crates/ui-egui/tests/view.rs::fit_page_holds_zoom_across_mixed_page_sizes", "crates/ui-egui/tests/view.rs::single_page_fit_page_fits_the_shown_page"]
|
||
notes = "View ▸ Zoom to page level (⌘0) and the rail's Page display menu, which also offers Fit one full page (single-page view at Fit page). Scrolling views fit their largest page so mixed sizes never jump the zoom; single-page view fits the shown page"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-width"
|
||
title = "Fit width"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.fit_width_scrolling"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::actual_size_fit_width_and_fit_page", "crates/ui-egui/tests/view.rs::rail_page_display_menu_offers_acrobats_view_choices"]
|
||
notes = "View ▸ Fit to width (⌘2) and the rail's Page display menu's Fit to width scrolling (continuous scrolling at Fit width)"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-height"
|
||
title = "Fit height"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme"]
|
||
notes = "View ▸ Fit to height"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-visible"
|
||
title = "Fit visible content"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.fit_visible"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::fit_visible_zooms_to_the_content_width", "crates/ui-egui/src/zoom_snap.rs::ink_bounds_finds_the_marked_area"]
|
||
notes = "View ▸ Zoom ▸ Fit visible (⌘3): the current page's inked area (found by a quick low-resolution render) spans the window width; respects view rotation; drivable via the control channel's command"
|
||
|
||
[[feature]]
|
||
id = "view.marquee-zoom"
|
||
title = "Marquee zoom tool"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.marquee_zoom"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::marquee_zoom_and_snapshot"]
|
||
notes = "drag to fill the window with an area; click to zoom in"
|
||
|
||
[[feature]]
|
||
id = "view.dynamic-zoom"
|
||
title = "Dynamic zoom (drag to zoom)"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.loupe"
|
||
title = "Loupe magnifier tool"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.pan-and-zoom"
|
||
title = "Pan and zoom overview window"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.hand-tool"
|
||
title = "Hand tool for panning"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::the_hand_tool_pans_by_dragging", "crates/ui-egui/tests/viewer.rs::v_h_and_space_pick_the_quick_tools"]
|
||
notes = "drag to pan; Grab/Grabbing cursors; H picks it, V goes back to Select, Space held pans temporarily"
|
||
|
||
[[feature]]
|
||
id = "view.rotate-view"
|
||
title = "Rotate the view without changing the file"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::selection_works_in_every_view_rotation"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.read-mode"
|
||
title = "Read mode (hide tool chrome)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.read_mode"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::inspect_and_click_by_label_and_id", "crates/ui-egui/tests/commands.rs::every_registered_command_is_implemented"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.full-screen"
|
||
title = "Full screen mode"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.full_screen"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::full_screen_toggles_by_shortcut_and_command_and_escape_leaves"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.full-screen-presentation"
|
||
title = "Full-screen presentation options (auto-advance, loop, click to advance)"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.reflow"
|
||
title = "Reflow text to the window width"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.page-navigation"
|
||
title = "Page navigation (next, previous, first, last, go to page)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::state_and_view_options", "crates/ui-egui/tests/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/viewer.rs::two_page_view_steps_a_spread_at_a_time", "crates/ui-egui/tests/viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document"]
|
||
notes = "in two-page view, next and previous page move a whole spread (with or without a cover page); → / ← turn pages in every layout, ↓ / ↑ scroll a line and Page Down / Page Up a screen (the page in single-page view), as in Acrobat; in single-page view ↓ / ↑ turn the page once there is nothing left to scroll that way, like the wheel"
|
||
|
||
[[feature]]
|
||
id = "view.goto-page-label"
|
||
title = "Go to a page by its label (logical page numbers)"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme"]
|
||
notes = "the page box takes labels (iv, A-1) before numbers"
|
||
|
||
[[feature]]
|
||
id = "view.view-history"
|
||
title = "Previous view / next view"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||
notes = "⌘[ / ⌘] and View ▸ Page navigation"
|
||
|
||
[[feature]]
|
||
id = "view.follow-links"
|
||
title = "Follow links and destinations"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["link_open"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::clicking_a_link_goes_to_its_destination", "crates/ui-egui/tests/view.rs::link_destinations_scroll_to_their_position", "crates/render/src/inspect.rs::bookmarks_and_links_keep_their_destination_view", "crates/render/src/inspect.rs::links_read_bounded_local_remote_go_to_actions", "crates/automation/tests/automation.rs::local_gotor_link_open_applies_destinations_and_honors_root", "crates/ui-egui/tests/document_links.rs::a_local_gotor_link_opens_in_a_tab_and_applies_its_destination"]
|
||
notes = "GoTo and URI links plus local-PDF GoToR links: source-relative or local absolute /F paths, /UF preferred in file-spec dictionaries, explicit numeric page/view destinations, and internal tabs. link_open honors --root. Named remote destinations open at default view with a warning; /NewWindow is a tab hint. URL/network file systems, GoToE, Launch and other actions are not executed. Same-document destinations retain their supported /XYZ, /Fit, /FitH, /FitV and /FitR views; /FitB* is read as whole-page fit."
|
||
|
||
[[feature]]
|
||
id = "view.annotation-hover"
|
||
title = "Show comment text on hover"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/comments.rs::hovering_a_comment_shows_its_author_and_text"]
|
||
notes = "a popup with the author and the comment text, as Acrobat shows"
|
||
|
||
[[feature]]
|
||
id = "view.document-tabs"
|
||
title = "Tabbed documents"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_list"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::combining_files_opens_a_new_unsaved_tab", "crates/ui-egui/tests/control.rs::state_and_view_options", "crates/ui-egui/tests/viewer.rs::each_document_keeps_its_own_scroll_position", "crates/ui-egui/tests/viewer.rs::a_new_document_opens_at_the_top_after_a_relaunch"]
|
||
notes = "each tab keeps its own scroll position (#189), and a newly opened one starts at the top, not where an earlier session left the scroll"
|
||
|
||
[[feature]]
|
||
id = "view.multiple-windows"
|
||
title = "Multiple windows; drag a tab out into a window"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.split-window"
|
||
title = "Split window (two panes on one document)"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.spreadsheet-split"
|
||
title = "Spreadsheet split (four synchronised panes)"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.thumbnails-panel"
|
||
title = "Page thumbnails panel"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::theme_and_view_options_apply"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.thumbnail-context-ops"
|
||
title = "Page operations from the thumbnails panel (drag reorder, context menu)"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::dragging_pages_panel_thumbnails_reorders_pages", "crates/ui-egui/tests/viewer.rs::pages_panel_thumbnails_have_a_context_menu", "crates/ui-egui/tests/editing.rs::deleting_pages_from_the_pages_panel_menu", "crates/ui-egui/tests/editing.rs::the_delete_key_deletes_pages_picked_in_the_pages_panel_but_never_every_page"]
|
||
notes = "Drag the selection (or the page grabbed) to reorder, with a drop bar and edge scrolling; right-click offers Extract pages, Cut, Copy, Paste after and Delete pages (also the Delete key over the panel). Not yet: Rotate, Insert and Page labels in the menu, dragging pages between documents, or dropping files onto the panel."
|
||
|
||
[[feature]]
|
||
id = "view.bookmarks-panel"
|
||
title = "Bookmarks panel with navigation"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/control.rs::state_and_view_options", "crates/ui-egui/tests/view.rs::bookmark_destinations_scroll_to_their_position"]
|
||
notes = "A bookmark goes to its destination's position and zoom, not just its page. The panel shows the first 10,000 bookmarks, nested 33 levels deep at most, and says so; bookmark_list pages through every bookmark in the file (up to 33 levels deep)."
|
||
|
||
[[feature]]
|
||
id = "view.highlight-current-bookmark"
|
||
title = "Highlight the current bookmark"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.attachments-panel"
|
||
title = "Attachments panel: list, open and save embedded files"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/render/src/inspect.rs::attachments_of_both_kinds_are_listed_and_extracted"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.layers-panel"
|
||
title = "Layers panel: toggle optional content visibility"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/render/src/raster.rs::layer_override_hides_content", "crates/engine/src/tests.rs::layer_choices_survive_edits"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.layers-reset"
|
||
title = "Reset layers to initial visibility; list for all or current pages"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.destinations-panel"
|
||
title = "Named destinations panel"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.articles-panel"
|
||
title = "Articles panel and article reading"
|
||
area = "B"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.model-tree"
|
||
title = "3D model tree panel"
|
||
area = "B"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.page-labels-display"
|
||
title = "Show page labels (i, ii, 1, 2...)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/render/src/inspect.rs::roman_and_alpha_labels", "crates/automation/tests/automation.rs::open_inspect_render_and_find"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.find"
|
||
title = "Find text across the document"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["edit.find"]
|
||
tools = ["text_find"]
|
||
evidence = ["crates/ui-egui/tests/ui.rs::find_counts_matches_across_pages", "crates/ui-egui/tests/ui.rs::find_reports_no_matches", "crates/automation/tests/automation.rs::open_inspect_render_and_find", "crates/render/src/text.rs::identity_v_columns_are_searchable_in_reading_order"]
|
||
notes = "Horizontal and WMode 1 vertical text are grouped independently, including a horizontal heading above right-to-left vertical columns. Complex interleaved flows still use a geometric reading-order heuristic."
|
||
|
||
[[feature]]
|
||
id = "view.find-next-previous"
|
||
title = "Find next / previous match"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["edit.find"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::find_counts_matches_across_pages"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.find-options"
|
||
title = "Find options: whole words, case-sensitive"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["edit.find"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||
notes = "whole words only, case-sensitive"
|
||
|
||
[[feature]]
|
||
id = "view.find-in-comments-bookmarks"
|
||
title = "Find in comments and bookmarks"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.find-results-panel"
|
||
title = "Find results list"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["edit.advanced_search"]
|
||
tools = ["text_find"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::advanced_search_lists_results_with_context"]
|
||
notes = "Search panel (⇧⌘F): every match grouped by page with its context, whole words / case options, click to go; shares the find bar's search (closing the panel moves it to the bar)"
|
||
|
||
[[feature]]
|
||
id = "view.advanced-search"
|
||
title = "Advanced search window"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["edit.advanced_search"]
|
||
tools = ["text_find"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::advanced_search_lists_results_with_context"]
|
||
notes = "in the current document, as a side panel; searching other open documents, folders and indexes is still to come"
|
||
|
||
[[feature]]
|
||
id = "view.search-folder"
|
||
title = "Search all PDFs in a folder"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.search-boolean"
|
||
title = "Boolean, proximity and stemming search"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.search-index"
|
||
title = "Build and search a full-text index across files"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.embedded-index"
|
||
title = "Embedded search index"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.text-select"
|
||
title = "Select text in reading order"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::drag_selects_text_and_copy_returns_it", "crates/ui-egui/tests/ui.rs::selection_works_in_every_view_rotation", "crates/ui-egui/tests/ui_group/text_select.rs::quick_clicks_select_a_word_then_the_line_then_the_page", "crates/ui-egui/tests/ui_group/text_select.rs::quick_clicks_with_the_highlighter_mark_only_the_word", "crates/ui-egui/tests/ui_group/text_select.rs::shift_click_extends_the_selection_from_its_anchor", "crates/ui-egui/tests/comments.rs::selected_text_shows_actions_and_underlines_without_changing_tools", "crates/ui-egui/tests/comments.rs::selected_text_replace_action_opens_replacement_composer", "crates/ui-egui/tests/comments.rs::selected_text_comment_action_opens_note_composer"]
|
||
notes = "Drag to select; ⇧-click extends the selection to the click, keeping its anchor (same page only); with the Select tool, quick clicks select a word (two), the line (three) or all the text on the page (four). A floating action bar offers comment, highlight, underline, strikethrough, replace text and copy after selection. The markup and Redact tools mark a double-clicked word and stop there. Missing: selection across pages (dragging or ⇧-clicking onto another page), paragraph selection, and ⌘A across the whole document (it selects the current page). Acrobat's other selection context actions such as bookmark, link and export selection remain outside this bar."
|
||
|
||
[[feature]]
|
||
id = "view.column-select"
|
||
title = "Column select (Alt/Option-drag a rectangle of text)"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["edit.column_select"]
|
||
tools = ["text_extract"]
|
||
evidence = ["crates/render/src/text.rs::a_column_selection_takes_one_table_column", "crates/render/src/text.rs::a_column_selection_across_columns_reads_row_by_row", "crates/render/src/text.rs::right_to_left_rows_read_right_to_left", "crates/render/src/text.rs::vertical_lines_keep_reading_order", "crates/ui-egui/tests/ui_group/column_select.rs::alt_drag_selects_only_the_text_inside_the_rectangle", "crates/ui-egui/tests/ui_group/column_select.rs::the_column_select_tool_needs_no_modifier", "crates/ui-egui/tests/ui_group/column_select.rs::alt_drag_with_the_highlighter_marks_only_the_column", "crates/automation/tests/automation.rs::text_extract_with_a_rect_takes_one_column"]
|
||
notes = "Issue #740. Holding Alt (Option on macOS) when a drag starts selects only the text inside the rectangle drawn, with the Select tool, the text markup tools and Redact (with Highlight and Redact the drag must start on text, since off text they draw an area box). Edit ▸ Column select does the same with a plain drag. A glyph counts when its centre is inside. Copy gives one row per visual line, top to bottom; pieces side by side (table cells) are separated by a tab, right to left in right-to-left rows; vertical text keeps reading order. text_extract takes the same rectangle as rect. Platforms: on Linux desktops whose window manager moves windows with Alt-drag (Xfce, Cinnamon and MATE by default; KDE Plasma 5 and other setups with Alt as the window modifier; GNOME and Plasma 6 use Super) the drag never reaches PdfCraft, so use Edit ▸ Column select or change that setting. On Windows, releasing Alt does not open a system menu (the window has no native menu). The web build has the same Linux caveat in the browser. Checked by hand on Linux (Fedora) with the Column select tool; Alt/Option-drag, and macOS and Windows generally, rest on the kittest tests. Missing: selection across pages, ⇧-click extending a column, a default shortcut, and a check of Acrobat's own copied-text format (not observed)."
|
||
|
||
[[feature]]
|
||
id = "view.copy-text"
|
||
title = "Copy selected text"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::drag_selects_text_and_copy_returns_it"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.select-all"
|
||
title = "Select all text on a page"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||
notes = "⌘A on the current page"
|
||
|
||
[[feature]]
|
||
id = "view.copy-with-layout"
|
||
title = "Copy text keeping layout"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.snapshot"
|
||
title = "Snapshot tool (copy a region as an image)"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["edit.snapshot"]
|
||
tools = ["page_render"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::marquee_zoom_and_snapshot"]
|
||
notes = "copies the area at the current zoom to the clipboard as an image"
|
||
|
||
[[feature]]
|
||
id = "view.copy-image"
|
||
title = "Select an image on the page and copy it"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::a_clicked_picture_is_selected_and_copied_in_its_original_format", "crates/ui-egui/tests/viewer.rs::right_click_on_a_picture_copies_it_at_full_size", "crates/ui-egui/tests/viewer.rs::a_picture_inside_form_artwork_under_text_is_selected_and_copied", "crates/engine/src/tests.rs::copy_image_finds_a_picture_inside_form_artwork_under_text"]
|
||
notes = "Select tool: a click selects a raster image (also inside Form artwork or under text) and ⌘C copies it as a file in its original format (JPEG as stored, else PNG); right-click ▸ Copy Image copies its pixels at full resolution. Missing: inline images (BI/ID/EI), JPEG 2000/JBIG2/CCITT images, multi-format clipboard (file and bitmap at once), the web build"
|
||
|
||
[[feature]]
|
||
id = "view.rulers"
|
||
title = "Rulers"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.grid"
|
||
title = "Grid and snap to grid"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.guides"
|
||
title = "Guides"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.line-weights"
|
||
title = "Show line weights"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.cursor-coordinates"
|
||
title = "Cursor coordinates"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.theme-light-dark"
|
||
title = "Light and dark themes"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.theme", "view.theme.light", "view.theme.dark"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui_group/theme.rs::toolbar_and_view_menu_share_three_state_preferences", "crates/ui-egui/src/theme.rs::text_is_readable_on_every_surface"]
|
||
notes = "Toolbar and Display theme share three choices and a preference setter; manual choices exit system mode. Every text colour meets WCAG AA (4.5:1) on every surface in both themes; glyphs are pixel-aligned for sharper text at 100–150 % scaling."
|
||
|
||
[[feature]]
|
||
id = "view.theme-system"
|
||
title = "Follow the system theme"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.theme.system"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui_group/theme.rs::toolbar_and_view_menu_share_three_state_preferences", "crates/ui-egui/tests/ui_group/theme.rs::saved_preferences_restore_with_legacy_light_dark_settings", "crates/ui-egui/tests/control.rs::theme_commands_and_options_report_preference_and_effective_colours"]
|
||
notes = "Toolbar, Display theme and --theme system share a persisted System preference that follows OS changes without losing the selected mode; ui.state reports preference and resolved colours separately."
|
||
|
||
[[feature]]
|
||
id = "view.home-view"
|
||
title = "Home view with recommended tools and recent files"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["file.pin_folder"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::home_shows_welcome_and_tools", "crates/ui-egui/tests/folders.rs::a_pinned_folder_lists_its_newest_pdfs_on_home", "crates/ui-egui/tests/folders.rs::clicking_a_pinned_pdf_opens_it_and_adds_it_to_recent", "crates/ui-egui/tests/folders.rs::pinned_folders_survive_a_restart"]
|
||
notes = "Desktop: folders pinned to Home (File ▸ Pin folder to Home…, up to 8) list their newest PDFs, read on a worker thread and refreshed every few seconds while Home shows; a folder that can't be read (moved, offline synced drive) stays pinned and says so. Pinned folders are local paths, so a cloud drive works through its synced folder (iCloud Drive, OneDrive, Dropbox). Not yet: subfolders, search within a pinned folder, the web build."
|
||
|
||
[[feature]]
|
||
id = "view.all-tools"
|
||
title = "All tools catalogue with milestone badges"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::every_catalog_tool_is_listed_after_view_more", "crates/engine/src/commands.rs::every_ready_catalogue_item_is_a_registered_command"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.command-palette"
|
||
title = "Command palette (⌘K) over every command"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.palette"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::the_palette_lists_commands_with_shortcuts_and_runs_them"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.keyboard-shortcuts"
|
||
title = "Platform keyboard shortcuts for registered commands"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::shortcuts_run_registered_commands", "crates/engine/src/commands.rs::shortcut_labels_follow_platform_conventions"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.shortcuts-dialog"
|
||
title = "Keyboard shortcuts reference dialog"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["help.shortcuts"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::the_shortcuts_dialog_lists_the_real_bindings"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.menus-from-registry"
|
||
title = "Menus generated from the command registry"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::the_pages_menu_comes_from_the_registry"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.about-dialog"
|
||
title = "About dialog"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["help.about"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::the_about_dialog_shows_the_running_version"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.check-for-updates"
|
||
title = "Help ▸ Check for updates"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = ["help.check_updates"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui_group/updates.rs::a_newer_release_is_offered_for_download", "crates/ui-egui/tests/ui_group/updates.rs::nothing_is_asked_until_the_user_checks", "apps/pdfcraft/src/updates.rs::answers_are_read_and_only_our_release_pages_are_offered"]
|
||
notes = "asks GitHub Releases on request and offers the release page. By the owner's decision there is no check at start and no in-place install (Acrobat has both)"
|
||
|
||
[[feature]]
|
||
id = "view.single-key-accelerators"
|
||
title = "Single-key tool accelerators"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.customize-quick-tools"
|
||
title = "Customise the quick-action toolbar and pinned tools"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.restriction-notice"
|
||
title = "Message bar for restricted documents"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::restricted_documents_show_a_notice_and_block_page_changes"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.pdfa-view-mode"
|
||
title = "Read-only view for PDF/A files with an Enable editing bar"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.show-page-boxes"
|
||
title = "Show art, trim and bleed boxes"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.web-app"
|
||
title = "Run in the browser (WebAssembly)"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/pickers.rs::a_startup_url_opens_in_front_when_the_user_has_not_started_working", "crates/ui-egui/tests/pickers.rs::a_late_startup_url_preserves_the_current_edit_workspace_and_save_target", "crates/ui-egui/tests/pickers.rs::deliberate_browser_file_opens_still_activate_the_new_tab"]
|
||
notes = "web build works; a late startup ?file= download opens in a background tab after the user starts working, preserving the active document and Save target (#171). IndexedDB/OPFS recent list and download flow are M3.10"
|
||
|
||
[[feature]]
|
||
id = "view.large-document-performance"
|
||
title = "Smooth scrolling on 500-page documents"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/perf.rs::scrolling_a_500_page_document_stays_within_the_frame_budget", "crates/ui-egui/tests/perf.rs::panels_with_hundreds_of_items_stay_within_the_frame_budget"]
|
||
notes = "UI work per frame ~6 ms scrolling 500 pages (comments panel with 500 cards ~6 ms, thumbnails ~2 ms); rendering is off-thread and tiled"
|
||
|
||
# ==================================================================================================
|
||
# C. Content editing
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "edit.text-edit"
|
||
title = "Edit existing text in place"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_lines", "text_paragraphs", "text_edit", "text_edit_layout", "text_font_files", "text_font_import", "text_fonts"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools", "crates/edit/src/tests.rs::a_line_whose_operator_starts_the_next_stream_can_be_edited", "crates/automation/tests/automation.rs::a_line_split_across_content_streams_is_listed_and_edited", "crates/ui-egui/tests/editing.rs::selected_text_formatting_keeps_the_rest_of_the_paragraph_and_reopens", "crates/edit/src/tests.rs::marked_paragraph_boundaries_and_cross_stream_transforms_survive_rewrite", "crates/automation/tests/automation.rs::explicit_font_and_disjoint_ranges_save_reopen_through_tools", "crates/edit/src/tests.rs::disjoint_style_ranges_preserve_unselected_runs_and_validate_utf8_atomically", "crates/engine/tests/document_fonts.rs::opentype_cff_document_font_preserves_program_codes_and_rendering_after_reopen", "crates/ui-egui/tests/editing.rs::cropped_images_do_not_intercept_text_and_stay_inside_each_page", "crates/automation/tests/automation.rs::clipped_image_bounds_and_text_edit_save_reopen_through_tools", "crates/edit/src/tests.rs::mixed_styles_and_matrix_sizes_form_visual_regions_without_merging_columns", "crates/edit/src/tests.rs::rich_region_edits_preserve_styles_positions_and_delete_all_runs_after_reopen", "crates/ui-egui/tests/editing.rs::one_editor_contains_mixed_styles_and_visual_rows", "crates/ui-egui/tests/editing.rs::inline_editor_preserves_source_font_and_previews_live_bounds", "crates/fonts/src/pdf.rs::mac_subset_cmap_uses_pdf_unicode_without_changing_source_program", "crates/automation/tests/automation.rs::mixed_visual_region_edit_preview_save_reopen_and_undo_through_tools"]
|
||
notes = "click a paragraph box and retype it in place (font reused or Helvetica substituted, colour and spacing kept, rewrapped); a page's /Contents pieces are parsed joined, so a line whose operands and operator sit in different pieces is listed and edited (lines and paragraphs still don't join across pieces; Edit image still parses each piece alone); drag a box to move it or its right-edge handle to rewrap it (edit.text-move-resize); per-character formatting changes are next Since #1116: mixed-style visual regions retain source rows, subset encodings, colour and baseline anchors in one textless-background editor. Explicit paragraph/heading scopes constrain grouping; compatible graphics planes permit cross-stream regions. Full matching document fonts complete absent subset glyphs; installed/file selection, TrueType/OpenType-CFF embedding and disjoint UTF-8 range styles have save/reopen coverage. Single-face OpenType shaping/bidi retains logical text through ToUnicode/ActualText. Legacy non-SFNT preview, mixed-font/style contextual runs, full structure-tree preservation and author intent in untagged PDFs remain limited; see docs/editing-existing-text.md"
|
||
|
||
[[feature]]
|
||
id = "edit.text-reflow"
|
||
title = "Reflow edited text inside its paragraph box"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::japanese_fallback_reports_the_face_after_save_and_reopen", "crates/edit/src/tests.rs::japanese_paragraph_keeps_ideographic_spaces", "crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "edited paragraphs are rewrapped to the box width with the font's own advance widths and the original line spacing, starting at the original text matrix; text state and fill colour restored after; a single line grows to the right margin; U+3000 ideographic spaces are preserved (including paragraph edges and repeated spaces); Japanese Type 3 fallback reports its face through FontDescriptor/FontName after editing and save/reopen; source family/weight matching remains limited (#184)"
|
||
|
||
[[feature]]
|
||
id = "edit.text-move-resize"
|
||
title = "Move and resize text boxes"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_move_and_rewrap_to_a_new_width", "crates/ui-egui/tests/editing.rs::dragging_a_paragraph_moves_it_and_its_edge_rewraps_it", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "existing paragraphs: drag the box to move it, drag the handle on its right edge to rewrap it to a new width (upright pages); the move is applied in page space through the paragraph's CTM, in its own q … Q; a click still opens it for typing (#40)"
|
||
|
||
[[feature]]
|
||
id = "edit.paragraph-detection"
|
||
title = "Detect paragraphs and text blocks for editing"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_paragraphs"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "lines grouped into paragraphs by stream, font, size, left alignment and regular baseline spacing; hyphenated line ends are joined"
|
||
|
||
[[feature]]
|
||
id = "edit.font-reuse-substitution"
|
||
title = "Reuse embedded fonts or substitute with a warning"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::missing_glyphs_substitute_helvetica_and_impossible_text_is_refused", "crates/fonts/src/pdf.rs::subset_fonts_only_encode_the_glyphs_they_have", "crates/fonts/src/pdf.rs::composite_fonts_use_to_unicode"]
|
||
notes = "the line's font is reused when every character has a code and a glyph (ToUnicode, encodings, Differences; subset glyph checks); otherwise Helvetica (WinAnsi) is used and reported; text no font can show is refused"
|
||
|
||
[[feature]]
|
||
id = "edit.text-format-font"
|
||
title = "Change font, size and colour"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text", "edit.text"]
|
||
tools = ["text_edit", "content_update"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "existing paragraphs and added text: font family (Helvetica, Times, Courier) with bold and italic, size, colour; existing paragraphs are rewrapped"
|
||
|
||
[[feature]]
|
||
id = "edit.text-format-style"
|
||
title = "Bold, italic, underline"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "bold and italic through the standard families; underline drawn under each line in the text colour"
|
||
|
||
[[feature]]
|
||
id = "edit.text-super-subscript"
|
||
title = "Superscript and subscript"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.paragraph-alignment"
|
||
title = "Paragraph alignment (left, centre, right, justify)"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text", "edit.text"]
|
||
tools = ["text_edit", "content_update"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "left, centre, right and justify (word spacing per line, last line left) for existing paragraphs and added text"
|
||
|
||
[[feature]]
|
||
id = "edit.line-paragraph-spacing"
|
||
title = "Line and paragraph spacing"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "line spacing (1, 1.15, 1.5, 2 × size or any multiple); space between paragraphs is not adjustable yet"
|
||
|
||
[[feature]]
|
||
id = "edit.char-spacing-scale"
|
||
title = "Character spacing and horizontal scaling"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "character spacing in points and horizontal scaling in percent for a paragraph (Tc, Tz), rewrapped"
|
||
|
||
[[feature]]
|
||
id = "edit.lists"
|
||
title = "Bulleted and numbered lists"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.add-text"
|
||
title = "Add a new text box as page content"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.text"]
|
||
tools = ["page_add_text", "content_update"]
|
||
evidence = ["crates/ui-egui/tests/content.rs::typing_moving_styling_and_deleting_added_content", "crates/ui-egui/tests/content.rs::clicking_elsewhere_keeps_the_text_and_starts_a_new_box", "crates/automation/tests/automation.rs::adding_content_through_tools", "crates/edit/src/tests.rs::added_text_and_images_are_page_content_that_stays_editable", "crates/engine/src/tests.rs::added_text_and_images_render_and_stay_editable", "crates/edit/src/tests.rs::added_arabic_text_is_shaped_in_display_order_and_stays_searchable", "crates/automation/tests/automation.rs::thai_text_through_tools_saves_reopens_extracts_and_renders", "crates/ui-egui/tests/content.rs::typing_thai_text_keeps_marks_and_commits_to_page", "crates/automation/tests/automation.rs::added_arabic_text_keeps_copy_search_and_editor_order_after_retyping_and_reopen"]
|
||
notes = "page content, editable later (retype, move, restyle); standard fonts, Windows-1252 text. Arabic text is shaped with the craft-fonts Arab face (Type 3 outlines with ToUnicode) and laid out in bidirectional display order; per-line ActualText keeps copy, search and editor text in typing order through retyping and save/reopen; it is drawn regular whatever the bold/italic setting, Hebrew and other right-to-left scripts are not covered, and typing in the box is still left to right (misc.rtl-ui). Thai Add Text uses the craft-fonts Thai face, with shaped clusters and ToUnicode; Thai is regular style, wrapping preserves clusters but is not dictionary-based, and forms/Fill and Sign are not covered by this change. Each click on the page keeps the box being typed and starts a new one; Done and Discard beside the box"
|
||
|
||
[[feature]]
|
||
id = "edit.find-replace"
|
||
title = "Find and replace in edit mode"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.rtl-cjk-editing"
|
||
title = "Edit right-to-left and CJK text"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["text_lines", "text_paragraphs", "text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::hebrew_lines_read_in_typing_order", "crates/edit/src/tests.rs::hebrew_edits_are_drawn_right_to_left_and_keep_their_right_edge", "crates/edit/src/tests.rs::hebrew_letters_the_page_font_lacks_use_the_hebrew_face", "crates/edit/src/bidi.rs::hebrew_sentences_numbers_latin_and_brackets_round_trip", "crates/edit/src/tests.rs::arabic_actual_text_keeps_typing_order_when_opened_for_editing", "crates/automation/tests/automation.rs::arabic_actual_text_stays_logical_through_editor_tools_and_save_reopen", "crates/automation/tests/automation.rs::added_arabic_text_keeps_copy_search_and_editor_order_after_retyping_and_reopen"]
|
||
notes = "Added Arabic copy/search keeps typing order through per-line ActualText. Arabic/Hebrew ActualText is preserved in typing order rather than reordered twice; font-independent tests cover marks, numbers, Latin, reopening and complete replacement. This does not add Arabic fallback fonts or bidi caret/selection. Hebrew: lines and paragraphs read in typing order and are written back in drawing order, right-aligned by default; letters the page's font lacks use the craft-fonts Noto Sans Hebrew face as Type 3 glyphs. Japanese uses its Type 3 fallback. Missing: Hebrew points (niqqud) in replacement text, writing Arabic (shaping), right-to-left typing in the edit box (misc.rtl-ui), lines a producer draws word by word from the right, vertical CJK. Also (#1116): bounded single-face existing-text engine coverage now exists (Arabic marks/ligatures/mixed bidi and explicit full-face Chinese subset completion), with private keyboard/save/reopen checks. General mixed-font/style contextual runs, bidi caret/selection, legacy programs and release font availability remain incomplete, so broader feature remains planned"
|
||
|
||
[[feature]]
|
||
id = "edit.rotated-text-editing"
|
||
title = "Edit rotated text"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.keep-tags-on-edit"
|
||
title = "Keep structure tags when editing"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.image-add"
|
||
title = "Add an image"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.image"]
|
||
tools = ["page_add_image"]
|
||
evidence = ["crates/ui-egui/tests/content.rs::typing_moving_styling_and_deleting_added_content", "crates/automation/tests/automation.rs::adding_content_through_tools", "crates/edit/src/tests.rs::added_text_and_images_are_page_content_that_stays_editable", "crates/engine/src/tests.rs::added_text_and_images_render_and_stay_editable"]
|
||
notes = "PNG, JPEG, TIFF, GIF, BMP; centred at natural size or in a rect"
|
||
|
||
[[feature]]
|
||
id = "edit.image-replace"
|
||
title = "Replace an image (keep position and size)"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text", "edit.image"]
|
||
tools = ["image_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools"]
|
||
notes = "existing page images (right-click ▸ Replace Image) and added images: the new picture is drawn in the same place"
|
||
|
||
[[feature]]
|
||
id = "edit.image-crop"
|
||
title = "Crop an image"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.image"]
|
||
tools = ["content_update"]
|
||
evidence = ["crates/engine/src/tests.rs::added_images_rotate_flip_and_crop_as_drawn", "crates/automation/tests/automation.rs::adding_content_through_tools"]
|
||
notes = "for added images: trim each side, clipped"
|
||
|
||
[[feature]]
|
||
id = "edit.image-rotate-flip"
|
||
title = "Rotate and flip images"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["image_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools"]
|
||
notes = "existing raster images and grouped Form artwork: rotate either way about the centre, flip horizontally or vertically (a q/cm/Q wrapper); Forms stay intact as groups; added images too"
|
||
|
||
[[feature]]
|
||
id = "edit.image-move-resize"
|
||
title = "Move and resize images"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["image_edit", "image_hit"]
|
||
evidence = ["crates/edit/src/tests.rs::private_image_crop_with_intervening_state_resizes_across_streams", "crates/automation/tests/automation.rs::private_crops_with_nested_state_resize_save_reopen_through_tools", "crates/edit/src/tests.rs::form_artwork_edits_only_the_selected_placement", "crates/ui-egui/tests/editing.rs::editing_existing_form_artwork_on_the_page", "crates/automation/tests/automation.rs::editing_form_artwork_through_tools", "crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools", "crates/edit/src/tests.rs::curved_image_clips_have_analytic_bounds_and_holes_do_not_capture_points", "crates/edit/src/tests.rs::cropped_image_resizing_moves_private_clips_and_keeps_shared_ones", "crates/ui-egui/tests/editing.rs::cropped_image_corner_resize_keeps_opposite_corner_and_scales_crop", "crates/edit/src/tests.rs::image_selection_bounds_respect_clips_and_restore_graphics_state", "crates/ui-egui/tests/editing.rs::cropped_images_do_not_intercept_text_and_stay_inside_each_page", "crates/automation/tests/automation.rs::clipped_image_bounds_and_text_edit_save_reopen_through_tools"]
|
||
notes = "existing raster images and grouped Form artwork: select, drag to move, drag a corner to resize keeping proportions; Delete removes one placement. Forms include their nested content as a group; individual internal elements and inline images are not supported. See docs/editing-images.md; added images too Since #1116: raster images and grouped Forms move/resize/turn as placements; selected grips own the gesture and opposite anchor, with no PDF writes during movement. Curved path frames use analytic Bezier extrema and hit testing evaluates actual nonzero/even-odd fill rules, excluding holes; intersections can retain a loose frame. Selection and resize use the visible (clipped) frame; exclusive image clips move with their pixels across nested states, intervening operators and content streams; shared clips stay fixed. Inline images and Form internals remain unsupported; see docs/editing-images.md"
|
||
|
||
[[feature]]
|
||
id = "edit.image-extract"
|
||
title = "Save an image to a file"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["image_save"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools", "crates/create/src/tests.rs::images_export_as_jpeg_unchanged_and_others_as_png"]
|
||
notes = "right-click an image ▸ Save Image As: JPEG unchanged, others PNG"
|
||
|
||
[[feature]]
|
||
id = "edit.image-external-editor"
|
||
title = "Edit an image in an external application"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.arrange-objects"
|
||
title = "Arrange objects (bring forward, send backward)"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.align-distribute-objects"
|
||
title = "Align and distribute objects"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.vector-edit"
|
||
title = "Select, move, scale, recolour and delete vector paths"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.edit-object-tool"
|
||
title = "Edit object tool for any page object"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["object_list", "object_move"]
|
||
evidence = ["crates/ui-egui/tests/object_selection.rs::marquee_and_mixed_group_drag_move_once_and_undo_together", "crates/automation/tests/automation.rs::mixed_objects_move_through_tools_with_generation_and_one_undo", "crates/edit/src/tests.rs::group_moves_preserve_glyph_bytes_and_unselected_text_state", "crates/engine/src/tests.rs::mixed_object_moves_are_one_undo_step_and_survive_save_reopen"]
|
||
notes = "Same-page mixed selection (#844): Shift/Ctrl/Command-click toggles, empty-space marquee (additive with modifiers), drag previews and one atomic undo step for existing paragraphs, Image/Form XObjects and added text/images. Source glyph bytes, fonts and kerning retained; unselected text and shared Form resources stay put. Generation-scoped references with optional automation guard; count and geometry bounded; unsupported vertical/clipping text refused without partial changes. Still missing: arbitrary paths, inline images, cross-page movement, group resizing, arrange/align and a general object editor. See docs/editing-multiple-objects.md."
|
||
|
||
[[feature]]
|
||
id = "edit.object-properties"
|
||
title = "Object properties (colour space, font, convert colour)"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.link-create"
|
||
title = "Create web and page links"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.link"]
|
||
tools = ["link_add"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/pdf_links.rs::drawing_editing_and_deleting_links", "crates/automation/tests/automation.rs::links_through_tools", "crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed"]
|
||
notes = "drag a rectangle; go to a page or open a web page"
|
||
|
||
[[feature]]
|
||
id = "edit.link-properties"
|
||
title = "Link appearance and properties"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.link"]
|
||
tools = ["link_edit"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/pdf_links.rs::drawing_editing_and_deleting_links", "crates/automation/tests/automation.rs::links_through_tools", "crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed"]
|
||
notes = "visible/invisible rectangle, highlight, thickness, solid/dashed/underline and colour; supported page/URI action editing. Appearance-only edits preserve unsupported actions, including GoToR."
|
||
|
||
[[feature]]
|
||
id = "edit.links-from-urls"
|
||
title = "Create links from URLs in the text"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.links_from_urls"]
|
||
tools = ["links_from_urls"]
|
||
evidence = ["crates/engine/src/tests.rs::links_from_urls_and_link_edits", "crates/automation/tests/automation.rs::links_through_tools", "crates/annot/src/tests.rs::urls_are_found_in_text"]
|
||
notes = "http(s):// and www. addresses; already-linked text skipped"
|
||
|
||
[[feature]]
|
||
id = "edit.remove-all-links"
|
||
title = "Remove all links"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.remove_links"]
|
||
tools = ["links_remove"]
|
||
evidence = [
|
||
"crates/engine/src/tests.rs::links_from_urls_and_link_edits",
|
||
"crates/automation/tests/automation.rs::links_through_tools",
|
||
"crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed",
|
||
"crates/ui-egui/tests/ui_group/pdf_links.rs::removing_all_links_and_undo",
|
||
"crates/ui-egui/tests/ui_group/pdf_links.rs::the_link_context_menu_shows_only_for_a_right_click_on_a_link",
|
||
]
|
||
notes = "removes all link annotations across the document or on specified pages (links_remove pages); a right-click on a link offers Properties, Delete and Remove all links; undoable, with a toast giving the count"
|
||
|
||
[[feature]]
|
||
id = "edit.action-goto"
|
||
title = "Go-to-page-view action (with named destinations)"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.link"]
|
||
tools = ["link_add"]
|
||
evidence = ["crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed", "crates/ui-egui/tests/ui_group/pdf_links.rs::drawing_editing_and_deleting_links"]
|
||
notes = "go to a page (Fit view); named destinations and zoom pending"
|
||
|
||
[[feature]]
|
||
id = "edit.action-uri"
|
||
title = "Open-a-web-link action"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.link"]
|
||
tools = ["link_add"]
|
||
evidence = ["crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed", "crates/automation/tests/automation.rs::links_through_tools", "crates/ui-egui/tests/document_links.rs::a_link_asks_first_and_opens_when_allowed", "crates/ui-egui/tests/document_links.rs::a_button_uri_action_asks_first", "crates/ui-egui/tests/document_links.rs::an_email_link_cannot_attach_a_local_file", "crates/ui-egui/tests/document_links.rs::a_lookalike_host_is_shown_in_punycode_and_flagged"]
|
||
notes = "Opening a document's link or button URI asks first; only http, https and mailto addresses are offered (#90, #91), and an email address that asks the email app to attach or insert a local file is refused. The prompt shows the host as the browser receives it: international hosts in punycode, lookalikes that mix alphabets flagged, numeric hosts in their usual form."
|
||
|
||
[[feature]]
|
||
id = "edit.action-launch"
|
||
title = "Open-a-file action"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.action-named"
|
||
title = "Execute-a-menu-item action (safe list)"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.action-set-ocg-state"
|
||
title = "Set-layer-visibility action"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui_group/layer_actions.rs::links_set_layer_visibility", "crates/ui-egui/tests/ui_group/layer_actions.rs::push_buttons_set_layer_visibility", "crates/engine/src/tests.rs::set_layer_actions_change_what_renders", "crates/engine/src/tests.rs::set_layer_actions_toggle_in_order_and_keep_radio_groups", "crates/render/src/inspect.rs::links_read_set_layer_actions", "crates/forms/src/tests.rs::push_buttons_read_set_layer_actions"]
|
||
notes = "Links and push buttons run it: ON, OFF and Toggle in order, by optional content group, and with /PreserveRB (the default) a layer turned on turns off the rest of its /RBGroups group. Like the Layers panel it changes the view, not the document (no undo step, not saved). Not yet: creating the action (Link Properties, a field's Actions), bookmarks and other triggers, /Next chains, JavaScript ocg.state, and the Layers panel keeping radio-button groups itself."
|
||
|
||
[[feature]]
|
||
id = "edit.action-show-hide-field"
|
||
title = "Show/hide-a-field action"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/buttons.rs::hide_actions_hide_and_show_fields", "crates/forms/src/tests.rs::push_buttons_read_hide_actions"]
|
||
notes = "A push button's Hide action (/T names, field or widget references, arrays; /H) hides or shows the fields and their kids; hidden widgets take no clicks, tab stops or highlights. A widget reference acts on its whole field, and only form fields are hidden, not other annotations."
|
||
|
||
[[feature]]
|
||
id = "edit.action-javascript"
|
||
title = "Run-a-JavaScript action"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.action-thread"
|
||
title = "Read-an-article action"
|
||
area = "C"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.action-goto-embedded"
|
||
title = "Go to a target in an embedded file (GoToE)"
|
||
area = "C"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.action-media"
|
||
title = "Play sound / media / rich-media actions"
|
||
area = "C"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-add"
|
||
title = "Add header and footer"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/marks.rs::header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset", "crates/edit/src/tests.rs::header_and_footer_are_drawn_in_display_space_and_wrap_the_original_content", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = "Helvetica only (no font choice yet); drawn in display space on rotated pages; artifact-tagged"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-update-remove"
|
||
title = "Update or remove header and footer"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer.update", "edit.header_footer.remove"]
|
||
tools = ["doc_header_footer", "doc_remove_marks"]
|
||
evidence = ["crates/edit/src/tests.rs::replace_and_remove_restore_the_original_content", "crates/ui-egui/tests/ui_group/marks.rs::header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset"]
|
||
notes = "recognises PdfCraft marks; Acrobat-made headers are not detected yet"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-tokens"
|
||
title = "Page number and date tokens in header and footer"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/edit/src/tests.rs::tokens_expand", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = "<<1>>, <<n>>, <<1 of n>>, <<Page 1 of n>>, dates, Bates"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-ranges"
|
||
title = "Header and footer page ranges (all, range, odd, even)"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/ui-egui/src/marks_ui.rs::page_ranges_with_subsets", "crates/ui-egui/tests/ui_group/marks.rs::header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset"]
|
||
notes = "all / from–to, all / even / odd"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-shrink"
|
||
title = "Shrink document to avoid overwriting content"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-add-text"
|
||
title = "Add a text watermark"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.watermark"]
|
||
tools = ["doc_watermark"]
|
||
evidence = ["crates/edit/src/tests.rs::watermarks_rotate_fade_and_can_go_behind", "crates/ui-egui/tests/ui_group/marks.rs::watermark_and_background_dialogs", "crates/automation/tests/automation.rs::headers_watermarks_and_backgrounds_through_tools"]
|
||
notes = "Helvetica; fit-to-page or a size"
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-add-file"
|
||
title = "Add a watermark from an image or PDF page"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.watermark"]
|
||
tools = ["doc_watermark"]
|
||
evidence = ["crates/engine/src/tests.rs::backgrounds_and_watermarks_from_files"]
|
||
notes = "an image or a page of a PDF, scaled relative to the page, rotated, with opacity, behind or on top"
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-update-remove"
|
||
title = "Update or remove watermarks"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.watermark.update", "edit.watermark.remove"]
|
||
tools = ["doc_watermark", "doc_remove_marks"]
|
||
evidence = ["crates/edit/src/tests.rs::replace_and_remove_restore_the_original_content", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-appearance"
|
||
title = "Watermark rotation, opacity, scale, position, print/screen visibility"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.watermark"]
|
||
tools = ["doc_watermark"]
|
||
evidence = ["crates/edit/src/tests.rs::watermarks_rotate_fade_and_can_go_behind", "crates/edit/src/tests.rs::watermark_offset_moves_the_centre_right_and_up", "crates/edit/src/tests.rs::a_watermark_offset_that_is_not_finite_is_refused", "crates/automation/tests/automation.rs::watermarks_move_from_the_page_centre_through_tools", "crates/ui-egui/src/marks_ui.rs::the_watermark_edit_carries_the_offset"]
|
||
notes = "rotation, opacity, behind/on top, and a horizontal/vertical offset from the page centre in points (dialog and offset_x/offset_y); no offset from the page edge or in page units, and no print/screen-only variants yet"
|
||
|
||
[[feature]]
|
||
id = "edit.background-add"
|
||
title = "Add a page background (colour or file)"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.background"]
|
||
tools = ["doc_background"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/marks.rs::watermark_and_background_dialogs", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove", "crates/engine/src/tests.rs::backgrounds_and_watermarks_from_files", "crates/ui-egui/tests/ui_group/marks.rs::backgrounds_and_watermarks_from_a_file", "crates/organize/src/tests.rs::a_page_becomes_a_form_xobject_upright"]
|
||
notes = "colour, or a picture from a file (an image or a page of a PDF) scaled relative to the page"
|
||
|
||
[[feature]]
|
||
id = "edit.background-update-remove"
|
||
title = "Update or remove backgrounds"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.background.update", "edit.background.remove"]
|
||
tools = ["doc_background", "doc_remove_marks"]
|
||
evidence = ["crates/edit/src/tests.rs::replace_and_remove_restore_the_original_content", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.decoration-profiles"
|
||
title = "Saved header/footer and watermark settings"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.bates-numbering"
|
||
title = "Bates numbering"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.bates", "edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/edit/src/tests.rs::tokens_expand"]
|
||
notes = "Bates ▸ Add opens Header & Footer with a Bates number; no multi-file batch yet"
|
||
|
||
[[feature]]
|
||
id = "edit.bates-multi-file"
|
||
title = "Bates numbering across multiple files"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.bates-remove"
|
||
title = "Remove Bates numbering"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.spell-check"
|
||
title = "Spell check in comments, form fields and edited text"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.spell-dictionaries"
|
||
title = "Spelling dictionaries per language and a user dictionary"
|
||
area = "C"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.format-panel"
|
||
title = "Format panel for edited text"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "Edit a PDF ▸ Format text shows the paragraph being edited (family, bold/italic guessed from its font, size) and applies changes at once as one undo step each, keeping the paragraph open"
|
||
|
||
[[feature]]
|
||
id = "edit.adobe-express-edits"
|
||
title = "Adobe Express image edits (remove background, generative fill)"
|
||
area = "C"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "cloud-only; images open in an external editor instead"
|
||
|
||
# ==================================================================================================
|
||
# D. Organize pages and documents
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "organize.organize-grid"
|
||
title = "Organize pages thumbnail grid"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.organize"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::shift_click_selects_a_range_and_moves_it", "crates/ui-egui/tests/editing.rs::organize_tool_opens_the_overview_directly_and_can_be_reentered", "crates/ui-egui/tests/control.rs::organize_overview_is_reachable_through_the_control_channel"]
|
||
notes = "Opening the Organize pages tool enters the overview directly. Hovered, focused and selected pages expose individual rotate/delete actions; the toolbar acts on the selection."
|
||
|
||
[[feature]]
|
||
id = "organize.multi-select-pages"
|
||
title = "Select pages with click, ⌘-click and ⇧-click"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.organize"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::command_click_toggles_and_rotation_applies_to_selection", "crates/ui-egui/tests/editing.rs::shift_click_selects_a_range_and_moves_it", "crates/ui-egui/tests/editing.rs::organize_select_all_shortcut_preserves_current_page_and_sets_range_anchor", "crates/ui-egui/tests/control.rs::select_all_key_selects_every_page_in_organize", "crates/ui-egui/tests/editing.rs::organize_page_range_tracks_thumbnails_and_applies_typed_selection", "crates/ui-egui/tests/editing.rs::organize_page_range_rejects_invalid_input_and_escape_cancels_without_clearing", "crates/ui-egui/tests/control.rs::state_reports_the_selected_pages"]
|
||
notes = "Ctrl+A / ⌘A selects every page in the Organize grid, preserving the current page; text inputs and dialogs keep their own keyboard input. Page checkboxes also toggle selection without modifiers (organize_checkboxes_toggle_multiple_pages_without_modifiers). An editable physical page-range list stays synchronized with thumbnails; Enter, focus loss or Apply commits valid input, Escape cancels, and invalid input preserves the selection. UI control select accepts the same bounded ranges."
|
||
|
||
[[feature]]
|
||
id = "organize.rotate-pages"
|
||
title = "Rotate pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.rotate", "page.rotate_ccw"]
|
||
tools = ["page_rotate"]
|
||
evidence = ["crates/organize/src/tests.rs::rotate_uses_inherited_rotation_and_round_trips", "crates/ui-egui/tests/editing.rs::command_click_toggles_and_rotation_applies_to_selection", "crates/ui-egui/tests/editing.rs::organize_page_actions_rotate_only_the_named_page_and_undo", "crates/ui-egui/tests/editing.rs::organize_local_actions_respect_document_permissions"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.rotate-filters"
|
||
title = "Rotate by range with even/odd and orientation filters"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.rotate_dialog"]
|
||
tools = ["page_rotate"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_options_and_rotate_pages_dialog", "crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "Rotate Pages: all/selection/range; even/odd; landscape/portrait"
|
||
|
||
[[feature]]
|
||
id = "organize.delete-pages"
|
||
title = "Delete pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.delete"]
|
||
tools = ["page_delete"]
|
||
evidence = ["crates/organize/src/tests.rs::delete_keeps_inherited_attributes_on_survivors", "crates/organize/src/tests.rs::cannot_delete_every_page_or_missing_pages", "crates/ui-egui/tests/editing.rs::delete_is_refused_when_it_would_remove_every_page"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.move-pages"
|
||
title = "Move pages earlier or later"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["page_move"]
|
||
evidence = ["crates/organize/src/tests.rs::move_pages_reorders", "crates/ui-egui/tests/editing.rs::shift_click_selects_a_range_and_moves_it"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.drag-reorder"
|
||
title = "Reorder pages by drag and drop"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::dragging_thumbnails_reorders_pages"]
|
||
notes = "drag thumbnails (or the selection) in the page grid; an insertion bar shows where they go; move earlier/later buttons too"
|
||
|
||
[[feature]]
|
||
id = "organize.insert-blank"
|
||
title = "Insert blank pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.insert_blank"]
|
||
tools = ["page_insert_blank"]
|
||
evidence = ["crates/organize/src/tests.rs::inserts_blank_page", "crates/ui-egui/tests/editing.rs::insert_blank_page_after_selection", "crates/engine/src/tests.rs::insert_blank_between_pages_keeps_both_neighbours"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.insert-from-file"
|
||
title = "Insert pages from another file"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.insert"]
|
||
tools = ["page_insert_file"]
|
||
evidence = ["crates/organize/src/tests.rs::inserting_pages_from_another_file_is_an_incremental_edit", "crates/ui-egui/tests/editing.rs::inserting_a_file_goes_after_the_selection_and_undoes", "crates/ui-egui/tests/editing.rs::plus_between_pages_inserts_picked_files_there_in_order"]
|
||
notes = "After the selection (toolbar, File menu) or at any gap of the page grid (a + between pages and at both ends); several files at once, in order; images and .txt files are converted first; the inserted pages end up selected. The page_insert_file tool takes PDFs only. Missing: choosing which pages of the file to insert in the app."
|
||
|
||
[[feature]]
|
||
id = "organize.insert-from-clipboard"
|
||
title = "Insert pages from the clipboard"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.insert-from-scanner"
|
||
title = "Insert pages from a scanner"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.insert-from-web"
|
||
title = "Insert pages from a web page"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.insert-by-drop"
|
||
title = "Insert by dropping files onto the page grid"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "partial"
|
||
commands = ["page.organize"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::files_dropped_between_pages_are_inserted_there", "crates/ui-egui/tests/editing.rs::files_dropped_before_the_pointer_is_known_wait_for_it", "crates/ui-egui/tests/editing.rs::files_dropped_outside_the_page_grid_still_open_as_documents"]
|
||
notes = "Desktop: PDFs, images and .txt files dropped on the Organize page grid are inserted at the gap under the pointer, in order, and selected (elsewhere a dropped file still opens as a document). While the files hover, a bar at the gap and a + badge follow the pointer; on macOS the pointer is read from AppKit, because the windowing layer sends no moves during a file drag. Missing: on Windows and Linux there is no live position, so the grid is only outlined and the files go to the gap the pointer is at right after the drop (or the end after a second); the web build (dropped files open as documents). Covered by simulated drop events; a real drag from Finder is not in the automated tests."
|
||
|
||
[[feature]]
|
||
id = "organize.extract-pages"
|
||
title = "Extract pages to a new document"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.extract"]
|
||
tools = ["page_extract"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_button_copies_selected_pages_to_a_new_tab", "crates/automation/tests/automation.rs::combine_extract_and_split", "crates/organize/src/tests.rs::extract_and_split_keep_the_pdfx_output_intent_and_identification"]
|
||
notes = "Carries the PDF/X output intent and identification (#263) in a new XMP packet; the source XMP (dates, other schemas) is not copied."
|
||
|
||
[[feature]]
|
||
id = "organize.extract-separate-files"
|
||
title = "Extract pages as separate files"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.extract"]
|
||
tools = ["page_extract"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_options_and_rotate_pages_dialog", "crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/ui-egui/tests/pickers.rs::extract_pages_saves_under_the_file_name_typed_in_the_dialog"]
|
||
notes = "The dialog has a File name field (#737), filled in with the document's name: pages are written as `<name> (page N).pdf` into the chosen folder, and one renamed page as `<name>.pdf`. Extracting into a new tab names the tab (and so Save As) after it."
|
||
|
||
[[feature]]
|
||
id = "organize.extract-and-delete"
|
||
title = "Delete pages after extracting"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.extract"]
|
||
tools = ["page_extract"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_options_and_rotate_pages_dialog", "crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options"]
|
||
notes = "undoable"
|
||
|
||
[[feature]]
|
||
id = "organize.replace-pages"
|
||
title = "Replace pages with pages from another file"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.replace"]
|
||
tools = ["page_replace"]
|
||
evidence = ["crates/organize/src/tests.rs::replace_pages_swaps_content_but_keeps_annotations", "crates/ui-egui/tests/editing.rs::replace_pages_dialog_swaps_page_content", "crates/ui-egui/tests/editing.rs::replace_button_replaces_selected_pages_in_organize", "crates/automation/tests/automation.rs::replacing_pages_through_tools"]
|
||
notes = "content, resources and boxes replaced; links, comments, fields and bookmarks kept"
|
||
|
||
[[feature]]
|
||
id = "organize.duplicate-pages"
|
||
title = "Duplicate pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.duplicate"]
|
||
tools = ["page_duplicate"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/pageboxes.rs::duplicate_pages_copies_the_current_page", "crates/organize/src/tests.rs::duplicate_pages_inserts_copies_after_the_last"]
|
||
notes = "copies share fonts and images with the originals"
|
||
|
||
[[feature]]
|
||
id = "organize.reverse-pages"
|
||
title = "Reverse page order"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.cut-copy-paste-pages"
|
||
title = "Cut, copy and paste pages"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.copy", "page.cut", "page.paste"]
|
||
tools = ["page_insert_file"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::copying_cutting_and_pasting_pages"]
|
||
notes = "⌘C/⌘X/⌘V and the thumbnail menu in Organize Pages; paste works across documents"
|
||
|
||
[[feature]]
|
||
id = "organize.drag-pages-between-documents"
|
||
title = "Drag pages between documents"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-range-selection"
|
||
title = "Select by range, odd/even, landscape/portrait"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "partial"
|
||
commands = ["page.organize"]
|
||
tools = ["page_rotate"]
|
||
evidence = ["crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "Organize ▸ Select: all, odd, even, landscape, portrait; ranges via the page tools"
|
||
|
||
[[feature]]
|
||
id = "organize.thumbnail-size"
|
||
title = "Thumbnail size slider"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.organize"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::page_grid_zooms_with_its_buttons_keys_and_pinch", "crates/ui-egui/tests/editing.rs::a_zoomed_page_grid_still_inserts_and_moves_at_the_right_gap", "crates/ui-egui/tests/editing.rs::zoomed_pages_in_view_are_rendered_at_the_size_drawn"]
|
||
notes = "Organize page grid: 50–300% with toolbar buttons (and a percentage that resets to 100%), trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0; pages drawn larger than their thumbnails are rendered at the size drawn while they are in view (the thumbnail stands in until then, and after an edit), so text stays sharp however long the document is. Missing: a slider as in Acrobat; the size is per document view and not remembered between sessions; the Pages side panel has no size control."
|
||
|
||
[[feature]]
|
||
id = "organize.split-every-n"
|
||
title = "Split every N pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.split"]
|
||
tools = ["doc_split"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::split_dialog_writes_one_file_per_part", "crates/organize/src/tests.rs::split_ranges_cover_every_page_once", "crates/organize/src/tests.rs::split_produces_standalone_documents_with_metadata", "crates/organize/src/tests.rs::extract_and_split_keep_the_pdfx_output_intent_and_identification"]
|
||
notes = "Each part carries the PDF/X output intent and identification (#263), as Extract does."
|
||
|
||
[[feature]]
|
||
id = "organize.split-before-pages"
|
||
title = "Split before chosen pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.split"]
|
||
tools = ["doc_split"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::split_before_selected_pages"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.split-by-size"
|
||
title = "Split by file size"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.split"]
|
||
tools = ["doc_split"]
|
||
evidence = ["crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "greedy by single-page size (an over-estimate: parts stay under the limit)"
|
||
|
||
[[feature]]
|
||
id = "organize.split-by-bookmarks"
|
||
title = "Split by top-level bookmarks"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.split"]
|
||
tools = ["doc_split"]
|
||
evidence = ["crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "files named after the bookmarks"
|
||
|
||
[[feature]]
|
||
id = "organize.split-naming"
|
||
title = "Name split parts after bookmarks; never overwrite"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.split-multiple-files"
|
||
title = "Split multiple files in one run"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.combine-files"
|
||
title = "Combine files into one PDF"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/organize/src/tests.rs::combine_concatenates_with_a_bookmark_per_file", "crates/ui-egui/tests/editing.rs::combining_files_opens_a_new_unsaved_tab", "crates/ui-egui/tests/editing.rs::combine_files_opens_in_its_own_tab", "crates/ui-egui/tests/editing.rs::combine_lists_size_and_warns_before_combining", "crates/ui-egui/tests/editing.rs::a_password_protected_file_is_unlocked_with_its_password_and_combined", "crates/ui-egui/tests/editing.rs::a_folder_adds_its_pdfs_images_and_text_in_order_and_optionally_its_subfolders", "crates/ui-egui/tests/editing.rs::images_and_text_files_join_the_combine_list_as_pdfs", "crates/ui-egui/tests/editing.rs::files_that_cannot_be_read_are_listed_and_left_out_once_the_user_agrees", "crates/ui-egui/tests/ui.rs::pdfs_dropped_on_the_combine_tab_join_its_list", "crates/automation/tests/automation.rs::combine_extract_and_split", "crates/automation/tests/automation.rs::combine_opens_protected_files_with_their_passwords", "crates/automation/tests/automation.rs::combine_takes_images_and_text_files", "crates/engine/src/tests.rs::files_for_combine_are_made_into_pdfs_and_pdfs_pass_through", "crates/organize/src/tests.rs::combine_keeps_pdfx_when_every_source_agrees", "crates/organize/src/tests.rs::combine_claims_no_pdfx_when_the_sources_disagree"]
|
||
notes = "Combine files is its own tab: a table of the files (name, pages, size, modified, warnings) with column sorting, resizing and moving; files, folders (with subfolders) and open documents can be added, or dropped on the tab. Images (PNG, JPEG, TIFF, GIF, BMP, JPEG 2000) and .txt files become PDFs as they are added (#569; also in doc_combine). A file that can't be read stays in the list with why, and Combine asks before leaving it out; Office documents, HTML and other formats are not converted yet. Problems show before Combine (password, security settings that withhold page assembly, bad page ranges, duplicates); protected files are unlocked with their open or permissions password (also doc_combine passwords). The result is never encrypted, and is not yet offered for protection in the same step. A PDF/X result (output intent, GTS_PDFXVersion, Trapped, XMP pdfxid) only when every source declares the same version and output intent (#263). When they differ the result claims none, and nothing reports that yet; Acrobat behaviour not observed."
|
||
|
||
[[feature]]
|
||
id = "organize.combine-bookmarks"
|
||
title = "Combine adds a bookmark per file with its own bookmarks nested"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/organize/src/tests.rs::combine_nests_source_bookmarks_and_keeps_attachments", "crates/organize/src/tests.rs::a_file_split_around_another_is_copied_once_with_one_bookmark", "crates/organize/src/tests.rs::grouped_runs_keep_their_order_repeats_and_separate_files", "crates/engine/src/tests.rs::a_file_split_around_another_combines_in_order_with_one_bookmark", "crates/ui-egui/tests/combine_grid.rs::a_split_file_counts_once_and_combines_with_one_bookmark", "crates/automation/tests/automation.rs::combine_extract_and_split"]
|
||
notes = "Also when a file's pages are split around other files' pages (Combine files grid): the file is copied once, so it keeps one bookmark at its first page, its nested bookmarks, links between its own pages, its form fields and one copy of its attachments. doc_combine does the same with groups."
|
||
|
||
[[feature]]
|
||
id = "organize.combine-choose-pages"
|
||
title = "Choose which pages of each file to combine"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::combine_files_takes_chosen_pages_in_the_order_listed", "crates/automation/tests/automation.rs::combine_extract_and_split"]
|
||
notes = "A page range per file (such as 1-3, 6; in the order given), checked as it is typed, with the page count it takes; bookmarks to left-out pages lose their destination"
|
||
|
||
[[feature]]
|
||
id = "organize.combine-sort"
|
||
title = "Sort and reorder files before combining"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::column_headings_sort_the_list_and_flip_on_the_second_click", "crates/ui-egui/tests/editing.rs::ctrl_and_shift_clicks_select_several_files_that_move_and_go_together", "crates/ui-egui/tests/editing.rs::undo_and_redo_restore_the_combine_list", "crates/automation/tests/automation.rs::combine_extract_and_split"]
|
||
notes = "Sort by any column (name in natural order, pages, size, modified, warnings), ascending or descending; multi-select (Ctrl/Shift) and move up/down, Alt+arrows or drag (rows in the list, cards in the grid); every change undoable. doc_combine combines in the order its paths are given."
|
||
|
||
[[feature]]
|
||
id = "organize.combine-thumbnails"
|
||
title = "Thumbnail (grid) view of the files before combining"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/ui-egui/tests/combine_grid.rs::combine_shows_a_grid_by_default_and_the_view_is_kept_in_the_settings", "crates/ui-egui/tests/combine_grid.rs::cards_select_like_the_list_and_the_selection_survives_switching_views", "crates/ui-egui/tests/combine_grid.rs::dragging_a_card_moves_it_and_undo_puts_it_back", "crates/ui-egui/tests/combine_grid.rs::a_dragged_selection_moves_together_to_the_first_and_last_gaps", "crates/ui-egui/tests/combine_grid.rs::thumbnails_show_the_first_page_each_file_takes", "crates/ui-egui/tests/combine_grid.rs::many_files_render_only_what_is_in_view_a_few_at_a_time", "crates/ui-egui/tests/combine_grid.rs::long_thin_pages_get_thumbnails_within_the_size_limits", "crates/ui-egui/tests/combine_grid.rs::combine_follows_the_order_shown_in_the_grid", "crates/ui-egui/src/combine_grid.rs::render_threads_never_exceed_three_while_files_come_and_go", "crates/ui-egui/src/combine_grid.rs::textures_over_budget_go_longest_unseen_first_and_those_in_view_stay", "crates/ui-egui/src/combine_grid.rs::on_the_web_one_thumbnail_waits_at_a_time", "crates/ui-egui/tests/combine_grid.rs::a_file_whose_pages_cant_be_measured_says_there_is_no_preview", "crates/ui-egui/tests/combine_grid.rs::the_toolbar_sizes_the_cards_within_limits_and_the_size_is_kept_in_the_settings", "crates/ui-egui/tests/combine_grid.rs::ctrl_keys_and_ctrl_wheel_size_the_cards_and_leave_the_window_alone", "crates/ui-egui/tests/combine_grid.rs::zooming_keeps_the_card_under_the_pointer_in_place_and_thumbnails_never_go_blank", "crates/ui-egui/tests/combine_grid.rs::zooming_quickly_never_runs_more_than_three_render_threads", "crates/ui-egui/tests/combine_grid.rs::hovering_a_card_shows_its_trash_and_magnifier_and_only_then", "crates/ui-egui/tests/combine_grid.rs::the_card_trash_removes_only_that_file_and_undo_brings_it_back", "crates/ui-egui/tests/combine_grid.rs::the_magnifier_shows_the_file_large_and_flips_through_the_pages_it_adds", "crates/ui-egui/tests/combine_grid.rs::the_preview_closes_when_its_file_leaves_and_keeps_to_three_render_threads", "crates/ui-egui/tests/combine_grid.rs::a_right_click_on_a_card_offers_preview_and_remove", "crates/ui-egui/tests/combine_grid.rs::space_shows_the_selected_file_large_in_the_grid_and_the_list", "crates/ui-egui/tests/combine_grid.rs::expanding_a_file_shows_the_pages_it_adds_in_order_with_their_numbers", "crates/ui-egui/tests/combine_grid.rs::expand_all_and_collapse_all_from_the_toolbar_and_a_right_click", "crates/ui-egui/tests/combine_grid.rs::expanding_is_a_view_kept_across_views_and_zoom_and_never_an_undo_step", "crates/ui-egui/tests/combine_grid.rs::dragging_a_page_moves_that_page_even_between_another_files_pages", "crates/ui-egui/tests/combine_grid.rs::combining_with_files_expanded_gives_the_same_document", "crates/ui-egui/tests/combine_grid.rs::a_page_click_selects_that_page_and_the_arrows_step_rows_of_cards", "crates/ui-egui/tests/combine_grid.rs::a_page_cards_magnifier_opens_the_preview_on_that_page", "crates/ui-egui/tests/combine_grid.rs::a_long_file_expanded_renders_only_its_pages_in_view_within_three_threads", "crates/ui-egui/tests/combine_grid.rs::collapsing_brings_the_files_card_into_view", "crates/ui-egui/tests/combine_grid.rs::a_file_too_long_to_spread_out_offers_no_expand", "crates/ui-egui/src/combine_grid.rs::the_next_page_of_a_file_reuses_its_open_document_until_it_closes", "crates/ui-egui/tests/combine_grid.rs::removing_pages_from_the_hover_bar_the_toolbar_and_delete_never_touches_the_file", "crates/ui-egui/tests/combine_grid.rs::a_split_file_counts_once_and_combines_with_one_bookmark", "crates/ui-egui/src/combine_ui.rs::a_page_moved_between_another_files_pages_splits_its_file_and_parts_rejoin", "crates/ui-egui/src/combine_ui.rs::removing_pages_drops_emptied_parts_and_joins_what_meets", "crates/ui-egui/src/combine_ui.rs::page_lists_become_the_shortest_range", "crates/ui-egui/tests/combine_grid.rs::pages_picked_out_of_sight_or_before_an_undo_are_let_go_of", "crates/ui-egui/tests/combine_grid.rs::a_files_parts_join_when_what_was_between_them_goes_and_one_file_cannot_be_combined", "crates/organize/src/tests.rs::a_page_shown_twice_gets_its_own_annotations", "crates/engine/src/tests.rs::a_page_with_a_form_field_shown_twice_keeps_one_field", "crates/ui-egui/tests/combine_grid.rs::files_picked_by_automation_let_go_of_the_pages_picked"]
|
||
notes = "Combine files shows a card per file (the default; Grid/List toggle kept in the settings, --combine-view grid|list): the first page the file contributes (its page range's first page), stacked when it contributes more, and its name shortened in the middle. Cards are sized from 60% to 200% (toolbar slider, buttons and percentage reset, Ctrl/⌘ +/−/0, Ctrl/⌘ with the wheel or a pinch over the grid; kept in the settings, --combine-zoom 60..200), keeping the card under the pointer in place. Hovering a card shows its own actions along the top of its page: remove that file only (whatever else is selected; undoable) and a magnifier that shows the file large, flipping through the pages it adds (buttons, Left/Right; Esc, close or a click outside closes it), also on the card's right-click menu, and Space shows the selected file large (the way there by keyboard). Cards select (click, Ctrl, Shift, arrows by card and by row), drag to reorder (one undo step; the grid scrolls at its edges) and share the list's toolbar. Thumbnails render off the UI thread, only for cards in view, sized to both sides of the page and at most 768 px; at most three render threads at once, counted as threads until they exit (no watchdog replacements; a page stuck for good in one step that can't be interrupted keeps its thread, and so its place, for the rest of the run); textures out of view are dropped over a 32 MB budget (those in view are kept). Locked, unreadable and bad-range files show why instead of a page, and a file whose page size can't be read (e.g. opened with an RC4 owner password) says No preview; page ranges are edited in the list. On the web a thumbnail, and the page shown large, render on the UI thread (one per frame, and only one waits at a time), so a very slow or very large page stalls that frame. A file that adds more than one page (up to 2000) expands into its pages (hover bar, right-click, or Expand all / Collapse all in the toolbar): one card per page it adds, in its range's order, each saying Page N and the file's name, on a tinted band; a click on a page selects that page, Up/Down step rows of cards, dragging a file's card moves the whole file (a drop among a file's pages goes to its nearer end), and its magnifier opens the preview on that page; expanding is a view, not an undo step, and changes nothing in the combined file; a page's render reuses its file's open document for a moment rather than reading the file again. With a file expanded, single pages are selected (click, Ctrl, Shift), dragged anywhere (a page moved between another file's pages splits its file into parts, shown as rows in the list with their own page ranges; parts side by side join again) and removed (a page card's trash, the toolbar, Delete), each one undo step, never changing the files themselves; a split file still counts and combines as one file."
|
||
|
||
[[feature]]
|
||
id = "organize.combine-options"
|
||
title = "Combine options (file size, convert to PDF/A, include open files)"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["page.combine"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::open_documents_can_be_added_to_combine"]
|
||
notes = "Include open files: Add open documents (with their unsaved changes). Missing: file-size options and converting the result to PDF/A."
|
||
|
||
[[feature]]
|
||
id = "organize.resource-dedupe"
|
||
title = "Store identical fonts and images once when combining or inserting"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.combine", "page.insert"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/organize/src/tests.rs::combine_and_insert_share_identical_resources", "crates/organize/src/dedupe.rs::merges_bottom_up_and_keeps_identity_objects"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.preserve-links-dests"
|
||
title = "Rewire links and named destinations to copied pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/organize/src/tests.rs::links_are_rewired_to_copied_pages_or_dropped", "crates/organize/src/tests.rs::named_destinations_are_resolved_to_the_copies"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.preserve-fields"
|
||
title = "Keep form fields interactive when copying pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/organize/src/tests.rs::form_fields_come_along_and_are_registered"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.preserve-layers"
|
||
title = "Keep layers and their default state when copying pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/organize/src/tests.rs::layers_are_registered_with_their_default_state"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.preserve-inherited"
|
||
title = "Copy inherited page attributes with pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/organize/src/tests.rs::inherited_attributes_travel_with_copied_pages"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.preserve-attachments"
|
||
title = "Keep attachments when combining"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/organize/src/tests.rs::combine_nests_source_bookmarks_and_keeps_attachments"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.field-name-conflicts"
|
||
title = "Rename or merge colliding field names when combining"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.insert", "page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/organize/src/tests.rs::import_renames_a_colliding_field_name", "crates/organize/src/tests.rs::import_renames_to_the_first_free_variant", "crates/organize/src/tests.rs::import_renames_away_from_an_orphaned_widgets_name", "crates/organize/src/tests.rs::import_renames_a_nested_field_whose_full_name_collides", "crates/organize/src/tests.rs::import_renames_the_colliding_child_not_the_whole_tree", "crates/organize/src/tests.rs::import_renames_a_field_colliding_with_another_incoming_field", "crates/organize/src/tests.rs::import_can_merge_into_same_named_fields_instead", "crates/organize/src/tests.rs::a_renamed_field_tree_survives_a_save_and_reopen", "crates/organize/src/tests.rs::a_repeated_page_joins_the_renamed_field", "crates/organize/src/tests.rs::combine_renames_colliding_field_names_and_reports_them", "crates/organize/src/tests.rs::combine_grouped_renames_colliding_field_names", "crates/organize/src/tests.rs::combine_field_conflicts_previews_each_file_in_order", "crates/engine/src/tests.rs::insert_from_file_renames_colliding_fields_and_notes_it", "crates/engine/src/tests.rs::combine_reports_renamed_fields_and_can_merge", "crates/automation/tests/automation.rs::combine_renames_colliding_field_names", "crates/ui-egui/tests/editing.rs::combine_list_warns_that_colliding_field_names_will_be_renamed"]
|
||
notes = "Collisions are found by fully-qualified name (parent.T.child), counting names that hang only off page widgets even when /AcroForm never lists them, and also between two incoming fields. The colliding field's own /T takes the first free variant (Text1-1, Text1-2, …), children keep their place, and nothing is silent: inserting into an open document notes each rename in the repair log (warning that scripts referring to the old names were not rewritten) and the undo step's label counts them; doc_combine lists renamed_fields in its result; the Combine tab's Warnings column previews the renames per file before combining. field_conflicts \"merge\" on doc_combine (and the engine policy) keeps the older one-field behaviour. Still missing: the Combine tab's preview considers whole files, not a narrowed page range; the UI offers no merge choice (it always renames); Create ▸ Multiple files renames without reporting; scripts and XFA bindings that reference old names are never rewritten; Acrobat's behaviour not observed."
|
||
|
||
[[feature]]
|
||
id = "organize.struct-tree-merge"
|
||
title = "Merge structure trees when combining"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-labels-edit"
|
||
title = "Edit page labels (style, prefix, start)"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.number"]
|
||
tools = ["page_number"]
|
||
evidence = ["crates/organize/src/tests.rs::number_pages_like_acrobat", "crates/engine/src/tests.rs::number_pages_shows_in_the_viewer_and_undoes", "crates/ui-egui/tests/bookmarks.rs::number_pages_dialog_labels_the_selected_pages", "crates/automation/tests/automation.rs::numbering_pages_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-boxes"
|
||
title = "Set page boxes (crop, trim, bleed, art)"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.boxes"]
|
||
tools = ["page_set_box"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/pageboxes.rs::set_page_boxes_dialog_applies_margins_to_the_chosen_pages", "crates/organize/src/tests.rs::page_boxes_default_inherit_and_set"]
|
||
notes = "crop/trim/bleed/art/media, margins in pt/in/mm, page ranges, preview; no Remove white margins yet"
|
||
|
||
[[feature]]
|
||
id = "organize.crop-tool"
|
||
title = "Crop pages by dragging a rectangle"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.crop"]
|
||
tools = ["page_set_box"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/pageboxes.rs::the_crop_tool_crops_to_the_dragged_rectangle_then_returns_to_select"]
|
||
notes = "drag to crop; double-click opens Set Page Boxes"
|
||
|
||
[[feature]]
|
||
id = "organize.remove-white-margins"
|
||
title = "Remove white margins"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.change-page-size"
|
||
title = "Change page size"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-transitions"
|
||
title = "Page transitions"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-tab-order"
|
||
title = "Page tab order property"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-actions"
|
||
title = "Page open/close actions"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-templates"
|
||
title = "Page templates"
|
||
area = "D"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-add"
|
||
title = "Add a bookmark (⌘B)"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["bookmark.add"]
|
||
tools = ["bookmark_add"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save", "crates/automation/tests/automation.rs::bookmarks_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-rename"
|
||
title = "Rename bookmarks"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_rename"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/ui-egui/tests/bookmarks.rs::escape_cancels_a_rename", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/organize/src/tests.rs::bookmark_edits_keep_unknown_keys_and_touch_few_objects"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-delete"
|
||
title = "Delete bookmarks"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_delete"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-move"
|
||
title = "Reorder and nest bookmarks"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_move"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save", "crates/automation/tests/automation.rs::bookmarks_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-set-destination"
|
||
title = "Set a bookmark's destination"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_set_page"]
|
||
evidence = ["crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save", "crates/automation/tests/automation.rs::bookmarks_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-appearance"
|
||
title = "Bookmark style and colour"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-actions"
|
||
title = "Bookmark actions"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-expand-collapse"
|
||
title = "Expand and collapse all bookmarks"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::expanding_and_collapsing_all_bookmarks"]
|
||
notes = "Bookmarks panel options: expand all, expand top-level, collapse all"
|
||
|
||
[[feature]]
|
||
id = "organize.bookmarks-from-structure"
|
||
title = "Generate bookmarks from headings or tags"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["bookmark.from_structure"]
|
||
tools = ["bookmark_from_structure"]
|
||
evidence = ["crates/a11y/src/tests.rs::headings_are_found_with_their_levels_titles_and_pages", "crates/organize/src/tests.rs::a_bookmark_tree_nests_entries_by_level_under_a_new_first_bookmark", "crates/engine/src/tests.rs::bookmarks_from_structure_nest_the_headings_and_undo", "crates/automation/tests/automation.rs::bookmarks_from_structure_through_tools", "crates/ui-egui/tests/bookmarks.rs::new_bookmarks_from_structure_nest_the_tagged_headings"]
|
||
notes = "Bookmark options ▸ New bookmarks from structure: tagged headings (H1–H6 through the role map; a plain H takes its Sect/Part/Art nesting) become bookmarks nested by level under a new first \"Untitled\" bookmark, each linked to its element (/SE). Titles come from /ActualText, the marked text drawn directly on the page (not inside Form XObjects), /Alt or /T. Not yet: Acrobat's choice of element types (only headings), and destinations go to the heading's page rather than its position on it"
|
||
|
||
[[feature]]
|
||
id = "organize.attachments-add"
|
||
title = "Add file attachments"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.attachments-delete"
|
||
title = "Delete attachments"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.attachments-description"
|
||
title = "Edit attachment descriptions"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.associated-files"
|
||
title = "Associated files with relationships (PDF/A-3, PDF 2.0)"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.portfolio-view"
|
||
title = "View PDF Portfolios as a file list"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.portfolio-extract"
|
||
title = "Extract files from a portfolio"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.portfolio-create"
|
||
title = "Create PDF Portfolios"
|
||
area = "D"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.document-properties"
|
||
title = "Document properties dialog (description, fonts, advanced)"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::document_properties_edit_is_one_undoable_step", "crates/ui-egui/tests/editing.rs::cancelling_properties_discards_the_draft"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.edit-metadata"
|
||
title = "Edit title, author, subject and keywords"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_set_info"]
|
||
evidence = ["crates/organize/src/tests.rs::document_info_edits_round_trip_with_unicode", "crates/ui-egui/tests/editing.rs::document_properties_edit_is_one_undoable_step"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.fonts-list"
|
||
title = "List fonts with type and embedding status"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::fonts_tab_lists_fonts_and_embedding"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.initial-view"
|
||
title = "Edit initial view (layout, magnification, panels, window options)"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/ui-egui/tests/viewer.rs::initial_view_is_edited_and_honoured_on_open", "crates/automation/tests/automation.rs::initial_view_through_tools"]
|
||
notes = "navigation panel, layout, magnification, page, window and interface options; honoured on open; script open actions kept"
|
||
|
||
[[feature]]
|
||
id = "organize.custom-properties"
|
||
title = "Custom document properties"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M4"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.xmp-editor"
|
||
title = "Additional metadata (XMP) editor"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.document-language"
|
||
title = "Set document language and binding"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/automation/tests/automation.rs::initial_view_through_tools"]
|
||
notes = "Advanced ▸ Reading Options: binding and language"
|
||
|
||
[[feature]]
|
||
id = "organize.print-presets"
|
||
title = "Print dialog presets in document properties"
|
||
area = "D"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.base-url"
|
||
title = "Base URL for relative links"
|
||
area = "D"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
# ==================================================================================================
|
||
# E. Comments and review
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "comment.list-view"
|
||
title = "Comments panel with threaded replies (read-only)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/comments.rs::the_panel_posts_comments_and_replies"]
|
||
notes = "cards grouped by page with replies, status badges, selection, reply box and an Add a comment box"
|
||
|
||
[[feature]]
|
||
id = "comment.appearance-generation"
|
||
title = "Generate correct appearances for every markup type"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/engine/src/tests.rs::highlight_multiplies_over_text", "crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn"]
|
||
notes = "appearances for every type PdfCraft creates (highlight multiplies; clouds, callouts, stamps, polygons, carets); rich text not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.sticky-note"
|
||
title = "Sticky note comments"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.note"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::sticky_note_composer_posts_and_returns_to_select", "crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = "our own Comment/Note icon drawings, with a pop-up"
|
||
|
||
[[feature]]
|
||
id = "comment.highlight"
|
||
title = "Highlight text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.highlight"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::dragging_over_text_with_the_highlighter_highlights_it", "crates/ui-egui/tests/comments.rs::a_new_highlight_opens_its_note_for_typing", "crates/engine/src/tests.rs::highlight_multiplies_over_text"]
|
||
notes = "drag over text, or pick the tool with text selected; a new highlight opens its note for typing in the Comments panel; agents can highlight by text search"
|
||
|
||
[[feature]]
|
||
id = "comment.underline"
|
||
title = "Underline text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.underline"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/ui-egui/tests/comments.rs::a_selection_made_first_is_marked_when_a_tool_is_picked"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.strikeout"
|
||
title = "Strikethrough text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.strikeout"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::a_selection_made_first_is_marked_when_a_tool_is_picked", "crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.squiggly"
|
||
title = "Squiggly underline"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.squiggly"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/ui-egui/tests/comments.rs::the_squiggly_tool_marks_selected_text"]
|
||
notes = "Highlight ▸ Squiggly underline (with Underline and Strikethrough), the agent tool and the engine"
|
||
|
||
[[feature]]
|
||
id = "comment.replace-text"
|
||
title = "Replace-text proposal (strikeout + caret)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.replace"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::replace_text_groups_a_strikeout_and_a_caret", "crates/ui-egui/tests/comments.rs::replacing_text_strikes_it_and_adds_a_caret", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "strikeout (/IT StrikeOutTextEdit) and a grouped caret (/RT /Group) with the replacement"
|
||
|
||
[[feature]]
|
||
id = "comment.insert-text"
|
||
title = "Insert-text proposal (caret)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.caret"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/ui-egui/tests/comments.rs::inserted_text_snaps_to_the_gap_between_characters_on_the_baseline", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "in the UI the caret snaps to the nearer character edge, on the baseline and sized to the text, on upright horizontal text of unrotated pages; elsewhere it stays at the click. comment_add places it where it is told (no snapping)"
|
||
|
||
[[feature]]
|
||
id = "comment.typewriter"
|
||
title = "Add text comment (typewriter)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.text"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/annot/src/tests.rs::fill_and_sign_items_are_drawn", "crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/ui-egui/tests/fill_sign.rs::fill_text_size_controls_new_text_and_survives_settings_restore", "crates/automation/tests/automation.rs::fill_sign_text_font_size_is_saved_and_validated"]
|
||
notes = "Fill & Sign text and dates have a 4–300 pt size control in the active toolbar; new items use the chosen size, which persists across launches. fill_sign_add accepts font_size for text and dates."
|
||
|
||
[[feature]]
|
||
id = "comment.text-box"
|
||
title = "Text box comment with rich text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.freetext"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::sticky_note_composer_posts_and_returns_to_select", "crates/annot/src/tests.rs::text_box_text_and_colour_changes_are_drawn"]
|
||
notes = "Helvetica text with colour, size and alignment; /RC (XHTML) and /DS are written and kept in step with edits; per-run formatting (bold/italic spans) is not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.callout"
|
||
title = "Callout comment"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.callout"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/annot/src/tests.rs::editing_a_callout_refits_the_box_and_keeps_the_leader", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "drag from the target to the box; resizing re-attaches the leader, and so does editing the text"
|
||
|
||
[[feature]]
|
||
id = "comment.pencil"
|
||
title = "Freehand drawing (ink)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.ink"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::shapes_and_ink_are_drawn_by_dragging", "crates/annot/src/tests.rs::ink_strokes_are_smooth_curves_through_their_points", "crates/annot/src/tests.rs::drawings_resize_by_scaling_their_strokes"]
|
||
notes = "one ink annotation per stroke; strokes are drawn as smooth curves through their points, as in Acrobat; resize with the 8 handles (the strokes scale into the new box)"
|
||
|
||
[[feature]]
|
||
id = "comment.eraser"
|
||
title = "Eraser for ink strokes"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.eraser"]
|
||
tools = []
|
||
evidence = ["crates/annot/src/tests.rs::the_eraser_cuts_strokes_and_removes_empty_drawings", "crates/ui-egui/tests/comments.rs::erasing_part_of_a_drawing"]
|
||
notes = "cuts strokes where the eraser passes; an emptied drawing is deleted; one undo step"
|
||
|
||
[[feature]]
|
||
id = "comment.line"
|
||
title = "Line"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.line"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.arrow"
|
||
title = "Arrow"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.arrow"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::shapes_and_ink_are_drawn_by_dragging"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.rectangle"
|
||
title = "Rectangle"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.square"]
|
||
tools = ["comment_add", "comment_edit"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::shapes_and_ink_are_drawn_by_dragging", "crates/annot/src/tests.rs::restyle_and_resize_redraw_the_appearance", "crates/annot/src/tests.rs::shapes_can_be_filled_and_unfilled", "crates/ui-egui/tests/comments.rs::shapes_take_a_fill_colour_from_the_quick_bar_and_properties"]
|
||
notes = "resize with the 8 handles; fill colour or no fill (#686) from the quick bar, Properties and comment_edit `fill`; fill swatches are the comment palette only (no custom colour picker yet)"
|
||
|
||
[[feature]]
|
||
id = "comment.oval"
|
||
title = "Oval"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.circle"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.polygon"
|
||
title = "Polygon"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.polygon"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "click the corners; Enter, double-click or the first point finishes"
|
||
|
||
[[feature]]
|
||
id = "comment.polyline"
|
||
title = "Connected lines (polyline)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.polyline"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.cloud"
|
||
title = "Cloud shape"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.cloud"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/annot/src/tests.rs::cloud_paths_bulge_outwards_either_way_round", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "polygon with a cloudy border (/BE /S /C, /IT /PolygonCloud)"
|
||
|
||
[[feature]]
|
||
id = "comment.area-highlight"
|
||
title = "Area highlight"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.highlight"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::highlighting_an_area_off_the_text"]
|
||
notes = "drag the highlighter off text to highlight a box"
|
||
|
||
[[feature]]
|
||
id = "comment.attach-file-comment"
|
||
title = "Attach a file as a comment"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.attach"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::files_attach_as_comments", "crates/ui-egui/tests/comments.rs::attaching_a_file_as_a_comment", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "push pin, paperclip, graph or tag icon; the file is embedded and listed in the Attachments panel"
|
||
|
||
[[feature]]
|
||
id = "comment.record-audio"
|
||
title = "Sound comments"
|
||
area = "E"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.image-comment"
|
||
title = "Add an image as a comment"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.line-endings"
|
||
title = "All ten line-ending styles and dashed/cloudy borders"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_line_ending_is_drawn", "crates/automation/tests/automation.rs::line_endings_through_the_tool", "crates/ui-egui/tests/comments.rs::line_ending_properties_change_and_undo"]
|
||
notes = "All ten /LE styles (None, Square, Circle, Diamond, OpenArrow, ClosedArrow, Butt, ROpenArrow, RClosedArrow, Slash) are drawn on lines, polylines and callouts. comment_add endings and Comment properties set them. Dashed borders are drawn. Cloudy borders are still polygon-only."
|
||
|
||
[[feature]]
|
||
id = "comment.properties-dialog"
|
||
title = "Comment properties (appearance, author, subject)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_edit"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comment_properties_change_appearance_and_author", "crates/annot/src/tests.rs::author_subject_and_note_icon_change", "crates/ui-egui/tests/comments.rs::shapes_take_a_fill_colour_from_the_quick_bar_and_properties"]
|
||
notes = "Appearance (colour, fill colour for rectangles/ovals/polygons, opacity, thickness, note icon), General (author, subject), Review History"
|
||
|
||
[[feature]]
|
||
id = "comment.properties-bar"
|
||
title = "Comment properties bar"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.note"]
|
||
tools = ["comment_edit"]
|
||
evidence = ["crates/annot/src/tests.rs::restyle_and_resize_redraw_the_appearance", "crates/ui-egui/tests/comments.rs::the_opacity_set_for_a_tool_goes_into_its_new_comments", "crates/ui-egui/tests/comments.rs::the_quick_bar_colour_changes_the_selected_comment", "crates/ui-egui/tests/comments.rs::the_quick_bar_colour_changes_the_selected_arrow"]
|
||
notes = "quick-bar colour swatches immediately recolour a selected comment made with the active tool, and set the tool default; fill colour (rectangle, oval, polygon, cloud), opacity (10–100 %) and line thickness per tool; colour from the context menu"
|
||
|
||
[[feature]]
|
||
id = "comment.lock-comment"
|
||
title = "Lock comments"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["comment_lock"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "Comment Properties ▸ Locked; locked comments refuse move, resize, restyle and delete; text stays editable"
|
||
|
||
[[feature]]
|
||
id = "comment.default-properties"
|
||
title = "Make current properties the tool default"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/comments.rs::make_current_properties_default"]
|
||
notes = "colour, opacity and line width become the tool's defaults (canvas and Comments panel menus) and survive a restart: the per-tool styles are persisted with the settings and restored through their clamping setters (#340)"
|
||
|
||
[[feature]]
|
||
id = "comment.author-identity"
|
||
title = "Author name from identity preferences"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::dragging_over_text_with_the_highlighter_highlights_it", "crates/ui-egui/tests/ui_group/identity.rs::the_identity_name_signs_new_comments_and_is_remembered"]
|
||
notes = "Preferences ▸ Identity: the name on new comments (default the login name, as Acrobat), remembered across restarts"
|
||
|
||
[[feature]]
|
||
id = "comment.reply"
|
||
title = "Reply to comments"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_reply"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_posts_comments_and_replies", "crates/annot/src/tests.rs::replies_and_status_thread_onto_the_parent"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.set-status"
|
||
title = "Set review status (accepted, rejected, cancelled, completed)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_set_status"]
|
||
evidence = ["crates/annot/src/tests.rs::replies_and_status_thread_onto_the_parent", "crates/engine/src/tests.rs::comments_are_added_drawn_threaded_and_undone"]
|
||
notes = "status replies (/State, /StateModel Review), shown as a badge"
|
||
|
||
[[feature]]
|
||
id = "comment.checkmark"
|
||
title = "Mark comments with a checkmark"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["comment_mark"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "hidden /StateModel /Marked reply; card toggle and context menu"
|
||
|
||
[[feature]]
|
||
id = "comment.delete-comment"
|
||
title = "Delete comments"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_delete"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comments_are_selected_moved_and_deleted_with_the_select_tool", "crates/annot/src/tests.rs::delete_takes_popup_and_reply_chain"]
|
||
notes = "deletes the pop-up and the reply thread"
|
||
|
||
[[feature]]
|
||
id = "comment.edit-comment-text"
|
||
title = "Edit comment text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_edit"]
|
||
evidence = ["crates/annot/src/tests.rs::text_box_text_and_colour_changes_are_drawn", "crates/annot/src/tests.rs::editing_a_text_box_refits_its_rectangle", "crates/automation/tests/automation.rs::comments_through_tools"]
|
||
notes = "double-click a comment or card, or Edit in the menus; a text box's rectangle follows the new text (wrap width and top edge stay, the height fits the wrapped lines)"
|
||
|
||
[[feature]]
|
||
id = "comment.copy-comment-text"
|
||
title = "Copy comment text"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize"]
|
||
notes = "card and canvas context menus"
|
||
|
||
[[feature]]
|
||
id = "comment.filter-comments"
|
||
title = "Filter comments (type, author, status, colour, checkmark)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_filters_and_sorts", "crates/ui-egui/tests/comments.rs::the_panel_filters_by_colour_and_checkmark"]
|
||
notes = "type, author, status, colour and checkmark"
|
||
|
||
[[feature]]
|
||
id = "comment.sort-comments"
|
||
title = "Sort comments (page, author, date, type, colour)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_filters_and_sorts"]
|
||
notes = "page, author, date, type, colour, with group headers"
|
||
|
||
[[feature]]
|
||
id = "comment.search-comments"
|
||
title = "Search comments"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_posts_comments_and_replies"]
|
||
notes = "search by author, text and type in the panel; not yet in the Find bar"
|
||
|
||
[[feature]]
|
||
id = "comment.show-hide-comments"
|
||
title = "Show or hide all comments and pop-ups"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.hide_all"]
|
||
tools = ["comments_hide"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "hide/show all comments (fields and links still draw); per-type hiding not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.popups"
|
||
title = "Open and reposition comment pop-ups"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.connector-lines"
|
||
title = "Connector lines between markups and pop-ups"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.review-history"
|
||
title = "Review history of state changes"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comment_properties_change_appearance_and_author"]
|
||
notes = "status changes listed in Comment Properties"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-standard"
|
||
title = "Standard business stamps"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::stamps_draw_their_label_and_by_line", "crates/ui-egui/tests/stamps.rs::choosing_and_placing_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "12 standard business stamps in PdfCraft's own design; ISO 32000 stamp names where defined"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-sign-here"
|
||
title = "Sign-here stamps"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::stamps_draw_their_label_and_by_line", "crates/ui-egui/tests/stamps.rs::choosing_and_placing_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "Accepted, Initial Here, Rejected, Sign Here, Witness as pointed tags"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-dynamic"
|
||
title = "Dynamic stamps (name and date)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::stamps_draw_their_label_and_by_line", "crates/ui-egui/tests/stamps.rs::choosing_and_placing_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "Approved, Confidential, Received, Reviewed, Revised with a By author at time, date line (local time)"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-custom"
|
||
title = "Custom stamps from images or PDF pages"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["stamp_custom"]
|
||
evidence = ["crates/ui-egui/tests/stamps.rs::creating_placing_and_keeping_custom_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "Create custom stamp from a PDF page or an image (category and name), kept in the app's settings; placed at natural size (at most 200 pt) keeping its shape; resizable (appearance redrawn from /PCPicture); delete from the palette"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-manage"
|
||
title = "Manage stamp categories and imports"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-rotate"
|
||
title = "Rotate stamps with a handle"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.import-fdf-xfdf"
|
||
title = "Import comments from FDF/XFDF"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.import"]
|
||
tools = ["doc_import_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/xfdf/src/tests.rs::fdf_round_trips_values_and_comments", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "replies, pop-ups, ink, quads, line ends; same /NM replaces"
|
||
|
||
[[feature]]
|
||
id = "comment.export-fdf-xfdf"
|
||
title = "Export all comments to FDF/XFDF"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.export"]
|
||
tools = ["doc_export_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/xfdf/src/tests.rs::fdf_round_trips_values_and_comments", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.export-selected"
|
||
title = "Export selected comments"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.import-from-pdf"
|
||
title = "Import comments from another PDF"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.summarize"
|
||
title = "Summarize comments into a new PDF"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.summarize"]
|
||
tools = ["comments_summarize"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "Comments only layout, sorted by page, author, date or type; the layouts with the document and connector lines not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.flatten"
|
||
title = "Flatten comments into page content"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.flatten"]
|
||
tools = ["doc_flatten"]
|
||
evidence = ["crates/edit/src/tests.rs::flattening_draws_appearances_into_the_page_and_removes_the_comments", "crates/engine/src/tests.rs::flattening_keeps_the_look_and_drops_the_objects", "crates/automation/tests/automation.rs::flattening_through_tools"]
|
||
notes = "appearances become page content; pop-ups and replies removed"
|
||
|
||
[[feature]]
|
||
id = "comment.migrate"
|
||
title = "Migrate comments to a revised document"
|
||
area = "E"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.measure-distance"
|
||
title = "Distance measuring tool"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["measure.distance"]
|
||
tools = ["measure_distance"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Two-point distance annotations with saved numeric captions and rectilinear scales. Partial: only single-unit decimal formats; compound (ft-in) and fractional formats are not supported (listed as unsupported, data kept), no leader lines or caption placement options, geospatial scales unsupported, and interop with Acrobat-authored measurements (reading theirs, Acrobat reading ours) is untested."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-perimeter"
|
||
title = "Perimeter measuring tool"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.perimeter"]
|
||
tools = ["measure_perimeter"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Connected-line lengths with click vertices and double-click/Enter finish. Repeat the first point to include a closing edge. Saved standard PolyLineDimension annotations."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-area"
|
||
title = "Area measuring tool"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.area"]
|
||
tools = ["measure_area"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Simple polygon areas, including concave boundaries and either winding. Self-intersecting boundaries are refused. Saved PolygonDimension annotations; no 3D surface measurement."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-scale"
|
||
title = "Scale ratio and calibration per viewport"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["measure.scale"]
|
||
tools = ["measure_scale"]
|
||
evidence = ["crates/measure/src/tests.rs::viewport_precedence_first_point_user_unit_and_roundtrip", "crates/measure/src/tests.rs::coordinates_roundtrip_for_crop_rotation_and_user_unit", "crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Persistent rectangular viewports with last-containing-viewport precedence, numeric scale and two-point calibration. First vertex selects the scale; prior annotations retain theirs. Partial: single-unit decimal rectilinear scales only; compound (ft-in), fractional and differing distance units can't be authored or imported, no viewport editing/removal UI, and interop with Acrobat's scale ratio dialog and its saved viewports is untested."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-snap"
|
||
title = "Snap to paths, endpoints, midpoints and intersections"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.snap"]
|
||
tools = ["measure_snap"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurement_snap_tool_covers_all_targets", "crates/measure/src/tests.rs::nested_forms_transforms_curves_and_cycles_are_bounded", "crates/measure/src/tests.rs::snap_paths_endpoints_midpoints_and_intersections"]
|
||
notes = "Paths, endpoints, midpoints and intersections of painted vector geometry, including transformed Form XObjects. Cubics flattened to 0.2 user units. Bounded extraction and dense-intersection limits are reported. Raster/text outlines, clipping and optional-content visibility are not interpreted."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-info-panel"
|
||
title = "Measurement info panel"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.info"]
|
||
tools = ["measure_info", "measure_list"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Live value, units, X/Y deltas, angle and selected scale while drawing; saved measurements can be selected from the panel. Rectilinear 2D readings only."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-export"
|
||
title = "Export measurements as CSV"
|
||
area = "E"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.export"]
|
||
tools = ["measure_export"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::measurement_csv_quotes_newlines_and_blocks_formula_cells", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Atomic CSV export of page, annotation index, type, numeric value, unit, label, author and scale. Quotes embedded delimiters and prefixes spreadsheet formula triggers."
|
||
|
||
[[feature]]
|
||
id = "comment.geospatial"
|
||
title = "Geospatial location and measuring"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.shared-review"
|
||
title = "Send for comments / shared review"
|
||
area = "E"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; exchange XFDF files or a pluggable review server instead"
|
||
|
||
[[feature]]
|
||
id = "comment.mentions-live-review"
|
||
title = "@mentions, presence and live co-review"
|
||
area = "E"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; no local equivalent"
|
||
|
||
[[feature]]
|
||
id = "comment.review-tracker"
|
||
title = "Review tracker"
|
||
area = "E"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud / legacy"
|
||
|
||
[[feature]]
|
||
id = "comment.enable-reader-commenting"
|
||
title = "Enable commenting and measuring in Reader (usage rights)"
|
||
area = "E"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "needs Adobe's private key; modern readers allow commenting anyway"
|
||
|
||
# ==================================================================================================
|
||
# F. Forms and JavaScript
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "form.field-list"
|
||
title = "Form fields panel listing every field and value"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::field_list_jumps_to_a_fields_page", "crates/render/src/inspect.rs::fields_listed_only_on_the_pages_are_listed", "crates/ui-egui/tests/ui_group/bulk_fields.rs::fields_panel_selects_multiple_fields_and_opens_their_shared_properties", "crates/ui-egui/tests/ui_group/bulk_fields.rs::double_clicking_a_field_in_the_panel_opens_its_individual_properties"]
|
||
notes = "list with jump-to-field; in Prepare a form, Shift/Ctrl/Command-click selects several fields and Properties edits their shared properties; double-click opens field properties; fields a file lists only as page widgets (empty /Fields) are adopted too"
|
||
|
||
[[feature]]
|
||
id = "form.highlight-fields"
|
||
title = "Highlight existing fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::highlight_fields_tints_the_field_area", "crates/ui-egui/tests/view.rs::required_radio_buttons_get_a_round_red_border", "crates/ui-egui/tests/view.rs::field_highlighting_is_remembered"]
|
||
notes = "remembered for the next document and session; required fields get a red border (round for radio buttons); the tint is drawn over the field, where Acrobat paints the highlight colour behind it"
|
||
|
||
[[feature]]
|
||
id = "form.field-appearance-display"
|
||
title = "Display checkbox, radio, list and signature widgets as designed"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/render/src/raster.rs::widget_appearance_states"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.fill-text"
|
||
title = "Fill text fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill", "form_fields"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::typing_into_a_text_field_and_tabbing_on", "crates/forms/src/tests.rs::text_fields_get_new_appearances", "crates/engine/src/tests.rs::filling_a_form_shows_saves_and_undoes", "crates/automation/tests/automation.rs::orphan_form_fields_through_tools"]
|
||
notes = "in-place editor; Enter or clicking away commits, Escape cancels; no format/keystroke JavaScript yet (M6.4)"
|
||
|
||
[[feature]]
|
||
id = "form.fill-checkbox-radio"
|
||
title = "Toggle checkboxes and radio buttons (radios in unison)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::check_boxes_radios_and_choices", "crates/forms/src/tests.rs::check_boxes_and_radios_switch_states"]
|
||
notes = "NoToggleToOff honoured; widgets without appearances get our own"
|
||
|
||
[[feature]]
|
||
id = "form.fill-choice"
|
||
title = "Choose from combo boxes and list boxes"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::check_boxes_radios_and_choices", "crates/forms/src/tests.rs::choices_accept_exports_or_display_text"]
|
||
notes = "combo and list boxes, multi-select lists; editable combo boxes accept free text"
|
||
|
||
[[feature]]
|
||
id = "form.push-buttons"
|
||
title = "Push buttons run their actions"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["form.fields"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/buttons.rs::buttons_reset_navigate_and_print", "crates/forms/src/af.rs::common_button_scripts_are_recognised", "crates/ui-egui/tests/javascript.rs::button_scripts_run_and_alert"]
|
||
notes = "Reset form, Named (print, page navigation), URI, Go to, and any JavaScript (run in the sandboxed engine); submit explained, not sent"
|
||
|
||
[[feature]]
|
||
id = "form.field-navigation"
|
||
title = "Tab between fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/forms.rs::tabbing_into_a_filled_field_selects_it_so_typing_replaces"]
|
||
notes = "Tab / Shift+Tab move between text and choice fields in field order; entering with Tab selects the value"
|
||
|
||
[[feature]]
|
||
id = "form.appearance-regeneration"
|
||
title = "Regenerate field appearances (auto-size, comb, multiline, password)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill", "form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::text_fields_get_new_appearances", "crates/forms/src/tests.rs::choices_accept_exports_or_display_text", "crates/forms/src/tests.rs::rotated_fields_draw_in_their_quadrant", "crates/engine/tests/form_rotation.rs::a_rotated_field_draws_along_the_vertical_edge", "crates/automation/tests/automation.rs::rotating_a_field_through_tools", "crates/ui-egui/tests/prepare.rs::position_tab_rotates_a_field"]
|
||
notes = "auto-size, comb, multiline, password, quadding, /MK colours and /MK /R rotation (0, 90, 180, 270); approximate Helvetica metrics"
|
||
|
||
[[feature]]
|
||
id = "form.rich-text-fields"
|
||
title = "Rich-text field values"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.af-number"
|
||
title = "Number and currency formatting (AFNumber)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::numbers_format_like_acrobat", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "AFNumber_Format/Keystroke run natively: decimals, 5 separator styles, currency before/after, negative styles (red shown as minus/parentheses)"
|
||
|
||
[[feature]]
|
||
id = "form.af-percent"
|
||
title = "Percentage formatting"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::numbers_format_like_acrobat"]
|
||
notes = "AFPercent_Format/Keystroke"
|
||
|
||
[[feature]]
|
||
id = "form.af-date-time"
|
||
title = "Date and time formatting and parsing"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::dates_times_and_masks", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat"]
|
||
notes = "AFDate_FormatEx/KeystrokeEx, AFDate_Format presets, AFTime_Format(Ex): lenient parsing, month and day names, impossible dates refused"
|
||
|
||
[[feature]]
|
||
id = "form.af-special"
|
||
title = "Special formats (zip, phone, SSN, arbitrary mask)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::dates_times_and_masks"]
|
||
notes = "zip, zip+4, phone, SSN, arbitrary masks (9 A O X)"
|
||
|
||
[[feature]]
|
||
id = "form.af-range-validate"
|
||
title = "Range validation"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::validation_and_calculation", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "AFRange_Validate with Acrobat's messages"
|
||
|
||
[[feature]]
|
||
id = "form.af-simple-calculate"
|
||
title = "Sum, product, average, min, max calculations"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::validation_and_calculation", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat"]
|
||
notes = "SUM, PRD, AVG, MIN, MAX over fields and groups"
|
||
|
||
[[feature]]
|
||
id = "form.simplified-notation"
|
||
title = "Simplified field notation calculations"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::validation_and_calculation", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "BVCALC expressions with + - * / and parentheses, escaped names"
|
||
|
||
[[feature]]
|
||
id = "form.custom-scripts"
|
||
title = "Custom format, keystroke, validate and calculate scripts"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_set_script"]
|
||
evidence = ["crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||
notes = "Custom keystroke (commit), validate, calculate and format scripts run in Acrobat's event order; AF calls still run natively."
|
||
|
||
[[feature]]
|
||
id = "form.calculation-order"
|
||
title = "Calculation order"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat"]
|
||
notes = "/CO order, then other calculated fields; recalculated after every fill and Clear form"
|
||
|
||
[[feature]]
|
||
id = "form.event-order"
|
||
title = "Keystroke, validate, calculate, format event order"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats"]
|
||
notes = "Keystroke (commit) \u2192 Validate \u2192 value \u2192 Calculate (in /CO order) \u2192 Format, for AF calls and custom scripts."
|
||
|
||
[[feature]]
|
||
id = "form.date-picker"
|
||
title = "Date picker for date fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::date_fields_offer_a_calendar"]
|
||
notes = "a month calendar under date fields; the pick is written in the field's own date format"
|
||
|
||
[[feature]]
|
||
id = "form.auto-complete"
|
||
title = "Auto-complete from previous entries"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.required-fields"
|
||
title = "Required-field highlighting"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::required_fields_get_a_red_border_when_highlighting"]
|
||
notes = "required fields get a red border when fields are highlighted"
|
||
|
||
[[feature]]
|
||
id = "form.reset-form"
|
||
title = "Clear or reset a form"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.clear"]
|
||
tools = ["form_reset"]
|
||
evidence = ["crates/forms/src/tests.rs::reset_restores_defaults", "crates/ui-egui/tests/forms.rs::check_boxes_radios_and_choices"]
|
||
notes = "back to /DV; undoable"
|
||
|
||
[[feature]]
|
||
id = "form.submit-form"
|
||
title = "Submit form to a URL or email (with a prompt)"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.import-data"
|
||
title = "Import form data (FDF, XFDF)"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.import_data"]
|
||
tools = ["doc_import_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/xfdf/src/tests.rs::fdf_round_trips_values_and_comments", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "XFDF, FDF; values go through formats and validation; read-only fields take data; XFA forms also take XDP/XML data files (form.xfa-data)"
|
||
|
||
[[feature]]
|
||
id = "form.export-data"
|
||
title = "Export form data (FDF, XFDF)"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.export_data"]
|
||
tools = ["doc_export_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "XFDF, FDF; XFA forms also export their data as XML or XDP (form.xfa-data)"
|
||
|
||
[[feature]]
|
||
id = "form.data-xml-csv-txt"
|
||
title = "Form data as XML, CSV or tab-delimited text"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.export_data", "form.import_data"]
|
||
tools = ["doc_export_data", "doc_import_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::form_data_as_xml_csv_and_text", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "Acrobat's XML (xfdf:original), CSV, tab-delimited"
|
||
|
||
[[feature]]
|
||
id = "form.merge-to-spreadsheet"
|
||
title = "Merge form data files into a spreadsheet"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.merge_data"]
|
||
tools = ["form_merge_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::data_files_merge_into_a_spreadsheet", "crates/automation/tests/automation.rs::merging_form_data_into_a_spreadsheet", "crates/ui-egui/tests/javascript.rs::merge_data_files_into_a_spreadsheet"]
|
||
notes = "FDF, XFDF or filled-in PDF forms \u2192 CSV, a column per field and a row per file."
|
||
|
||
[[feature]]
|
||
id = "form.flatten-fields"
|
||
title = "Flatten form fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.flatten"]
|
||
tools = ["doc_flatten"]
|
||
evidence = ["crates/edit/src/tests.rs::flattening_draws_appearances_into_the_page_and_removes_the_comments", "crates/engine/src/tests.rs::flattening_keeps_the_look_and_drops_the_objects", "crates/automation/tests/automation.rs::flattening_through_tools"]
|
||
notes = "widgets drawn into content and pruned from the field tree"
|
||
|
||
[[feature]]
|
||
id = "form.prepare-form-mode"
|
||
title = "Prepare a form mode"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_add_field", "form_set_props", "form_delete_field"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel", "crates/engine/src/tests.rs::preparing_a_form_adds_renames_and_deletes_fields", "crates/ui-egui/tests/ui_group/bulk_fields.rs::shared_properties_preserve_mixed_values_and_undo_the_whole_change", "crates/ui-egui/tests/ui_group/bulk_fields.rs::select_all_in_prepare_a_form_selects_only_the_current_pages_fields", "crates/ui-egui/tests/ui_group/bulk_fields.rs::dragging_an_empty_page_area_selects_fields_without_moving_them"]
|
||
notes = "fields shown with blue fill and name tags; select, move, resize by corners, Delete (every selected field, one undo step); field tools return to Select after one field. Shared General, Appearance and applicable Options edits are atomic and opt-in; Ctrl/Command+A selects the current page, rectangle and panel selection are supported. Names, positions, choices/defaults and script tabs remain individual GUI edits"
|
||
|
||
[[feature]]
|
||
id = "form.add-text-field"
|
||
title = "Add text fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.text"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "click for the default size or drag; named Text1, Text2…"
|
||
|
||
[[feature]]
|
||
id = "form.add-checkbox"
|
||
title = "Add checkboxes"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.checkbox"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field"]
|
||
notes = "Check Box1…, Yes/Off appearance states"
|
||
|
||
[[feature]]
|
||
id = "form.add-radio"
|
||
title = "Add radio buttons"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.radio"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "new group Group1… or join the selected group with the next ChoiceN"
|
||
|
||
[[feature]]
|
||
id = "form.check-box-style"
|
||
title = "Check box and radio button styles"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fields"]
|
||
evidence = ["crates/forms/src/tests.rs::check_box_styles_change_the_mark", "crates/ui-egui/tests/prepare.rs::options_tab_sets_the_check_box_style", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "Field Properties ▸ Options: check, circle, cross, diamond, square, star (/MK /CA, Acrobat's codes), drawn as paths; defaults check (check boxes) and circle (radio buttons)"
|
||
|
||
[[feature]]
|
||
id = "form.add-dropdown"
|
||
title = "Add drop-down lists"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.combo"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "Dropdown1…; items in Field Properties ▸ Options"
|
||
|
||
[[feature]]
|
||
id = "form.add-listbox"
|
||
title = "Add list boxes"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.list"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "List Box1…"
|
||
|
||
[[feature]]
|
||
id = "form.add-button"
|
||
title = "Add push buttons"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.button"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "Button1…; actions arrive with the JavaScript engine"
|
||
|
||
[[feature]]
|
||
id = "form.add-image-field"
|
||
title = "Add image fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.image"]
|
||
tools = ["form_add_field", "form_set_image"]
|
||
evidence = ["crates/forms/src/tests.rs::image_fields_ask_for_a_picture_and_show_it", "crates/ui-egui/tests/prepare.rs::image_fields_take_a_picture_when_clicked", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "icon-only push button with event.target.buttonImportIcon(), recognised natively; clicking asks for a PNG/JPEG/TIFF/GIF/BMP that becomes /MK /I, scaled proportionally and centred"
|
||
|
||
[[feature]]
|
||
id = "form.add-date-field"
|
||
title = "Add date fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.date"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "text field with AFDate_FormatEx(\"mm/dd/yyyy\") format/keystroke actions"
|
||
|
||
[[feature]]
|
||
id = "form.add-signature-field"
|
||
title = "Add digital signature fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.signature"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "unsigned /Sig field; signing is M9"
|
||
|
||
[[feature]]
|
||
id = "form.add-barcode-field"
|
||
title = "Barcode fields (PDF417, QR, Data Matrix)"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.props-general"
|
||
title = "Field properties: general (name, tooltip, visibility, read-only, required)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::properties_rename_and_delete", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/automation/tests/automation.rs::preparing_a_form_through_tools", "crates/ui-egui/tests/ui_group/bulk_fields.rs::locked_field_refuses_the_entire_batch_and_keeps_the_draft_for_correction", "crates/ui-egui/tests/ui_group/bulk_fields.rs::visiting_shared_property_tabs_without_changes_does_not_dirty_the_document"]
|
||
notes = "name, tooltip, read only, required, locked; shared tooltip/flags are opt-in, mixed values preserved, failures keep the draft and roll back the batch (visibility pending)"
|
||
|
||
[[feature]]
|
||
id = "form.props-appearance"
|
||
title = "Field properties: appearance (border, fill, font)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::appearance_properties_restyle_every_widget", "crates/ui-egui/tests/prepare.rs::appearance_tab_restyles_the_field", "crates/automation/tests/automation.rs::tab_order_and_field_appearance_through_tools", "crates/forms/src/tests.rs::partial_appearance_keeps_custom_default_appearance_and_font_resources", "crates/forms/src/tests.rs::partial_appearance_preserves_each_widgets_indirect_colours_and_border_extensions", "crates/automation/tests/form_bulk.rs::bulk_properties_are_one_undo_step_preserve_other_looks_and_survive_save"]
|
||
notes = "border/fill colour, line width, solid/dashed/beveled/inset/underline, text colour, Helvetica/Times/Courier, font size; shared GUI and partial tool edits preserve untouched custom fonts, widget styles and unknown keys. Appearance regeneration still uses approximate metrics"
|
||
|
||
[[feature]]
|
||
id = "form.props-position"
|
||
title = "Field properties: position and size"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::properties_rename_and_delete", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field"]
|
||
notes = "left/bottom/width/height in points; drag to move, corners to resize"
|
||
|
||
[[feature]]
|
||
id = "form.props-options"
|
||
title = "Field properties: options per field type"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::properties_rename_and_delete", "crates/automation/tests/automation.rs::preparing_a_form_through_tools", "crates/forms/src/tests.rs::options_tab_flags_alignment_and_defaults", "crates/ui-egui/tests/prepare.rs::options_tab_sets_flags_alignment_and_defaults", "crates/automation/tests/form_bulk.rs::bulk_options_apply_per_field_and_refuse_an_invalid_comb_atomically"]
|
||
notes = "text: alignment, default, multi-line, scroll, rich text flag, limit, password, file selection, spell check, comb; choices: items, default, sort, custom text, multiple selection, commit immediately; check boxes and radios: checked by default, radios in unison. Shared GUI offers applicable text/choice flags, alignment, text limits and check styles; choices/defaults remain individual GUI edits"
|
||
|
||
[[feature]]
|
||
id = "form.props-actions"
|
||
title = "Field properties: actions per trigger"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_actions", "form_set_actions"]
|
||
evidence = ["crates/forms/src/tests.rs::field_actions_round_trip_on_every_trigger", "crates/automation/tests/automation.rs::field_actions_through_tools", "crates/ui-egui/tests/prepare.rs::actions_tab_adds_and_removes_actions"]
|
||
notes = "Six triggers; Run a JavaScript, Open a web link, Reset a form, Execute a menu item, Go to a page view, Show/hide a field, Submit a form. Other action types are kept."
|
||
|
||
[[feature]]
|
||
id = "form.props-format"
|
||
title = "Field properties: format"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "None/Number/Percentage/Date/Time/Special with a live example; writes Acrobat's AF scripts"
|
||
|
||
[[feature]]
|
||
id = "form.props-validate"
|
||
title = "Field properties: validate"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "range from/to"
|
||
|
||
[[feature]]
|
||
id = "form.props-calculate"
|
||
title = "Field properties: calculate"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "sum/product/average/min/max of picked fields, simplified field notation; custom script pending"
|
||
|
||
[[feature]]
|
||
id = "form.props-selection-change"
|
||
title = "Field properties: selection change"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.props-signed"
|
||
title = "Field properties: signed (lock fields when signed)"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.align-distribute-fields"
|
||
title = "Align, centre, distribute and match field sizes"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::aligning_distributing_and_sizing_several_fields"]
|
||
notes = "Shift/⌘-click to select several; right-click: Align (left, right, top, bottom, centres), Distribute, Set Fields to Same Size; the right-clicked field is the anchor"
|
||
|
||
[[feature]]
|
||
id = "form.duplicate-fields"
|
||
title = "Duplicate fields across pages"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = []
|
||
evidence = ["crates/forms/src/tests.rs::duplicating_a_field_across_pages_shares_its_value", "crates/ui-egui/tests/prepare.rs::duplicating_a_field_onto_every_page"]
|
||
notes = "right-click a field ▸ Duplicate: all pages or a range; one field, shared value"
|
||
|
||
[[feature]]
|
||
id = "form.multiple-copies"
|
||
title = "Create multiple copies of a field in a grid"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.rename-lock-fields"
|
||
title = "Rename and lock fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::locked_fields_only_take_unlocking", "crates/ui-egui/tests/prepare.rs::preview_fills_the_form_and_locked_fields_keep_their_properties"]
|
||
notes = "rename in Field Properties ▸ General; Locked sets the widgets' annotation Locked flag (/F bit 8) and greys out the properties until unlocked; values can still be filled in"
|
||
|
||
[[feature]]
|
||
id = "form.tab-order"
|
||
title = "Tab order (row, column, structure, manual)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.tab_order.row", "form.tab_order.column", "form.tab_order.structure"]
|
||
tools = ["form_tab_order"]
|
||
evidence = ["crates/forms/src/tests.rs::tab_order_follows_the_page_setting", "crates/automation/tests/automation.rs::tab_order_and_field_appearance_through_tools", "crates/forms/src/tests.rs::ordering_tabs_manually", "crates/ui-egui/tests/prepare.rs::the_fields_panel_orders_tabs_manually"]
|
||
notes = "rows, columns, structure/annotation order (page /Tabs), and manual order from the Fields panel while preparing a form; honoured by Tab"
|
||
|
||
[[feature]]
|
||
id = "form.show-tab-numbers"
|
||
title = "Show tab numbers"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.preview-form"
|
||
title = "Preview mode for testing a form"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::preview_fills_the_form_and_locked_fields_keep_their_properties"]
|
||
notes = "Preview / Edit fields toggle in the Prepare a form panel"
|
||
|
||
[[feature]]
|
||
id = "form.auto-detect-fields"
|
||
title = "Automatic form field detection"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["form.detect", "form.prepare"]
|
||
tools = ["form_detect_fields"]
|
||
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools", "crates/ui-egui/tests/javascript.rs::prepare_a_form_detects_fields_on_a_paper_form", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel"]
|
||
notes = "Underscore runs, rules, empty boxes and small squares (check boxes); runs when Prepare a form starts on a document without fields, and from Prepare a form ▸ Detect form fields at any time (existing fields are skipped). Not yet: comb boxes, tables of cells, scanned forms (needs OCR first), radio groups."
|
||
|
||
[[feature]]
|
||
id = "form.auto-name-fields"
|
||
title = "Name new fields from nearby labels"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.detect"]
|
||
tools = ["form_detect_fields"]
|
||
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools"]
|
||
notes = "Names come from the label to the left (or above; right of check boxes), unique like Acrobat's (Name2, Text1)."
|
||
|
||
[[feature]]
|
||
id = "form.document-javascripts"
|
||
title = "Document-level JavaScripts"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["tools.document_js"]
|
||
tools = ["js_document_scripts", "js_set_document_script"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences", "crates/automation/tests/automation.rs::javascript_through_tools", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats"]
|
||
notes = "Names /JavaScript tree: list, add, edit, delete; functions are available to field scripts."
|
||
|
||
[[feature]]
|
||
id = "form.document-actions"
|
||
title = "Document actions (will close, will save, did save, will print, did print)"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.js-host"
|
||
title = "JavaScript engine with the Acrobat object model subset"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["tools.js_console"]
|
||
tools = ["js_run"]
|
||
evidence = ["crates/js/src/tests.rs::calculate_scripts_read_fields_and_set_the_value", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes), app, util.printf/printd/printx, console, display, color; no annotations, layers or dialogs (app.response returns null) yet."
|
||
|
||
[[feature]]
|
||
id = "form.js-console"
|
||
title = "JavaScript console"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["tools.js_console"]
|
||
tools = ["js_run"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::console_runs_scripts_against_the_document", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||
notes = "\u2318J: run JavaScript against the document, with console output, results and errors."
|
||
|
||
[[feature]]
|
||
id = "form.js-debugger"
|
||
title = "JavaScript debugger"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.edit-all-javascripts"
|
||
title = "Edit all JavaScripts in one editor"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.js-security"
|
||
title = "JavaScript enable switch and sandboxing"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["app.preferences"]
|
||
tools = ["js_enabled"]
|
||
evidence = ["crates/js/src/tests.rs::the_sandbox_has_no_host_access", "crates/js/src/tests.rs::errors_and_runaway_scripts_are_contained", "crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/document_links.rs::a_script_cannot_open_other_kinds_of_address"]
|
||
notes = "Preferences \u25b8 JavaScript \u25b8 Enable Acrobat JavaScript (remembered); no host access; loop and recursion limits; app.launchURL asks before opening and only offers http, https and mailto addresses (#90, #91)."
|
||
|
||
[[feature]]
|
||
id = "form.folder-level-js"
|
||
title = "Folder-level JavaScript"
|
||
area = "F"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.xfa-static"
|
||
title = "View and fill static XFA forms"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open", "form_fill"]
|
||
evidence = ["crates/engine/src/tests.rs::static_xfa_forms_take_their_values_from_the_datasets_and_write_them_back", "crates/ui-egui/tests/ui_group/xfa.rs::a_static_xfa_form_is_filled_from_its_data_and_says_so", "crates/xfa/src/tests.rs::static_forms_read_their_values_from_the_datasets", "crates/xfa/src/tests.rs::writing_values_keeps_the_data_no_field_binds_to", "crates/xfa/src/tests.rs::utf16_packets_are_read_and_written_back_as_utf16", "crates/engine/src/tests.rs::xfa_data_no_field_binds_to_survives_an_edit_and_save"]
|
||
notes = "the AcroForm fields are shown and filled as in any form; on open their values come from the XFA datasets (what another viewer saved) by the fields' Designer names (recorded as a document warning), and every edit merges the values back into the datasets, keeping data no field binds to, so Acrobat shows the same values; values that can't be written are warnings. Missing: no template semantics (no scripts, calculations or hidden subforms), explicit bind refs are not followed (#60)"
|
||
|
||
[[feature]]
|
||
id = "form.xfa-dynamic"
|
||
title = "View and fill dynamic XFA forms"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open", "doc_info", "form_fields", "form_fill", "js_run"]
|
||
evidence = ["crates/xfa/src/tests.rs::layout_places_captions_fields_and_sizes_containers_to_their_content", "crates/xfa/src/tests.rs::tables_break_across_pages_and_repeat_their_header_row", "crates/xfa/src/tests.rs::rendering_into_the_pdf_replaces_the_placeholder_page_and_adds_fields", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/automation/tests/automation.rs::dynamic_xfa_forms_open_render_fill_and_save_through_tools", "crates/ui-egui/tests/ui_group/xfa.rs::a_dynamic_xfa_form_is_laid_out_and_filled", "crates/xfa/src/tests.rs::nested_width_less_subforms_lay_out_in_polynomial_time", "crates/js/src/tests.rs::instance_managers_add_remove_and_count", "crates/xfa/src/tests.rs::the_form_tree_carries_values_presence_instances_and_the_events", "crates/engine/src/tests.rs::xfa_scripts_initialize_calculate_validate_toggle_and_add_rows", "crates/automation/tests/automation.rs::xfa_scripts_run_for_buttons_and_field_changes_through_tools", "crates/ui-egui/tests/ui_group/xfa.rs::xfa_buttons_run_their_scripts_in_the_app", "crates/engine/src/tests.rs::hostile_xfa_open_scripts_are_merged_capped_kept_from_printing_and_noted", "crates/engine/src/tests.rs::runaway_xfa_calculations_at_open_end_quickly_with_a_report", "crates/ui-egui/tests/ui_group/xfa.rs::a_form_whose_scripts_were_turned_off_at_open_says_so", "crates/ui-egui/tests/ui_group/xfa.rs::scripts_turned_off_by_a_click_bring_a_closed_notice_bar_back", "crates/automation/tests/automation.rs::doc_info_says_when_a_runaway_script_turned_the_forms_scripts_off", "crates/xfa/src/tests.rs::the_live_form_of_nested_repeating_subforms_stops_at_its_node_budget", "crates/xfa/src/tests.rs::data_ops_of_one_event_are_one_datasets_rewrite", "crates/xfa/src/tests.rs::choice_lists_signatures_passwords_and_pictures_become_fields_and_images", "crates/xfa/src/tests.rs::choice_values_come_from_the_data_as_saved_values_and_several_for_lists", "crates/xfa/src/tests.rs::pictures_are_sized_from_their_headers_and_hostile_ones_are_refused_unallocated", "crates/engine/src/tests.rs::xfa_choice_lists_signature_and_picture_fields_open_fill_script_and_save", "crates/xfa/src/tests.rs::repeating_subforms_inside_positioned_content_get_their_instances", "crates/engine/src/tests.rs::xfa_rows_in_areas_are_laid_out_read_calculated_and_written_where_adobe_binds_them"]
|
||
notes = "laid out from the template on open by the xfa crate (positioned, tb, lr-tb, table/row, page masters, breaks, repeated header rows, captions, rich text, JPEG/PNG/GIF images) into real pages and AcroForm fields (text, password, date with AFDate formats, check boxes, radio groups, drop-downs and list boxes with shown/saved item pairs and multi-select, signature fields, image fields showing their picture (pictures decoded once per document, at most 20 million pixels, and reused across re-layouts), buttons with reset/print/save/URL), filled from the datasets packet (including rows another viewer added), saved as an incremental revision with the values merged into the datasets so Adobe's viewers show them; doc_info reports xfa_layout. XFA JavaScript and FormCalc (form.xfa-formcalc) run in the js crate's XFA object model (xfa.form, SOM resolution, rawValue, presence, access, instance managers, xfa.host, xfa.layout, xfa.event): initialize and calculate on open, exit, validate and calculate after a field changes, click for buttons (through js_run and the app), with rows added and removed, subforms shown and hidden, and the form laid out again, all as one undo step and kept in the file. All the scripts of one event run on one thread, each in a fresh context on the form as the engine holds it then (nothing shared between scripts; the live form kept up to date in place for values, rebuilt only when the structure changed), so hundreds of calculations run within the open budget. Hostile scripts are bounded per event (merged and capped effects, messages and console lines, lower loop limits and a time budget on open and calculate, one datasets rewrite per event, print/save/links/focus from clicks only, a runaway script abandoned and the form's scripts turned off, which the app says in a toast and on the notice bar, with a button to the JavaScript console, and doc_info reports as xfa_scripts_off) and what open-time scripts changed is noted. Missing: loops inside nested calls are only stopped by abandoning their thread, and script memory is not capped; the change event per keystroke; xfa.template, connections and data descriptions; a hidden instance of a repeating subform hides them all; barcodes are drawn as their text, not bars; keep rules; metrics are approximate and Acrobat fidelity is eyeballed on real forms, not measured (#60)"
|
||
|
||
[[feature]]
|
||
id = "form.xfa-formcalc"
|
||
title = "FormCalc scripting"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open", "form_fill", "js_run"]
|
||
evidence = ["crates/js/src/tests.rs::arithmetic_strings_and_comparisons_follow_formcalc_rules", "crates/js/src/tests.rs::built_in_functions_cover_the_common_set", "crates/js/src/tests.rs::control_flow_variables_and_functions_work", "crates/js/src/tests.rs::form_objects_resolve_assign_and_record_effects", "crates/js/src/tests.rs::hostile_formcalc_scripts_end_with_an_error_and_bounded_output", "crates/js/src/tests.rs::formcalc_values_results_and_errors_stay_bounded", "crates/engine/src/tests.rs::dynamic_xfa_forms_run_formcalc_scripts", "crates/engine/src/tests.rs::hostile_formcalc_scripts_cannot_hang_or_bloat_the_document", "crates/engine/src/tests.rs::a_formcalc_calculate_result_is_cut_before_it_is_saved"]
|
||
notes = "a native FormCalc interpreter (crates/js/src/formcalc.rs) over the same form table, effects and budgets as the XFA JavaScript model: expressions and FormCalc coercions, var, if/while/for/foreach/func, accessors ($, $host, $form, $record, $layout, $event, xfa., .., [n], [*], _name instance managers) and the arithmetic, logical, string, date/time, financial and unit built-ins. Bounded: steps and loop iterations, statement and expression nesting, operator/accessor chain length, strings (1 MiB; messages and thrown errors clipped to 4 KiB, field values and calculate results to 64 KiB, noted; copying and comparing long strings is charged to the step budget, so it ends on wasm, which has no clock), lists (each object once), clamped dates, the instance cap, and a deadline the script itself honours after the engine stops waiting. Missing: locale-aware pictures beyond simple number and date ones, Format/Parse time and text pictures, Get/Post/Put (refused), $template/$connectionSet, user-defined functions across scripts, a cap on a script's total memory (each string and list is capped, but many variables can each hold one), real Uuid() values (deterministic, from the step count) and the wall clock on wasm (Date() reads 1970-01-01 and Time() midnight there), and checking against Acrobat on real Designer forms beyond the fixtures"
|
||
|
||
[[feature]]
|
||
id = "form.xfa-data"
|
||
title = "XFA XML data import and export"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["file.open", "file.save", "form.export_data", "form.import_data"]
|
||
tools = ["doc_open", "form_fill", "doc_save", "doc_export_data", "doc_import_data", "form_set_image"]
|
||
evidence = ["crates/xfa/src/tests.rs::the_datasets_packet_is_written_from_the_fields_and_read_back", "crates/xfa/src/tests.rs::data_fills_the_fields_and_adds_rows", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/xfa/src/tests.rs::data_paths_leave_areas_out", "crates/engine/src/tests.rs::xfa_rows_in_areas_are_laid_out_read_calculated_and_written_where_adobe_binds_them", "crates/xfa/src/tests.rs::data_exports_as_datasets_xml_or_an_xdp_and_imports_from_any_of_them", "crates/xfa/src/tests.rs::a_picture_chosen_for_an_image_field_is_kept_in_the_data_with_its_content_type", "crates/engine/src/tests.rs::xfa_data_exports_imports_and_pictures_round_trip_through_the_session", "crates/engine/src/tests.rs::xfa_data_import_adds_and_removes_rows_of_a_dynamic_form", "crates/automation/tests/automation.rs::xfa_data_files_export_and_import_through_tools"]
|
||
notes = "the datasets packet inside the PDF: read on open (values, dates in ISO form, check and radio states, extra rows for repeating subforms) and rewritten after every form edit from the fields' SOM paths (/PCSom), with areas left out of data paths as Adobe binds them. Export Data writes it as XML (the data alone, rooted at the form's root data node, as Acrobat does) or an XDP with the whole datasets and a reference to the PDF; Import Data takes an XDP, datasets or data XML file (or a bare data document) as the form's whole data, keeping data descriptions, lays a dynamic form out again from it (rows added and removed, more than 50 rows reported) and runs its calculations, undoable; a static form's fields take the data and the rest are emptied; the file's data is re-serialized with the packet's own prefix and every packet rewrite is parsed back before it is written; XFDF/FDF/CSV still go the AcroForm way. Files are UTF-8, UTF-16 or ISO-8859-1, at most 16 MB and 64 levels deep; pictures at most 8 MB (PNG, JPEG, GIF). Pictures chosen for image fields are kept in the data as base64 with xfa:contentType, as Designer saves them. Missing: explicit bind refs, data descriptions are kept but not checked, no data connection (WSDL/ODBC)"
|
||
|
||
[[feature]]
|
||
id = "form.xfa-flatten"
|
||
title = "Flatten XFA forms"
|
||
area = "F"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-text"
|
||
title = "Fill & Sign: add text on flat forms"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.text"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/annot/src/tests.rs::fill_and_sign_items_are_drawn", "crates/automation/tests/automation.rs::fill_and_sign_through_tools"]
|
||
notes = "typewriter FreeText, 10 pt Helvetica"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-marks"
|
||
title = "Fill & Sign: check, cross, dot, line marks"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.check", "sign.fill.cross", "sign.fill.dot", "sign.fill.line"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/annot/src/tests.rs::fill_and_sign_items_are_drawn"]
|
||
notes = "PdfCraft-drawn stamps"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-date"
|
||
title = "Fill & Sign: today's date"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.date"]
|
||
tools = ["fill_sign_add", "fill_sign_date_format"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/automation/tests/automation.rs::fill_and_sign_through_tools", "crates/ui-egui/tests/date_format.rs::fill_and_sign_dates_follow_the_date_format_preference_and_it_is_remembered", "crates/engine/src/dates.rs::presets_write_the_day_in_their_own_order", "crates/engine/src/dates.rs::names_follow_the_date_language", "crates/ui-egui/tests/date_format.rs::month_names_follow_the_date_language_or_else_the_interface_language", "crates/ui-egui/tests/date_format.rs::a_date_the_pdf_cannot_hold_is_refused_with_a_warning_first"]
|
||
notes = "local date in Preferences ▸ Date format (default m/d/yyyy; 12 presets or any pattern in Acrobat date letters with \\ escapes, checked and previewed as typed, remembered; only whole date codes accepted; month and weekday names in the interface languages, Czech months in the genitive after a day, following the interface language unless one is picked; fill_sign_add takes a one-off format and language). Beyond Acrobat: its Fill & Sign has no format setting (only Acrobat Sign accounts do). Missing: Fill & Sign text is Helvetica/WinAnsi, so a date with letters outside Western European (Czech ř, Japanese, Chinese names) is refused with a warning instead of being saved as ?; the OS locale's format isn't offered as a preset; names in languages without an interface translation"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-signature"
|
||
title = "Fill & Sign: typed, drawn, image or scanned signature and initials"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.signature", "sign.fill.initials", "sign.fill.signature.change", "sign.fill.initials.change", "sign.fill.signature.remove", "sign.fill.initials.remove"]
|
||
tools = ["fill_sign_add", "comment_edit", "comment_image_preview"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::drawn_signatures_resize_from_their_corners", "crates/ui-egui/tests/fill_sign.rs::drawn_and_typed_signatures_stay_upright_on_rotated_pages", "crates/automation/tests/automation.rs::typed_signatures_stay_upright_on_rotated_pages", "crates/ui-egui/tests/fill_sign.rs::image_signature_corners_restore_original_aspect_after_edge_resize_and_reopen", "crates/ui-egui/src/comments.rs::proportional_corners_keep_the_opposite_anchor_and_edges_stretch_one_axis", "crates/ui-egui/tests/fill_sign.rs::image_signature_live_resize_and_move_preserve_background_and_hide_moving_handles", "crates/ui-egui/tests/fill_sign.rs::embedded_image_signature_live_gestures_follow_document_and_view_rotation", "crates/automation/tests/automation.rs::image_signature_preview_layers_are_read_only_and_survive_encrypted_save", "crates/engine/src/signature_image.rs::embedded_signature_preview_preserves_alpha_without_editing_the_document", "crates/engine/src/signature_image.rs::oversized_embedded_signature_preview_is_rejected_before_decoding", "crates/ui-egui/tests/fill_sign.rs::image_signature_preview_follows_pointer_at_page_size_with_zoom_and_rotation", "crates/ui-egui/tests/fill_sign.rs::image_signature_placement_selects_resize_handles_and_requires_reselecting_to_repeat", "crates/ui-egui/tests/fill_sign.rs::typed_signatures_and_initials", "crates/ui-egui/tests/fill_sign.rs::signing_draws_a_signature_once_and_places_it", "crates/ui-egui/tests/fill_sign.rs::changing_saved_signatures_and_initials_preserves_placed_marks", "crates/ui-egui/tests/fill_sign.rs::changing_drawn_signatures_can_be_cancelled_or_switched_to_type", "crates/ui-egui/tests/fill_sign.rs::saved_signature_cards_remove_and_add_without_changing_the_document", "crates/ui-egui/tests/fill_sign.rs::long_typed_names_fit_the_placed_signature_and_keep_every_outline", "crates/ui-egui/src/fill_sign.rs::long_signature_previews_have_clear_margins", "crates/ui-egui/tests/control.rs::saved_signature_can_be_changed_through_the_control_channel", "crates/fonts/src/script.rs::a_name_becomes_outlines", "crates/fonts/src/script.rs::long_names_keep_every_glyph_with_bounded_outline_work", "crates/automation/tests/automation.rs::fill_and_sign_through_tools", "crates/ui-egui/tests/fill_sign.rs::image_signatures_and_initials_can_be_imported_placed_and_remembered", "crates/ui-egui/tests/fill_sign.rs::image_import_cancel_clear_and_errors_preserve_saved_signatures", "crates/engine/src/signature_image.rs::image_signatures_preserve_alpha_and_fit_the_signature_box", "crates/engine/src/signature_image.rs::invalid_and_oversized_signature_images_are_rejected", "crates/engine/src/signature_image.rs::large_signature_images_are_scaled_down", "crates/engine/src/signature_image.rs::jpeg_signatures_are_normalized_to_png", "crates/engine/src/signature_image.rs::jpeg_signature_orientation_is_applied_before_preview_and_embedding", "crates/engine/src/signature_image.rs::very_wide_image_signatures_keep_the_requested_rectangle", "crates/automation/tests/automation.rs::image_signatures_through_tools_preserve_transparency_and_survive_save", "crates/ui-egui/tests/fill_sign.rs::fill_and_sign_tools_pick_up_placed_marks_to_move_and_resize_them", "crates/ui-egui/tests/fill_sign.rs::select_tool_picks_up_a_signature_over_a_form_field", "crates/ui-egui/tests/fill_sign.rs::scanned_signatures_are_framed_cleaned_adjusted_placed_and_remembered", "crates/ui-egui/tests/fill_sign.rs::scan_cancel_errors_and_stale_answers_preserve_saved_signatures", "crates/automation/tests/automation.rs::scanned_signatures_through_tools_drop_the_paper_and_report_bad_arguments", "crates/engine/src/signature_scan.rs::a_photo_is_scanned_into_a_trimmed_transparent_signature_in_its_ink_colour"]
|
||
notes = "signature and initials, typed (Dancing Script outlines as filled paths, no font embedded), drawn or imported from local PNG/JPEG images; all three are placed upright and anchored as displayed on pages with /Rotate (#299); saved in settings with preview cards and add/change/remove controls; changes leave placed annotations untouched; images preserve alpha, aspect ratio and EXIF orientation, fit within 150 pt by 32 pt (24 pt for initials), and are normalized without source paths or metadata; image previews follow the pointer at placement size with zoom and rotation; signatures/initials place once then select the new annotation; while a Fill & Sign tool is active, marks already placed are picked up as with Select (hover shows the move cursor, a click selects, a drag moves, the selected mark's handles resize) and the tool places only away from them; embedded image signatures resize and move live, hiding the frame during movement and restoring it on release; corner handles preserve the original aspect ratio of the embedded image with the opposite corner anchored, while edge midpoint handles stretch width or height independently; cached background layers preserve page content and alpha, including reopened/encrypted documents; Escape cancels a gesture without committing; stamps resize through selection handles or comment_edit with their original appearance; native and web asynchronous pickers; fill_sign_add accepts path instead of text; image imports capped at 4 MiB, 32 megapixels and 8192 pixels per side, larger than 4 megapixels or 4096 pixels per side scaled down to fit (#1075); Scan tab (fill_sign_add scan/sensitivity headless): a phone photo of a signature on paper (PNG/JPEG within the same image limits, EXIF-oriented, kept at most 4 megapixels) is framed automatically (all words; table, sheet edge and dust ignored; a sideways signature turned upright), the frame can be moved, resized or redrawn and the photo turned in quarter turns, and an ink-sensitivity slider (0-100) controls how faint ink is kept; the paper is estimated by a morphological closing, so shadows and uneven light are removed, and the result is trimmed ink in its own colour on transparency, saved like an imported image. Still missing: perspective correction of a skewed sheet, live camera capture, keyboard control of the frame, and processing off the UI thread (bounded by the 4-megapixel working copy; quick 900 px previews while dragging)"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-flatten"
|
||
title = "Fill & Sign: flatten on save"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.flatten"]
|
||
tools = ["doc_save", "doc_flatten"]
|
||
evidence = ["crates/edit/src/tests.rs::fill_sign_flatten_bakes_marks_keeps_other_comments_and_does_not_reuse_pcfl0", "crates/engine/src/tests.rs::flattening_fill_and_sign_keeps_other_comments_fields_and_undoes", "crates/engine/src/tests.rs::flattening_fill_and_sign_is_refused_without_permission_to_change", "crates/automation/tests/automation.rs::saving_with_flatten_fill_sign_bakes_marks_and_leaves_other_comments", "crates/ui-egui/tests/fill_sign.rs::saving_flattens_fill_and_sign_only_when_the_preference_is_on"]
|
||
notes = "Preferences ▸ Fill & Sign, and doc_save flatten_fill_sign, bake text, marks and signatures into the page. The option defaults off, so a normal save leaves them editable. Other comments and form fields stay. Flatten comments still flattens every comment."
|
||
|
||
[[feature]]
|
||
id = "form.autofill-profile"
|
||
title = "Autofill collection synced to an Adobe account"
|
||
area = "F"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; a local profile may come later"
|
||
|
||
[[feature]]
|
||
id = "form.distribute-track"
|
||
title = "Distribute and track forms, collect responses"
|
||
area = "F"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; only submit-to-URL/email actions are supported"
|
||
|
||
[[feature]]
|
||
id = "form.web-forms"
|
||
title = "Acrobat Sign web forms"
|
||
area = "F"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud"
|
||
|
||
# ==================================================================================================
|
||
# G. Protect, redact, sanitize
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "protect.security-properties"
|
||
title = "Security properties (method, which password opened it, each permission)"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["protect.properties"]
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::password_prompt_opens_and_security_tab_reports_the_details"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.password-to-open"
|
||
title = "Protect with a password to open"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/protect.rs::protecting_for_viewing_requires_matching_passwords_and_saves_encrypted", "crates/engine/src/tests.rs::protect_with_an_open_password_then_save_reopen_and_undo", "crates/automation/tests/automation.rs::protecting_through_tools"]
|
||
notes = "Protect Using Password (Viewing); applied by the next save"
|
||
|
||
[[feature]]
|
||
id = "protect.permissions-password"
|
||
title = "Restrict editing and printing with a permissions password"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/protect.rs::editing_restrictions_via_advanced_options", "crates/engine/src/tests.rs::permissions_password_restricts_others_but_not_this_session", "crates/automation/tests/automation.rs::protecting_through_tools"]
|
||
notes = "Protect Using Password (Editing); the author keeps full rights after saving"
|
||
|
||
[[feature]]
|
||
id = "protect.permissions-matrix"
|
||
title = "Choose printing, changes, copying and accessibility permissions"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/protect.rs::editing_restrictions_via_advanced_options", "crates/engine/src/tests.rs::permissions_password_restricts_others_but_not_this_session"]
|
||
notes = "Advanced Options: printing (none/low/high), changes (5 levels), copying, screen readers"
|
||
|
||
[[feature]]
|
||
id = "protect.encryption-level"
|
||
title = "Choose encryption compatibility (RC4 40/128, AES-128, AES-256)"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/engine/src/tests.rs::protection_is_validated_undoable_and_never_logged"]
|
||
notes = "AES-256 (default), AES-128, RC4-128, RC4-40"
|
||
|
||
[[feature]]
|
||
id = "protect.encrypt-except-metadata"
|
||
title = "Encrypt everything except metadata"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/engine/src/tests.rs::protection_is_validated_undoable_and_never_logged"]
|
||
notes = "Advanced Options; disabled for RC4 levels as in Acrobat"
|
||
|
||
[[feature]]
|
||
id = "protect.encrypt-attachments-only"
|
||
title = "Encrypt only file attachments"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.password-strength"
|
||
title = "Password strength meter"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui_group/protect.rs::protecting_for_viewing_requires_matching_passwords_and_saves_encrypted"]
|
||
notes = "Weak / Medium / Strong under the password field"
|
||
|
||
[[feature]]
|
||
id = "protect.remove-security"
|
||
title = "Remove security"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.remove"]
|
||
tools = ["doc_unprotect"]
|
||
evidence = ["crates/engine/src/tests.rs::protect_with_an_open_password_then_save_reopen_and_undo", "crates/automation/tests/automation.rs::protecting_through_tools"]
|
||
notes = "needs the permissions password (or none set)"
|
||
|
||
[[feature]]
|
||
id = "protect.certificate-encryption"
|
||
title = "Encrypt with certificates for recipients"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.recipient-permissions"
|
||
title = "Per-recipient permissions for certificate security"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.recipient-ldap"
|
||
title = "Find recipients in a directory (LDAP)"
|
||
area = "G"
|
||
tier = "P3"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.security-policies"
|
||
title = "Local security policies (saved presets)"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.rights-management"
|
||
title = "Server-based rights management (AEM/LiveCycle)"
|
||
area = "G"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "server product; local policy presets only"
|
||
|
||
[[feature]]
|
||
id = "protect.mip-labels"
|
||
title = "Open Microsoft Purview-protected PDFs and apply labels"
|
||
area = "G"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "optional Windows plug-in"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-text"
|
||
title = "Mark text for redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.mark"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing", "crates/automation/tests/automation.rs::redacting_through_tools", "crates/redact/src/tests.rs::glyphs_under_a_mark_go_and_the_rest_stays_put"]
|
||
notes = "drag across text (or select first) with the Redact tool"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-area"
|
||
title = "Mark areas and images for redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.mark"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing", "crates/redact/src/tests.rs::images_are_removed_or_have_their_pixels_cleared"]
|
||
notes = "drag a box anywhere else"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-pages"
|
||
title = "Mark whole pages for redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.pages"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing"]
|
||
notes = "Mark Page Range: current page or a range"
|
||
|
||
[[feature]]
|
||
id = "protect.search-and-redact"
|
||
title = "Find text and redact"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.search"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/automation/tests/automation.rs::redacting_through_tools"]
|
||
notes = "single word or phrase, every match on every page"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-word-lists"
|
||
title = "Redact lists of words or phrases"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.search"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/redact/src/patterns.rs::word_lists_are_trimmed_unique_and_bounded", "crates/automation/tests/automation.rs::redacting_word_lists_through_tools", "crates/automation/tests/automation.rs::redaction_search_respects_case_and_word_boundaries", "crates/ui-egui/tests/ui_group/redact.rs::redacting_an_imported_word_list", "crates/ui-egui/tests/ui_group/redact.rs::redaction_search_can_match_case_and_whole_words"]
|
||
notes = "Find text and redact ▸ Multiple words or phrases: one per line, typed or imported from a .txt file (at most 1 MB, 1,000 entries of 256 characters); every match of each is marked in one undo step; redact_mark words reports marks per word. Single phrase and word-list searches offer case-sensitive and whole-words-only options in the UI and headless tool. Not yet: exporting the list"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-patterns"
|
||
title = "Redact patterns (phone, email, card, SSN, dates)"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.search"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/redact/src/patterns.rs::phone_numbers", "crates/redact/src/patterns.rs::credit_cards_need_a_valid_checksum", "crates/automation/tests/automation.rs::redacting_through_tools"]
|
||
notes = "phone (North American, E.164 with + country code, national with trunk 0), e-mail, credit card (Luhn), SSN, dates (English US forms); text drawn in a font whose built-in encoding we hold no table for (Symbol, ZapfDingbats, MacExpert) has no Unicode mapping, so a pattern search cannot see it -- it is skipped rather than mis-decoded"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-pattern-locales"
|
||
title = "Pattern sets per locale"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.redact-partial-words"
|
||
title = "Redact part of a match (e.g. all but the last four digits)"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.redact-folder"
|
||
title = "Search and redact across a folder"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.redaction-properties"
|
||
title = "Redaction fill colour and overlay text"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "partial"
|
||
commands = ["redact.properties"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing", "crates/redact/src/tests.rs::overlay_text_takes_its_font_size_colour_alignment_and_repeats"]
|
||
notes = "fill colour or none; overlay text with font, size or auto-size, colour, alignment and repeat, or redaction codes (protect.redaction-codes)"
|
||
|
||
[[feature]]
|
||
id = "protect.redaction-codes"
|
||
title = "Redaction code sets (e.g. FOIA exemptions)"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "partial"
|
||
commands = ["redact.properties"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/redact/src/tests.rs::redaction_codes_join_in_set_order_and_reject_strangers", "crates/automation/tests/automation.rs::redacting_with_codes_through_tools", "crates/ui-egui/tests/ui_group/redact.rs::redaction_codes_become_the_overlay_text"]
|
||
notes = "built-in U.S. FOIA (5 U.S.C. 552(b)) and U.S. Privacy Act (552a) sets; picked codes become the overlay text, in the set's order. Missing: Edit Codes, custom code sets, import/export of code sets"
|
||
|
||
[[feature]]
|
||
id = "protect.apply-redactions"
|
||
title = "Apply redactions (remove underlying content)"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing", "crates/engine/src/tests.rs::redaction_marks_apply_for_good_and_undo", "crates/automation/tests/automation.rs::redacting_through_tools"]
|
||
notes = "confirmation; one undo step; next save is a full rewrite"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-glyph-removal"
|
||
title = "Remove redacted text glyphs from content streams"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::glyphs_under_a_mark_go_and_the_rest_stays_put", "crates/redact/src/tests.rs::kerning_spacing_scaling_and_line_operators_are_honoured", "crates/redact/src/tests.rs::rotated_text_and_composite_fonts", "crates/redact/src/tests.rs::shared_form_xobjects_are_copied_not_changed"]
|
||
notes = "TJ displacement keeps the rest in place; simple, composite (CMaps), Type 3 fonts"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-image-pixels"
|
||
title = "Remove redacted image pixels"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::images_are_removed_or_have_their_pixels_cleared"]
|
||
notes = "covered pixels cleared in a copy; DCT/JPX/JBIG2/CCITT images removed whole"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-vectors"
|
||
title = "Remove or cut redacted vector paths"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::vectors_are_removed_or_clipped_and_inline_images_go"]
|
||
notes = "covered paths removed, others clipped; shadings clipped"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-annots-fields"
|
||
title = "Remove annotations and fields under redactions"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::comments_links_and_fields_under_a_mark_go", "crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing"]
|
||
notes = "with pop-ups; fields lose all widgets"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-struct-tree"
|
||
title = "Remove redacted content from tags and alternate text"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::tags_lose_what_redaction_removed"]
|
||
notes = "marked content changed by redaction is found by comparing each MCID before and after; its elements lose Alt, ActualText and E, and references to now-empty marked content are dropped; untouched tags stay"
|
||
|
||
[[feature]]
|
||
id = "protect.redaction-verification"
|
||
title = "Verify no residue remains after redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::unreadable_content_fails_closed", "crates/engine/src/tests.rs::redaction_marks_apply_for_good_and_undo"]
|
||
notes = "re-reads redacted pages; residue or unreadable content fails the whole operation"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-filename-suffix"
|
||
title = "Add a suffix to redacted file names"
|
||
area = "G"
|
||
tier = "P3"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.redact-ai-suggestions"
|
||
title = "Suggest sensitive content to redact via an AI provider"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "local or user-provided model; off by default"
|
||
|
||
[[feature]]
|
||
id = "protect.remove-hidden-information"
|
||
title = "Remove hidden information (selectable categories)"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.remove_hidden"]
|
||
tools = ["doc_hidden_info", "doc_remove_hidden"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/ui-egui/tests/ui_group/redact.rs::removing_hidden_information_and_sanitizing", "crates/automation/tests/automation.rs::removing_hidden_information_through_tools"]
|
||
notes = "metadata, attachments, comments, form fields (flattened), hidden text, hidden layers, bookmarks, links/actions/scripts, private data; overlapping objects and cropped content pending"
|
||
|
||
[[feature]]
|
||
id = "protect.sanitize"
|
||
title = "Sanitize document (remove all hidden information)"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.sanitize"]
|
||
tools = ["doc_remove_hidden"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/ui-egui/tests/ui_group/redact.rs::removing_hidden_information_and_sanitizing", "crates/automation/tests/automation.rs::removing_hidden_information_through_tools"]
|
||
notes = "every category, then a full rewrite on save"
|
||
|
||
[[feature]]
|
||
id = "protect.audit-faux-redactions"
|
||
title = "Audit: detect faux redactions (opaque covers over extractable text, unapplied marks)"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M8"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["doc_audit_redactions"]
|
||
evidence = ["crates/audit/src/tests.rs::faux_redaction_is_found", "crates/audit/src/tests.rs::background_before_text_is_not_flagged", "crates/audit/src/tests.rs::light_shading_after_text_is_not_flagged", "crates/audit/src/tests.rs::graphics_state_carries_across_streams", "crates/audit/src/tests.rs::audit_fails_closed_on_undecodable_stream", "crates/audit/src/tests.rs::unapplied_redact_mark_is_found", "crates/audit/src/tests.rs::rectangle_drawn_as_a_path_covers_text_like_re", "crates/audit/src/tests.rs::non_rectangular_path_is_not_a_cover", "crates/automation/tests/automation.rs::auditing_faux_redactions_through_tools"]
|
||
notes = "New pdfcraft-audit crate. A filled rectangle (re + fill, or a single m/l subpath whose corners form an axis-aligned rectangle, #1510; opaque) counts as a cover only when painted after the text it overlaps and dark enough to hide it (WCAG contrast < 3.0); page backgrounds, table shading and highlights are not flagged. Graphics state carries across a page's content streams. Fails closed: undecodable content is an error, never a clean bill. Also flags /Redact annotations never applied. CLI: pdfcraft-cli audit (fails when findings exist)."
|
||
|
||
[[feature]]
|
||
id = "protect.remove-hidden-on-close"
|
||
title = "Remove hidden information when closing or sending"
|
||
area = "G"
|
||
tier = "P3"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.attachment-trust"
|
||
title = "Block opening risky attachment types"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.url-access-control"
|
||
title = "Control internet access from PDFs"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.protected-view"
|
||
title = "Open untrusted files in a restricted view"
|
||
area = "G"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "protect.security-envelope"
|
||
title = "Security envelopes"
|
||
area = "G"
|
||
tier = "P3"
|
||
milestone = "M8"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "legacy"
|
||
|
||
# ==================================================================================================
|
||
# H. Digital signatures
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "sign.pkcs12"
|
||
title = "Sign with a PKCS#12 (.p12/.pfx) digital ID"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/crypto.rs::opens_every_openssl_flavour_of_pkcs12", "crates/sign/tests/crypto.rs::opens_pkcs12_as_windows_writes_it", "crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "PBES2 (AES, 3DES) and legacy SHA-1 3DES/RC2 files; RSA, P-256, P-384; both the DER OpenSSL writes and the BER Windows writes (indefinite lengths, segmented AuthenticatedSafe)"
|
||
|
||
[[feature]]
|
||
id = "sign.macos-keychain"
|
||
title = "Sign with macOS Keychain identities"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_keychain_ids", "sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_keychain_identities", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "Security framework identities (RSA PKCS #1 v1.5, ECDSA P-256/P-384) listed with the digital IDs in Sign with a Digital ID; the key never leaves the Keychain (ExternalKey); keychain:<fingerprint|name> for agents. The test uses a throwaway keychain file (run with --ignored)"
|
||
|
||
[[feature]]
|
||
id = "sign.windows-cert-store"
|
||
title = "Sign with the Windows certificate store"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital", "sign.certify"]
|
||
tools = ["sign_windows_ids", "sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_windows_store_identities", "crates/sign/tests/pdf.rs::a_windows_store_identity_signs_with_its_issuers", "crates/sign/tests/pdf.rs::windows_store_enumeration_and_missing_identity", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools", "crates/ui-egui/tests/signing.rs::windows_store_identity_does_not_ask_for_a_file_password", "crates/ui-egui/tests/signing.rs::an_unusable_id_is_shown_with_its_reason_and_cannot_be_chosen"]
|
||
notes = "Current User Personal (My) CNG identities: RSA PKCS #1 v1.5 and DER-encoded ECDSA P-256/P-384. Keys never leave Windows; windows:<fingerprint|common name> for agents and Windows store labels in the desktop ID picker. The signature embeds the issuers Windows chains the certificate to. Store certificates that can't sign (no private key, another key type, a key CNG can't open) are reported with the reason and Windows error code (greyed in the picker, `unusable` in sign_windows_ids) rather than left out; keys that need a prompt to open are listed and prompt at signing. Tested with throwaway software-backed RSA-2048, P-256 and P-384 certificates and a key-less certificate (run with --ignored). Smart cards and tokens whose driver registers a CNG key storage provider sign with the provider's own PIN dialog (checked by hand with a SafeNet token only; no hardware in CI); still missing: the Local Machine store, legacy CryptoAPI-only providers, P-521/Brainpool/Ed25519 keys, direct PKCS #11."
|
||
|
||
[[feature]]
|
||
id = "sign.encrypted-documents"
|
||
title = "Sign and timestamp password-protected (encrypted) documents"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital", "sign.certify"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::encrypted_documents_are_signed_with_an_unencrypted_contents", "crates/sign/tests/pdf.rs::encrypted_documents_are_signed_only_as_their_permissions_allow", "crates/sign/tests/pdf.rs::encrypted_documents_take_a_document_timestamp", "crates/cos/tests/encryption.rs::signature_contents_stay_unencrypted", "crates/automation/tests/automation.rs::signing_an_encrypted_form_through_tools"]
|
||
notes = "RC4-40/128, AES-128 and AES-256 Standard security; the incremental update keeps /Encrypt and /ID, encrypts the new strings and streams, and leaves the signature /Contents unencrypted (§7.6.2), which reading also honours. Opened with the user password, permissions (Table 22) apply: existing signature fields need fill-forms, new fields need annotate + modify, certifying needs modify, a document timestamp needs fill-forms; the owner may do anything. Not yet: certificate (public-key) security, which PdfCraft can't open; checked only against PdfCraft's own validator, not yet against an external one (pyHanko) or Acrobat"
|
||
|
||
[[feature]]
|
||
id = "sign.pkcs11"
|
||
title = "Sign with PKCS#11 tokens and smart cards"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.self-signed-id"
|
||
title = "Create a self-signed digital ID"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_id_create"]
|
||
evidence = ["crates/sign/tests/crypto.rs::new_digital_ids_are_self_signed_and_survive_a_p12_round_trip", "crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "RSA 2048/3072/4096 or P-256, saved as a password-protected .p12"
|
||
|
||
[[feature]]
|
||
id = "sign.export-certificate"
|
||
title = "Export a public certificate"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = []
|
||
evidence = ["crates/sign/tests/crypto.rs::certificates_load_from_pem_and_der_and_export_as_pem"]
|
||
notes = "Signatures panel ▸ Export certificate (.cer, PEM)"
|
||
|
||
[[feature]]
|
||
id = "sign.visible-signature"
|
||
title = "Visible signature by drawing a rectangle"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "drag a rectangle; Acrobat's name-and-details appearance"
|
||
|
||
[[feature]]
|
||
id = "sign.sign-existing-field"
|
||
title = "Sign an existing signature field"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_an_existing_field_and_counter_signing", "crates/ui-egui/tests/signing.rs::clicking_an_empty_signature_field_signs_it"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.invisible-signature"
|
||
title = "Invisible signature"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.certify_invisible"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::invisible_signatures_and_refusals", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.signature-appearance"
|
||
title = "Configure signature appearance (text, graphic, name)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/ui-egui/tests/signing.rs::the_fill_and_sign_signature_or_an_image_shows_in_a_digital_signature", "crates/sign/tests/pdf.rs::visible_signatures_show_a_drawn_typed_or_image_graphic", "crates/sign/tests/pdf.rs::malformed_signature_graphics_are_refused_not_drawn", "crates/sign/tests/pdf.rs::an_encrypted_documents_signature_graphic_is_encrypted_with_it", "crates/automation/tests/automation.rs::signing_with_an_image_typed_or_drawn_graphic_through_tools"]
|
||
notes = "text items (name, date, reason, location, DN, labels), and a graphic where the large name would be: the saved Fill & Sign signature (drawn, typed or image) or an imported PNG/JPEG (white paper made transparent, as in Fill & Sign), remembered for the next signing; alone it fills the box. Missing: named saved appearances (one remembered choice only), a PDF page as the graphic, the logo/watermark background, drawing a new signature inside the dialog"
|
||
|
||
[[feature]]
|
||
id = "sign.reason-location"
|
||
title = "Reason, location and contact info"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.lock-after-signing"
|
||
title = "Lock document or fields after signing"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.pkcs7-detached"
|
||
title = "PKCS#7 detached signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.validate"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/sign/tests/pdf.rs::validates_a_signature_made_by_openssl", "crates/sign/tests/pdf.rs::validates_signatures_written_with_ber_indefinite_lengths", "crates/sign/tests/ber.rs::deeply_nested_segmented_content_is_an_error_not_a_stack_overflow", "crates/sign/tests/ber.rs::indefinite_length_nesting_bombs_are_refused"]
|
||
notes = "validated, including BER-encoded CMS (indefinite lengths and segmented strings, as DocuSign, Documenso, Adobe PPKMS and Windows write it; the BER is reported in the details); new signatures use CAdES. Not read: BER high tag numbers. Still missing: multiple SignerInfos, counter-signatures, CMS with the signer's cert referenced but absent"
|
||
|
||
[[feature]]
|
||
id = "sign.cades-pades-bb"
|
||
title = "CAdES / PAdES B-B signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/crypto.rs::signatures_round_trip_for_every_key_type", "crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust"]
|
||
notes = "ETSI.CAdES.detached with signing-certificate-v2; pdfsig reports them valid"
|
||
|
||
[[feature]]
|
||
id = "sign.hash-algorithms"
|
||
title = "SHA-256/384/512 hashes; no SHA-1 for new signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/crypto.rs::signatures_round_trip_for_every_key_type", "crates/sign/tests/crypto.rs::rsa_signatures_are_read_with_every_digestinfo_variant", "crates/sign/tests/crypto.rs::ecdsa_signatures_are_read_as_der_non_minimal_der_or_raw", "crates/sign/tests/crypto.rs::verifies_ecdsa_on_p521_and_the_brainpool_curves", "crates/sign/tests/crypto.rs::keys_of_unknown_algorithms_are_unsupported_not_wrong", "crates/sign/tests/crypto.rs::tolerated_encodings_are_noted_and_standard_ones_are_not", "crates/sign/tests/crypto.rs::weak_digests_say_so_in_words_that_are_pinned", "crates/sign/tests/crypto.rs::an_rsa_signature_value_not_below_the_modulus_is_refused", "crates/sign/tests/crypto.rs::hostile_rsa_signature_values_are_wrong_not_crashes", "crates/sign/tests/algorithms.rs::rsa_with_sha3_sha512_256_and_ripemd160", "crates/sign/tests/algorithms.rs::rsa_pss_with_its_declared_parameters", "crates/sign/tests/algorithms.rs::ed25519_signs_the_signed_attributes_themselves", "crates/sign/tests/algorithms.rs::ecdsa_on_brainpool_p512r1", "crates/sign/tests/algorithms.rs::brainpool_p512r1_keys_must_have_coordinates_reduced_modulo_p", "crates/sign/tests/algorithms.rs::only_weak_digests_leave_a_note_when_a_signature_validates", "crates/sign/tests/pdf.rs::validates_the_legacy_adbe_x509_rsa_sha1_format"]
|
||
notes = "SHA-256, SHA-384 for P-384; SHA-1 refused for new signatures, validated in old ones. Validation also reads SHA-224, SHA-512/224, SHA-512/256, SHA3-224/256/384/512, RIPEMD-160, RSA PKCS#1 with or without the DigestInfo NULL (a bare digest without any DigestInfo is refused), RSA-PSS with its declared MGF1 hash and salt length, ECDSA as DER/non-minimal DER/raw r||s on P-256, P-384, P-521 and brainpoolP256r1/P384r1/P512r1 (the last hand-written on crypto-bigint, checked against OpenSSL), Ed25519 (RFC 8419), and the legacy adbe.x509.rsa_sha1 format. An RSA value not below the modulus is refused (RFC 8017 5.2.2). SHA-1 and RIPEMD-160 validate but the signature details say the algorithm is weak (wording pinned by tests); tolerated encodings are listed in the details. An algorithm PdfCraft cannot check gives Unknown, not Invalid. Still missing: Ed448/SHAKE, DSA, brainpool curves other than P256/P384/P512, signing with the new digests"
|
||
|
||
[[feature]]
|
||
id = "sign.certify"
|
||
title = "Certify a document (DocMDP)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.certify", "sign.certify_invisible"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::later_changes_are_classified_under_the_signature_permissions", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.certify-permissions"
|
||
title = "Allowed changes after certifying"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.certify"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::later_changes_are_classified_under_the_signature_permissions"]
|
||
notes = "DocMDP P=1/2/3 enforced when validating"
|
||
|
||
[[feature]]
|
||
id = "sign.field-mdp"
|
||
title = "Field locks (FieldMDP)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.document-timestamp"
|
||
title = "Add a document timestamp (RFC 3161)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::a_document_timestamp_covers_the_file_and_validates"]
|
||
notes = "timestamp_document appends a standalone /ETSI.RFC3161 signature dictionary over the whole file; validation discovers and verifies these (imprint, token signature, TSA cert validity) and reports the trusted time. Missing: automation tool and timestamp-server configuration (transport is caller-injected)."
|
||
|
||
[[feature]]
|
||
id = "sign.timestamp-servers"
|
||
title = "Configure timestamp servers"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.pades-bt"
|
||
title = "PAdES B-T (signature timestamp)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_a_timestamp_embeds_a_verified_rfc3161_token"]
|
||
notes = "Sign with an embedded RFC 3161 token (sign_with_timestamp): imprint and TSA signature verified at signing and validation time, trusted time reported. Missing: timestamp-server configuration and a fetcher (transport is caller-injected), the standalone document timestamp, automation tool."
|
||
|
||
[[feature]]
|
||
id = "sign.ltv"
|
||
title = "Long-term validation (DSS/VRI)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::embedding_ltv_evidence_adds_a_dss_and_keeps_signatures_valid", "crates/sign/tests/pdf.rs::an_embedded_verified_revocation_invalidates_the_signature"]
|
||
notes = "dss::embed merges caller-supplied evidence into the catalog /DSS with /VRI entries per signature, deduplicating blobs, as a permitted incremental change; revocation.rs verifies the embedded CRLs and OCSP responses (signature, responder identity, CertID, window) and a verified revocation invalidates the signature. Missing: fetching evidence, automation tool."
|
||
|
||
[[feature]]
|
||
id = "sign.pades-blta"
|
||
title = "PAdES B-LT and B-LTA"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::sign_then_ltv_then_timestamp_makes_a_b_lta_file"]
|
||
notes = "The B-LT/B-LTA chain works with caller-supplied evidence: sign, embed DSS (B-LT), then append a document timestamp over the result (B-LTA); earlier signatures stay valid with the store classified as a permitted change. Missing: OCSP/CRL verification of the evidence, re-timestamping automation."
|
||
|
||
[[feature]]
|
||
id = "sign.ocsp-crl"
|
||
title = "Revocation checking (OCSP and CRL)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/crypto.rs::crls_verify_and_report_revocation", "crates/sign/tests/crypto.rs::ocsp_responses_verify_and_match_the_certificate", "crates/sign/tests/pdf.rs::an_embedded_verified_revocation_invalidates_the_signature"]
|
||
notes = "RFC 5280 CRLs and RFC 6960 OCSP responses are parsed and verified (issuer/responder signature, delegated responder's OCSP-signing EKU, CertID hash match, validity window) and checked against the DSS during validation. Missing: network fetching of fresh evidence and the OS trust store."
|
||
|
||
[[feature]]
|
||
id = "sign.validate-all"
|
||
title = "Validate all signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.validate"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools", "crates/sign/tests/chain.rs::a_trusted_signer_whose_certificate_may_not_sign_is_not_valid"]
|
||
notes = "The signer's key usage, extended key usage and critical extensions are checked; name constraints are not"
|
||
|
||
[[feature]]
|
||
id = "sign.signatures-panel"
|
||
title = "Signatures panel with per-revision tree"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "per-signature tree with details and actions; no per-revision page outline yet"
|
||
|
||
[[feature]]
|
||
id = "sign.status-icons"
|
||
title = "Signature status (valid, unknown, invalid)"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.validation-banner"
|
||
title = "Signed/certified document message bar"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature"]
|
||
notes = "signed/valid, problems, invalid and certified bars"
|
||
|
||
[[feature]]
|
||
id = "sign.signature-properties"
|
||
title = "Signature properties (validity summary, signer, time)"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "validity summary, signer, issuer, time, algorithm in the panel; no separate properties dialog"
|
||
|
||
[[feature]]
|
||
id = "sign.certificate-viewer"
|
||
title = "Certificate viewer"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "Signatures panel ▸ Show certificate: the chain (root on top) and Summary, Details (serial, key, constraints, usage, SHA-1/SHA-256 digests) and Trust tabs; add to trusted certificates; export"
|
||
|
||
[[feature]]
|
||
id = "sign.trust-store"
|
||
title = "Trusted certificates store"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_trust"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "trusted certificates persist with the app's settings"
|
||
|
||
[[feature]]
|
||
id = "sign.trust-import"
|
||
title = "Import trusted certificates and set trust"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["sign_trust"]
|
||
evidence = ["crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "import .cer/.pem/.der/.p12 through the tool; per-certificate trust settings not yet"
|
||
|
||
[[feature]]
|
||
id = "sign.eutl"
|
||
title = "EU Trusted Lists"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.os-trust"
|
||
title = "Trust the operating system's root certificates"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.verification-time"
|
||
title = "Verification time (signing time, timestamp, now)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_a_timestamp_embeds_a_verified_rfc3161_token", "crates/sign/tests/pdf.rs::an_untrusted_timestamp_does_not_set_the_validation_time"]
|
||
notes = "Validation prefers the verified RFC 3161 timestamp time over /M when checking certificate validity and revocation, but only when the TSA chains to the trust store; an untrusted token is reported as an unverified timestamp and its time is not used. Missing: an explicit verification-time option (verify at a chosen date / now) in the validation API."
|
||
|
||
[[feature]]
|
||
id = "sign.changes-after-signing"
|
||
title = "List changes made after signing (MDP-aware diff)"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/sign/tests/pdf.rs::later_changes_are_classified_under_the_signature_permissions", "crates/sign/tests/pdf.rs::signing_an_existing_field_and_counter_signing", "crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards", "crates/sign/tests/pdf.rs::validation_data_and_xmp_added_after_signing_are_not_tampering", "crates/sign/tests/pdf.rs::a_dss_entry_does_not_make_the_object_it_names_part_of_the_store", "crates/sign/tests/pdf.rs::an_existing_dictionary_is_not_a_store_part_because_the_store_names_it", "crates/sign/tests/pdf.rs::an_existing_array_stays_what_it_is_when_the_store_points_at_it", "crates/sign/tests/pdf.rs::a_signed_store_may_grow_but_not_lose_or_swap_entries", "crates/sign/tests/pdf.rs::a_dictionary_labelled_metadata_is_not_a_metadata_change", "crates/sign/tests/pdf.rs::a_stream_already_labelled_metadata_when_signed_is_not_metadata_unless_the_catalog_names_it", "crates/sign/tests/pdf.rs::the_catalogs_own_xmp_stream_may_still_be_rewritten_after_signing"]
|
||
notes = "revision diff classified as signing, form fill, comments, metadata, page content or structure. Later revisions that add DSS data (new /Certs, /CRLs, /OCSPs, /VRI objects, or ones of the signed store that only grew) or rewrite the catalog's XMP are permitted changes; a /DSS entry that names other objects, or a /Type /DSS or /Type /Metadata label on an existing dictionary or stream, does not excuse changing them (negative tests at every DocMDP level)"
|
||
|
||
[[feature]]
|
||
id = "sign.view-signed-version"
|
||
title = "View the signed version"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.compare-signed-version"
|
||
title = "Compare signed version with the current one"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.verify-on-open"
|
||
title = "Verify signatures when a document opens"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards"]
|
||
notes = "validated when opened and after every edit"
|
||
|
||
[[feature]]
|
||
id = "sign.clear-signature"
|
||
title = "Clear an unsaved signature; clear all signature fields"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.seed-values"
|
||
title = "Signature seed values"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.preview-mode"
|
||
title = "Preview document mode before signing"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.qualified-labels"
|
||
title = "Show qualified signature/seal labels"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.web-signing"
|
||
title = "Sign in the browser with a PKCS#12 file"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.aatl"
|
||
title = "Adobe Approved Trust List"
|
||
area = "H"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe-hosted list; use OS trust stores and EU Trusted Lists"
|
||
|
||
[[feature]]
|
||
id = "sign.request-esignatures"
|
||
title = "Request e-signatures (Acrobat Sign)"
|
||
area = "H"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; pluggable provider hook only"
|
||
|
||
[[feature]]
|
||
id = "sign.cloud-signatures"
|
||
title = "Cloud signature (CSC) digital IDs via Adobe"
|
||
area = "H"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; a pluggable remote-signing KeyProvider may follow"
|
||
|
||
# ==================================================================================================
|
||
# I. Scan and OCR
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "ocr.recognize-text"
|
||
title = "Recognize text (OCR) in this file"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize", "ocr_status"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||
notes = "ocrs engine (MIT/Apache) with CC-BY-SA models fetched by `cargo xtask models`; the desktop release packages ship them (#103; packaging/env.sh stage_models), the web build does not yet; runs on a worker thread with progress; one undoable step; pages with text are skipped unless asked (ocr.skip-renderable-text)."
|
||
|
||
[[feature]]
|
||
id = "ocr.recognize-range"
|
||
title = "Recognize text in the current page or a range"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||
notes = "All pages, current page or a range."
|
||
|
||
[[feature]]
|
||
id = "ocr.recognize-multiple"
|
||
title = "Recognize text in multiple files"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize_batch"]
|
||
tools = ["ocr_recognize_files"]
|
||
evidence = ["crates/automation/tests/automation.rs::ocr_recognize_files_writes_searchable_copies", "crates/ui-egui/tests/ocr.rs::recognize_text_in_multiple_files_writes_searchable_copies"]
|
||
notes = "Pick PDFs, then an output folder; copies are saved incrementally under the same names, in the background with progress."
|
||
|
||
[[feature]]
|
||
id = "ocr.ocr-languages"
|
||
title = "OCR document language selection"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||
notes = "Language choice exists, but the models read only the Latin alphabet without accents (English)."
|
||
|
||
[[feature]]
|
||
id = "ocr.searchable-image"
|
||
title = "Searchable image output (invisible text layer)"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable"]
|
||
notes = "Invisible text (Tr 3) fitted to each word box; the image is not deskewed or downsampled (exact mode only)."
|
||
|
||
[[feature]]
|
||
id = "ocr.searchable-image-exact"
|
||
title = "Searchable image (exact): leave the image untouched"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable"]
|
||
notes = "The image is left untouched."
|
||
|
||
[[feature]]
|
||
id = "ocr.editable-text-output"
|
||
title = "Editable text and images output"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/engine/tests/ocr_edit.rs::editing_an_ocr_word_covers_the_scan_and_shows_the_new_text", "crates/engine/tests/ocr_edit.rs::an_explicit_colour_is_kept_for_the_new_text", "crates/engine/tests/ocr_edit.rs::ocr_words_group_into_phrases_that_edit_in_place", "crates/engine/tests/ocr_edit.rs::a_longer_ocr_phrase_grows_rightwards", "crates/engine/tests/ocr_edit.rs::a_rewritten_ocr_phrase_keeps_its_word_widths", "crates/ui-egui/tests/ocr.rs::an_ocr_word_opens_for_editing_and_stays_open"]
|
||
notes = "On demand, per edit: in Edit text & images, recognised text (the invisible OCR layer over a scan) is grouped into phrases (words across a word space; a wider gap such as a table cell, and each scanned line, stay separate) and opens for editing on a click. Applying draws the new text visibly in the scan's sampled ink colour (or the chosen one) and first covers the old words with the paper colour sampled around them, so the scanned word no longer shows; the phrase keeps each word's position and width and grows rightwards instead of wrapping. Still missing: Acrobat's Recognize Text output that converts a whole page to editable text at once, matching the scanned typeface (the layer's Helvetica is used), and covering on a textured or patterned background (one flat colour is used)."
|
||
|
||
[[feature]]
|
||
id = "ocr.ocr-downsample"
|
||
title = "Downsample images during OCR"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.skip-renderable-text"
|
||
title = "Skip pages that already have text"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/engine/src/tests.rs::recognize_text_reads_a_picture_on_a_page_that_has_text", "crates/automation/tests/automation.rs::ocr_recognize_reads_pictures_on_pages_with_text", "crates/ui-egui/tests/ocr.rs::recognize_text_can_read_pictures_on_pages_with_text"]
|
||
notes = "Pages that already have text are skipped and reported by default. The dialog's \"Also recognize pages that already have text\" (skip_text_pages false, also for multiple files) reads pictures on such pages (#1028); a recognised word at least half covered by the page's own text (an earlier OCR layer included) is left out and counted (already_on_page). Missing: recognising only the image areas (the whole page is still read, so it takes as long as a scan), and a word in a picture that sits under real text is treated as that text."
|
||
|
||
[[feature]]
|
||
id = "ocr.rotation-detection"
|
||
title = "Detect and fix page rotation"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.deskew"
|
||
title = "Deskew"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.despeckle"
|
||
title = "Despeckle"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.enhance_camera"]
|
||
tools = ["page_enhance_camera"]
|
||
evidence = ["crates/engine/src/camera_clean.rs::a_shadow_and_a_yellow_cast_become_white_paper", "crates/engine/src/tests.rs::enhance_camera_page_at_a_level_whitens_a_shadowed_sheet", "crates/ui-egui/tests/camera.rs::the_clean_up_level_enhances_again_from_the_photo_and_keeps_the_turn"]
|
||
notes = "Camera images only: from clean-up level 60, isolated dark pixels are removed. Missing: for scanned pages and as an OCR option."
|
||
|
||
[[feature]]
|
||
id = "ocr.background-removal"
|
||
title = "Background removal"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.enhance_camera"]
|
||
tools = ["page_enhance_camera"]
|
||
evidence = ["crates/engine/src/camera_clean.rs::a_shadow_and_a_yellow_cast_become_white_paper", "crates/engine/src/tests.rs::enhance_camera_page_at_a_level_whitens_a_shadowed_sheet", "crates/ui-egui/tests/camera.rs::the_clean_up_level_enhances_again_from_the_photo_and_keeps_the_turn"]
|
||
notes = "Camera images only: Enhance camera image's clean-up level (and page_enhance_camera level) evens out the paper's illumination per channel, so the background turns white and colour casts go; colour ink stays. Missing: the same for scanned pages and as an OCR option."
|
||
|
||
[[feature]]
|
||
id = "ocr.descreen"
|
||
title = "Descreen"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.remove-halo"
|
||
title = "Remove halos"
|
||
area = "I"
|
||
tier = "P3"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.edge-shadow-removal"
|
||
title = "Edge shadow removal"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.enhance_camera"]
|
||
tools = ["page_enhance_camera"]
|
||
evidence = ["crates/engine/src/camera_clean.rs::a_shadow_and_a_yellow_cast_become_white_paper", "crates/engine/src/tests.rs::enhance_camera_page_at_a_level_whitens_a_shadowed_sheet", "crates/ui-egui/tests/camera.rs::the_clean_up_level_enhances_again_from_the_photo_and_keeps_the_turn"]
|
||
notes = "Camera images only: the clean-up level divides out a smooth illumination estimate, which removes shadows and darker edges on the straightened photo. Missing: for scanned pages (book gutters) and as an OCR option."
|
||
|
||
[[feature]]
|
||
id = "ocr.camera-perspective"
|
||
title = "Enhance camera images (edges, perspective, contrast)"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.enhance_camera"]
|
||
tools = ["page_enhance_camera"]
|
||
evidence = ["crates/engine/src/tests.rs::enhance_camera_page_straightens_the_sheet", "crates/automation/tests/automation.rs::enhance_camera_image_through_tools", "crates/ui-egui/tests/camera.rs::dragging_a_corner_then_enhance_page_straightens_the_page", "crates/ui-egui/tests/camera.rs::after_enhance_page_crop_and_organize_take_over", "crates/engine/src/tests.rs::detect_camera_corners_finds_the_sheet_in_user_space", "crates/automation/tests/automation.rs::enhance_camera_image_finds_the_edges_by_itself"]
|
||
notes = "Four corners (any order, a convex quadrilateral) are warped to an upright page at the picture's own resolution, in one undoable step; comments, links and bookmarks stay. Scan & OCR ▸ Enhance camera image shows four corner handles on the current page (Enhance page / Cancel / Esc), then offers rotate either way, Crop pages and Organize pages for the enhanced page; page_enhance_camera does the same headlessly. The corners start on the document's edges when they are found (straight lines with an edge along most of each side, refined by a line fit; tested on synthetic photos with text, wood grain, shadows, strong perspective and a 30° turn), else inset 5%. After Enhance page a clean-up slider (0–100, default 50) re-enhances from the straightened photo: even lighting, white paper, contrast, despeckle from 60 (camera_clean). Missing: turning the result upright (text orientation), rounded-corner cards keep their background in the corners, document types."
|
||
|
||
[[feature]]
|
||
id = "ocr.camera-document-type"
|
||
title = "Camera image document types (whiteboard, card, form)"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.correct-recognized-text"
|
||
title = "Correct recognized text (low-confidence suspects)"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.adaptive-compression"
|
||
title = "Adaptive (MRC) compression of scans"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.scan-pdfa"
|
||
title = "Make scans PDF/A compliant"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.ocr-engines"
|
||
title = "Pluggable OCR engines (ocrs default; tesseract optional)"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.ocr-web"
|
||
title = "OCR in the browser"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.scanner-acquire"
|
||
title = "Scan from TWAIN/WIA/ICA/SANE scanners"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.scan-presets"
|
||
title = "Scan presets"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.scan-append"
|
||
title = "Append scans to an existing PDF or split into files"
|
||
area = "I"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "ocr.adobe-scan-sync"
|
||
title = "Adobe Scan mobile capture sync"
|
||
area = "I"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; import images from any source instead"
|
||
|
||
# ==================================================================================================
|
||
# J. Create and export
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "create.from-images"
|
||
title = "Create PDF from images (JPEG, PNG, TIFF, GIF, BMP, JPEG 2000)"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.images"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::jpeg_2000_images_are_embedded_as_is", "crates/create/src/tests.rs::images_export_as_jpeg_unchanged_and_others_as_png", "crates/create/src/tests.rs::embedded_icc_profiles_tag_the_image_colour_space"]
|
||
notes = "PNG, JPEG, TIFF (multi-page), GIF (first frame), BMP, and JPEG 2000 (JP2 or codestream, embedded as JPXDecode with its capture resolution). JPEG and PNG ICC profiles are kept as /ICCBased; TIFF profiles are not read yet"
|
||
|
||
[[feature]]
|
||
id = "create.from-multipage-tiff"
|
||
title = "Multi-page TIFF to multi-page PDF"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.images", "create.file"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::bmp_gif_and_multi_page_tiff_images"]
|
||
notes = "every page; 1-bit scans stay 1-bit; resolution from the TIFF"
|
||
|
||
[[feature]]
|
||
id = "create.from-text"
|
||
title = "Create PDF from plain text"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.file"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::text_is_wrapped_and_paginated", "crates/automation/tests/automation.rs::creating_and_reducing_through_tools"]
|
||
notes = "Helvetica 11 pt on US Letter; form feeds start pages"
|
||
|
||
[[feature]]
|
||
id = "create.from-markdown"
|
||
title = "Create PDF from Markdown"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.from-clipboard"
|
||
title = "Create PDF from the clipboard"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.clipboard"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/create.rs::clipboard_images_and_text_become_new_pdfs"]
|
||
notes = "an image (one page at its size) or text; headless tools read files instead"
|
||
|
||
[[feature]]
|
||
id = "create.from-screenshot"
|
||
title = "Create PDF from a screen capture"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.blank"
|
||
title = "Create a blank PDF"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.blank"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/ui-egui/tests/create.rs::opening_images_and_text_converts_them_to_new_pdfs", "crates/create/src/tests.rs::blank_documents", "crates/automation/tests/automation.rs::creating_and_reducing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.from-html"
|
||
title = "Create PDF from an HTML file"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.from-web-page"
|
||
title = "Create PDF from a web page URL"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.from-office"
|
||
title = "Create PDF from Office documents (LibreOffice sidecar)"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.create-multiple"
|
||
title = "Convert many files to PDF in one run"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.multiple"]
|
||
tools = ["doc_create_multiple"]
|
||
evidence = ["crates/engine/src/tests.rs::mixed_files_convert_and_combine_in_order", "crates/engine/src/tests.rs::files_that_cannot_be_converted_are_refused_clearly", "crates/automation/tests/automation.rs::creating_from_multiple_files_through_tools", "crates/ui-egui/tests/create.rs::multiple_files_open_as_one_document_in_the_page_grid", "crates/ui-egui/tests/editing.rs::plus_between_pages_inserts_picked_files_there_in_order", "crates/ui-egui/tests/editing.rs::save_pages_writes_what_the_grid_shows"]
|
||
notes = "Create ▸ Multiple files: the picked PDFs, images (PNG, JPEG, JPEG 2000, TIFF, GIF, BMP) and .txt files open as one unsaved document in the page grid, in the order picked (a bookmark per file); there pages are removed, reordered, more files inserted at any gap (a + button, or dropped there), and Save pages writes what is shown. One PDF per file is offered by the doc_create_multiple tool only (mode separate), not in the app. Missing: Office, HTML and Markdown inputs, PDF Portfolio output, per-file image resolution choice, progress and cancel for large batches (conversion runs on the UI thread). The combined file has combine's limits: untagged, no page labels or document info carried over, and its per-file bookmarks are not updated when pages are later moved between files."
|
||
|
||
[[feature]]
|
||
id = "create.image-conversion-options"
|
||
title = "Image compression options when converting"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["create.images"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::image_resolution_override_changes_size_without_resampling", "crates/automation/tests/automation.rs::creating_images_with_dpi_through_tools", "crates/ui-egui/tests/create.rs::image_import_dialog_chooses_dpi_and_cancels"]
|
||
notes = "Page sizing: embedded resolution (72 dpi fallback), fixed 72 dpi, or custom 1–1200 dpi; pixels are not resampled. Compression options are still absent. Windows image context menu opens the same chooser; other OS context-menu integration not yet."
|
||
|
||
[[feature]]
|
||
id = "create.pdf-printer"
|
||
title = "Print to PDF from other applications"
|
||
area = "J"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.distiller"
|
||
title = "PostScript/EPS to PDF (Distiller)"
|
||
area = "J"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "deferred: a PostScript interpreter is a large project"
|
||
|
||
[[feature]]
|
||
id = "create.job-options"
|
||
title = "PDF settings presets (job options)"
|
||
area = "J"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-docx"
|
||
title = "Export to Word (.docx)"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["export.docx"]
|
||
tools = ["doc_export_office"]
|
||
evidence = ["crates/export/src/lib.rs::word_does_not_invent_borders_for_aligned_invoice_fields", "crates/export/src/lib.rs::word_overlap_does_not_consume_invoice_body_space", "crates/export/src/lib.rs::word_keeps_a_page_with_a_clear_image_and_big_title_in_flow", "crates/export/src/lib.rs::word_rotated_pages_keep_separate_page_sizes_and_unique_shapes", "crates/engine/src/tests.rs::office_export_positions_follow_the_displayed_crop_rotation_and_units", "crates/automation/tests/automation.rs::exporting_an_invoice_to_word_keeps_the_watermark_out_of_flow", "crates/export/src/lib.rs::docx_is_a_word_package", "crates/export/src/lib.rs::text_colour_survives_every_format", "crates/engine/src/tests.rs::exporting_office_files_keeps_text_colour", "crates/export/src/lib.rs::grid_text_becomes_a_real_table_in_every_format", "crates/export/src/lib.rs::word_opens_text_with_control_characters_and_long_pages", "crates/engine/src/tests.rs::exporting_office_files_keeps_images", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf"]
|
||
notes = "Flowing Word document: paragraphs, Heading 1/2 from larger text, bold/italic, text colour (fill as sRGB; gray/CMYK converted naively, colour spaces via cs/sc read as black), borderless detected tables with spans/empty cells, per-page sections/sizes, and positioned editable text/artwork only for pages with rotated text or text overlapped by an image or watermark-sized text, other pages flowing with inline images (#773); synthetic invoice regression; the emitted package validates with Open XML SDK 2.20 (Office 2010 schema). Word/LibreOffice rendering of the positioned output remains unverified. The earlier flowing output was checked with macOS textutil and opened in Microsoft Word (Windows). Characters XML forbids are dropped; the page size stays within Word's 22 in. Not yet: exact font matching, vector rules, arbitrary paint-order/transparency, colour per run within a paragraph, general exact layout fidelity."
|
||
|
||
[[feature]]
|
||
id = "create.export-rtf"
|
||
title = "Export to RTF"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.rtf"]
|
||
tools = ["doc_export_office"]
|
||
evidence = ["crates/export/src/lib.rs::rtf_escapes_and_sizes", "crates/export/src/lib.rs::text_colour_survives_every_format", "crates/export/src/lib.rs::word_opens_text_with_control_characters_and_long_pages", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf"]
|
||
notes = "Paragraphs with sizes, bold, italic, text colour (colortbl), Unicode; page breaks. Images are left out."
|
||
|
||
[[feature]]
|
||
id = "create.export-xlsx"
|
||
title = "Export to Excel (.xlsx) with table detection"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-pptx"
|
||
title = "Export to PowerPoint (.pptx)"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["export.pptx"]
|
||
tools = ["doc_export_office"]
|
||
evidence = [
|
||
"crates/export/src/pptx.rs::pptx_is_a_presentationml_package",
|
||
"crates/export/src/pptx.rs::pptx_slide_has_title_body_and_images_in_order",
|
||
"crates/export/src/pptx.rs::pptx_text_colour_and_formatting",
|
||
"crates/export/src/pptx.rs::pptx_empty_pages_produce_valid_slide",
|
||
"crates/export/src/pptx.rs::pptx_slide_dimensions_and_clamping",
|
||
"crates/export/src/pptx.rs::pptx_table_cell_with_largest_font_stays_in_table_and_not_in_title",
|
||
"crates/export/src/pptx.rs::pptx_opens_in_libreoffice",
|
||
"crates/export/src/pptx.rs::pptx_package_parts_are_complete_and_consistent",
|
||
"crates/export/src/pptx.rs::hostile_sizes_do_not_overflow_the_slide_position",
|
||
"crates/export/src/lib.rs::grid_text_becomes_a_real_table_in_every_format",
|
||
"crates/export/src/lib.rs::text_colour_survives_every_format",
|
||
"crates/engine/src/tests.rs::exporting_office_files_keeps_images",
|
||
"crates/engine/src/tests.rs::exporting_office_files_keeps_text_colour",
|
||
"crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf",
|
||
"crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf",
|
||
]
|
||
notes = "PowerPoint presentation: each page becomes a slide, with the largest non-table text as slide title, remaining paragraphs as body text, images and detected tables in reading order; DrawingML formatting (font size, bold, italic, text fill colour, cell borders); wired into engine command export.pptx, automation tool doc_export_office, and Export dialog. Missing: content past the slide bottom is not paginated or shrunk across multiple slides (no normAutofit), all slides use the first page's dimensions, title is determined by largest font size rather than semantic structure, and there is no layout-preserving mode (Acrobat keeps positions). Not yet verified in PowerPoint or Keynote: pptx_opens_in_libreoffice only converts the file when LibreOffice is installed and passes silently otherwise (CI has none), so the package structure is checked by pptx_package_parts_are_complete_and_consistent instead."
|
||
|
||
[[feature]]
|
||
id = "create.export-png"
|
||
title = "Export pages as PNG"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.image"]
|
||
tools = ["doc_export_images", "page_render"]
|
||
evidence = ["crates/ui-egui/tests/export.rs::export_dialogs_write_images_and_text", "crates/engine/src/tests.rs::export_images_and_text_follow_the_working_file", "crates/automation/tests/automation.rs::exporting_images_and_text_through_tools"]
|
||
notes = "PNG at 72–600 dpi, page ranges; includes unsaved edits; background thread with progress"
|
||
|
||
[[feature]]
|
||
id = "create.export-jpeg-tiff"
|
||
title = "Export pages as JPEG or TIFF"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.image"]
|
||
tools = ["doc_export_images"]
|
||
evidence = ["crates/engine/src/tests.rs::pages_export_as_png_jpeg_and_tiff", "crates/automation/tests/automation.rs::exporting_images_and_text_through_tools"]
|
||
notes = "JPEG with quality, TIFF; flattened onto white"
|
||
|
||
[[feature]]
|
||
id = "create.export-jpeg2000"
|
||
title = "Export pages as JPEG 2000"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-all-images"
|
||
title = "Export all embedded images"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.all_images"]
|
||
tools = ["doc_export_all_images"]
|
||
evidence = ["crates/create/src/tests.rs::images_export_as_jpeg_unchanged_and_others_as_png", "crates/create/src/tests.rs::indexed_and_one_bit_images_decode", "crates/create/src/tests.rs::jpeg_images_with_a_soft_mask_export_as_png_with_alpha", "crates/create/src/tests.rs::soft_masks_of_another_size_and_stencil_and_colour_key_masks_become_alpha", "crates/automation/tests/automation.rs::exporting_all_images_through_tools", "crates/ui-egui/tests/export.rs::export_dialogs_write_images_and_text"]
|
||
notes = "each image once (page resources and nested forms); JPEG unchanged unless it has transparency, others PNG (gray/RGB/CMYK/ICC/Indexed, 1–16 bpc, stencil masks); transparency as PNG alpha: /SMask (scaled to the image size, /Matte un-blended), stencil /Mask and colour-key /Mask, JPEG images included (#645); JPX/JBIG2/CCITT masks are left out (image exported opaque); JPX/JBIG2/CCITT/separations reported as skipped; minimum size filter"
|
||
|
||
[[feature]]
|
||
id = "create.export-html"
|
||
title = "Export to HTML"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["export.html"]
|
||
tools = ["doc_export_office"]
|
||
evidence = ["crates/export/src/lib.rs::html_has_headings_paragraphs_and_images_in_order", "crates/export/src/lib.rs::text_colour_survives_every_format", "crates/export/src/lib.rs::grid_text_becomes_a_real_table_in_every_format", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf"]
|
||
notes = "Single HTML file with headings, paragraphs (with text colour), inline images, and detected tables with spans/empty cells. Not yet: multi-page folder output, CSS from fonts."
|
||
|
||
[[feature]]
|
||
id = "create.export-text"
|
||
title = "Export to plain text"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.text"]
|
||
tools = ["doc_export_text", "text_extract"]
|
||
evidence = ["crates/ui-egui/tests/export.rs::export_dialogs_write_images_and_text", "crates/automation/tests/automation.rs::exporting_images_and_text_through_tools"]
|
||
notes = "plain reading-order text; accessible text (tags) comes with M12"
|
||
|
||
[[feature]]
|
||
id = "create.export-accessible-text"
|
||
title = "Export accessible text (from tags)"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-xml"
|
||
title = "Export to XML"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-svg"
|
||
title = "Export to SVG"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-ps"
|
||
title = "Export to PostScript"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-eps"
|
||
title = "Export to EPS"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-ocr"
|
||
title = "Run OCR automatically when exporting scans"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.export-settings"
|
||
title = "Per-format export settings"
|
||
area = "J"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.text-extraction"
|
||
title = "Extract text in reading order (columns, RTL, CJK)"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["text_extract"]
|
||
evidence = ["crates/render/src/text.rs::rtl_runs_come_out_in_logical_order_and_cjk_has_no_spaces", "crates/render/src/text.rs::lines_words_and_search", "crates/automation/tests/automation.rs::parallel_text_extraction_keeps_page_order_and_follows_edits"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "create.translate"
|
||
title = "Translate documents"
|
||
area = "J"
|
||
tier = "P3"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "through an optional AiProvider; never Adobe's cloud"
|
||
|
||
[[feature]]
|
||
id = "create.cloud-office-conversion"
|
||
title = "Cloud conversion from the macOS Office add-in"
|
||
area = "J"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; LibreOffice sidecar instead"
|
||
|
||
# ==================================================================================================
|
||
# K. Optimize and standards
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "optimize.reduce-file-size"
|
||
title = "Reduce file size (one click)"
|
||
area = "K"
|
||
tier = "P0"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.reduce"]
|
||
tools = ["doc_reduce"]
|
||
evidence = ["crates/ui-egui/tests/create.rs::reduce_file_size_writes_a_compact_copy", "crates/ui-egui/tests/create.rs::reduce_file_size_saves_nothing_when_the_file_is_already_small", "crates/engine/src/tests.rs::create_and_reduce", "crates/automation/tests/automation.rs::creating_and_reducing_through_tools", "crates/automation/tests/automation.rs::optimizing_through_tools", "crates/optimize/tests/optimize.rs::an_identity_decode_array_does_not_stop_reduce"]
|
||
notes = "images above 225 ppi to 150 ppi JPEG medium, thumbnails dropped, resources merged, object streams; scanner images with the default decode array are included; a copy that isn't smaller is not saved (\"already as small as it can be made\"); runs in the background with a progress bar and Cancel"
|
||
|
||
[[feature]]
|
||
id = "optimize.compatibility-level"
|
||
title = "Make compatible with a chosen PDF version"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.optimizer"
|
||
title = "Advanced optimization (PDF Optimizer)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/ui-egui/tests/create.rs::the_pdf_optimizer_dialog_saves_an_optimized_copy", "crates/automation/tests/automation.rs::optimizing_through_tools", "crates/ui-egui/src/optimize_ui.rs::the_progress_card_shows_the_stage_and_cancels", "crates/engine/src/tests.rs::an_optimize_job_reports_its_stages_and_can_be_cancelled", "crates/optimize/tests/optimize.rs::images_and_forms_nothing_draws_are_dropped", "crates/automation/tests/automation.rs::a_deleted_image_is_not_kept_by_reduce_or_optimize"]
|
||
notes = "Images, Discard Objects, Discard User Data, Clean Up (also drops images and forms the pages list but never draw, such as deleted images); runs in the background with a progress bar (per image) and Cancel; no Fonts or Transparency panels, no space audit"
|
||
|
||
[[feature]]
|
||
id = "optimize.downsample-images"
|
||
title = "Downsample colour, grey and mono images"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled", "crates/optimize/tests/optimize.rs::settings_choose_what_happens", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "colour and grayscale, bicubic, effective resolution measured where drawn; monochrome not yet"
|
||
|
||
[[feature]]
|
||
id = "optimize.recompress-images"
|
||
title = "Recompress images (JPEG, JPEG 2000, Flate)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled", "crates/optimize/tests/optimize.rs::settings_choose_what_happens"]
|
||
notes = "JPEG (quality steps) and ZIP; no JPEG 2000"
|
||
|
||
[[feature]]
|
||
id = "optimize.mono-compression"
|
||
title = "Monochrome compression (JBIG2, CCITT G4)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.only-if-smaller"
|
||
title = "Optimize images only if smaller"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled"]
|
||
notes = "always on"
|
||
|
||
[[feature]]
|
||
id = "optimize.unembed-fonts"
|
||
title = "Unembed fonts"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.flatten-transparency"
|
||
title = "Flatten transparency"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-javascript"
|
||
title = "Discard JavaScript actions"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "with links and actions (Remove Hidden Information)"
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-thumbnails"
|
||
title = "Discard embedded thumbnails"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-tags"
|
||
title = "Discard document tags"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up", "crates/ui-egui/tests/create.rs::the_pdf_optimizer_dialog_saves_an_optimized_copy"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-bookmarks"
|
||
title = "Discard bookmarks"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-search-index"
|
||
title = "Discard embedded search index"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-print-settings"
|
||
title = "Discard embedded print settings"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-alternate-images"
|
||
title = "Discard alternate images"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-form-actions"
|
||
title = "Discard form submit, import and reset actions"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.flatten-form-fields"
|
||
title = "Flatten form fields while optimizing"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-comments"
|
||
title = "Discard comments, forms and multimedia"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-metadata"
|
||
title = "Discard document information and metadata"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/automation/tests/automation.rs::optimizing_through_tools", "crates/ui-egui/tests/create.rs::the_pdf_optimizer_dialog_saves_an_optimized_copy"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-attachments"
|
||
title = "Discard file attachments"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-private-data"
|
||
title = "Discard private application data (PieceInfo)"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-external-refs"
|
||
title = "Discard external cross-references"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-hidden-layers"
|
||
title = "Discard hidden layers and flatten visible ones"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "hidden layer content removed; visible layers not flattened"
|
||
|
||
[[feature]]
|
||
id = "optimize.merge-image-fragments"
|
||
title = "Detect and merge image fragments"
|
||
area = "K"
|
||
tier = "P3"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.lines-to-curves"
|
||
title = "Convert smooth lines to curves"
|
||
area = "K"
|
||
tier = "P3"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.compress-structure"
|
||
title = "Compress document structure (object streams)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["file.save_as"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/cos/tests/writer.rs::full_save_packs_objects_into_object_streams"]
|
||
notes = "on by default for full saves; no Optimizer option yet"
|
||
|
||
[[feature]]
|
||
id = "optimize.flate-unencoded"
|
||
title = "Flate-encode unencoded streams; replace LZW with Flate"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up"]
|
||
notes = "unencoded streams; LZW not yet re-encoded"
|
||
|
||
[[feature]]
|
||
id = "optimize.remove-invalid-links"
|
||
title = "Remove invalid links, bookmarks and unreferenced destinations"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::invalid_links_and_unreferenced_destinations_go"]
|
||
notes = "Clean Up: links to missing pages or names are removed; bookmarks keep their place (and children) but lose a broken destination; named destinations nothing refers to (links, bookmarks, actions anywhere) are removed from the name tree and the old /Dests; counts reported"
|
||
|
||
[[feature]]
|
||
id = "optimize.optimize-content"
|
||
title = "Optimize page content streams"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.audit-space"
|
||
title = "Audit space usage"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_audit_space"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::space_audit_shares_the_file_out_by_kind", "crates/automation/tests/automation.rs::auditing_space_through_tools", "crates/ui-egui/tests/viewer.rs::optimizer_audits_space_usage"]
|
||
notes = "PDF Optimizer ▸ Audit space usage: 16 categories (images, content streams, fonts, X Object forms, structure, bookmarks, thumbnails, comments, forms, named destinations, colour spaces, shadings, patterns, graphics states, embedded files, overhead) with bytes and percentages"
|
||
|
||
[[feature]]
|
||
id = "optimize.optimizer-presets"
|
||
title = "Optimizer presets"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.batch-optimize"
|
||
title = "Optimize multiple files"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight"
|
||
title = "Preflight analysis"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-fix"
|
||
title = "Preflight analyze and fix"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-profiles"
|
||
title = "Built-in preflight profiles"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-single-checks"
|
||
title = "Single checks"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-single-fixups"
|
||
title = "Single fixups"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-custom-profiles"
|
||
title = "Create and edit custom profiles"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-profile-exchange"
|
||
title = "Import, export and lock profiles"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-results"
|
||
title = "Preflight results tree with object details"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-snap-view"
|
||
title = "Snap view of a flagged object"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-reports"
|
||
title = "Preflight reports (PDF, XML, text)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-droplets"
|
||
title = "Preflight droplets and batch preflight"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.preflight-audit-trail"
|
||
title = "Embed preflight audit trail"
|
||
area = "K"
|
||
tier = "P3"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfa-validate"
|
||
title = "Verify PDF/A compliance (1-4)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_verify"]
|
||
evidence = ["crates/preflight/src/tests.rs::verify_reports_what_breaks_pdfa", "crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/preflight/src/tests.rs::verify_reports_an_output_intent_that_does_not_match_device_colour", "crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "PDF/A-2b/3b structural rules: identification, encryption, output intent (present, and RGB/CMYK to match DeviceRGB/DeviceCMYK use), fonts embedded, annotations, actions, images, embedded files. Not yet: content-stream operator checks, transparency details, PDF/A-1 and -4, a veraPDF comparison."
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfa-convert"
|
||
title = "Save as PDF/A (1b, 2b, 3b, 4)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_convert"]
|
||
evidence = ["crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/preflight/src/tests.rs::convert_does_not_give_cmyk_documents_an_srgb_output_intent", "crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "Fixes XMP, output intent (generated sRGB ICC profile, only for documents without DeviceCMYK; CMYK documents need a user-chosen CMYK output intent, not yet offered), forbidden actions, annotation flags, interpolation, encryption, AFRelationship. Not yet: embedding fonts, converting device CMYK, flattening transparency for PDF/A-1."
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfa-accessible-levels"
|
||
title = "PDF/A conformance levels a and u"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfx-validate"
|
||
title = "Verify PDF/X compliance"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfx-convert"
|
||
title = "Save as PDF/X (1a, 3, 4)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.output-intents"
|
||
title = "Choose output intents and conditions"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_convert", "pdfa_verify"]
|
||
evidence = ["crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "sRGB output intent added and listed; no choice of other conditions yet."
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfe"
|
||
title = "PDF/E"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfvt"
|
||
title = "PDF/VT"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfua-validate"
|
||
title = "Verify PDF/UA-1 and PDF/UA-2"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.standards-panel"
|
||
title = "Standards panel showing declared conformance"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_verify"]
|
||
evidence = ["crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "Declared PDF/A, PDF/UA and output intents shown in the PDF/A dialog and returned by pdfa_verify."
|
||
|
||
[[feature]]
|
||
id = "optimize.remove-standards-id"
|
||
title = "Remove PDF/A or PDF/X identification"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfx5-reference-xobjects"
|
||
title = "Show PDF/X-5 reference XObjects"
|
||
area = "K"
|
||
tier = "P3"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.einvoice-pdfa3"
|
||
title = "Embed e-invoice XML in PDF/A-3"
|
||
area = "K"
|
||
tier = "P3"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
# ==================================================================================================
|
||
# L. Print and print production
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "print.print-dialog"
|
||
title = "Print dialog"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print", "printers"]
|
||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf", "crates/engine/src/tests.rs::printing_lays_out_sheets_that_render", "crates/ui-egui/src/web_print.rs::a_job_draws_every_sheet_at_its_planned_resolution_then_builds_the_page", "crates/ui-egui/src/web_print.rs::the_print_page_turns_sideways_sheets_names_other_sizes_and_ends_without_a_blank_page"]
|
||
notes = "⌘P; settings and live sheet preview; on the web the same dialog prints through the browser's print dialog"
|
||
|
||
[[feature]]
|
||
id = "print.printer-selection"
|
||
title = "Choose printer and properties"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["printers", "printer_options", "doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list", "crates/print/src/tests.rs::driverless_printers_join_the_list_without_a_queue", "crates/print/src/tests.rs::windows_printers_carry_one_default_and_keep_their_names", "crates/print/src/tests.rs::printer_options_come_from_the_ppd", "crates/print/src/tests.rs::printer_option_defaults_and_job_arguments", "crates/ui-egui/tests/print.rs::print_dialog_shows_the_printer_driver_options", "crates/ui-egui/tests/print.rs::printers_listed_in_the_background_preselect_the_default_unless_one_was_picked", "crates/ui-egui/src/web_print.rs::resolution_falls_with_the_sheet_count_and_long_jobs_are_refused", "crates/ui-egui/src/web_print.rs::large_sheets_are_drawn_within_the_pixel_cap_and_bad_sizes_are_refused", "crates/ui-egui/src/web_print.rs::a_sink_error_or_a_page_that_isnt_there_stops_the_job"]
|
||
notes = "CUPS queues via lpstat -p -d and driverless destinations without a queue via lpstat -e; on Windows the queues and the default come from Win32_Printer (PowerShell CIM) and jobs are rendered with Windows.Data.Pdf and spooled through shared craft-print at each sheet's physical size and orientation, with bounded raster resolution up to 600 dpi (not vector passthrough); the printer list and submission run on workers without a console window. Native Windows regression execution is pending; Save as PDF on every platform. Properties…: on CUPS the driver's own PPD job options (tray, paper type, finishing …, not installable options or what the dialog sets) for this job, sent as lp -o; on Windows the driver's Printing Preferences window (printui), saved as that printer's per-user defaults rather than per job. On the web: \"Browser's print dialog\" (the default there) reaches the system's printers through the browser; PdfCraft draws the sheets itself (300 dpi up to 100 sheets, 200 up to 300, 150 up to 1,000; at most 64 megapixels a sheet and 1 GiB a job) and prints them from a hidden iframe, one per printed page, a landscape sheet among portrait ones turned onto the paper; copies, two-sided and colour are chosen in the browser's dialog; a browser that refuses to print gets the print-ready PDF as a download. Checked in headless Chromium, not yet in Firefox or Safari; pictures, not vectors; sheets of another paper size print at their own size only where the browser honours named @page sizes (Chromium doesn't)"
|
||
|
||
[[feature]]
|
||
id = "print.copies-collate"
|
||
title = "Copies and collation"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "lp -n, collate option; on Windows the driver's Copies and Collate settings, as the job's"
|
||
|
||
[[feature]]
|
||
id = "print.page-range"
|
||
title = "Print all, current page or a range (labels allowed)"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::page_selection", "crates/automation/tests/automation.rs::printing_through_tools"]
|
||
notes = "all, current, ranges with labels and open ends"
|
||
|
||
[[feature]]
|
||
id = "print.odd-even-reverse"
|
||
title = "Odd/even pages and reverse order"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::page_selection"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.scaling"
|
||
title = "Fit, actual size, shrink oversized, custom scale"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::size_modes", "crates/print/src/tests.rs::architectural_sheets_preserve_full_scale", "crates/automation/tests/automation.rs::architectural_and_iso_papers_through_tools", "crates/ui-egui/tests/print.rs::architectural_page_can_be_matched_at_100_percent"]
|
||
notes = "fit, actual size, shrink oversized, custom %; ARCH A/B/C/D/E1/E, ANSI C/D/E, ISO A0/A1/A2 presets; Match page at 100% matches the current page to a standard sheet. PDF scale shows the previewed source-to-sheet transform, not calibrated physical output; driver substitution/scaling remains possible"
|
||
|
||
[[feature]]
|
||
id = "print.paper-source-by-size"
|
||
title = "Choose paper source by PDF page size"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.duplex"
|
||
title = "Print on both sides"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "long/short edge via the spooler: lp -o sides=…, and the Windows driver's Duplex setting"
|
||
|
||
[[feature]]
|
||
id = "print.n-up"
|
||
title = "Multiple pages per sheet"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::multiple_pages_per_sheet", "crates/automation/tests/automation.rs::printing_through_tools", "crates/print/src/tests.rs::cut_stack_keeps_piles_in_order_and_blanks_in_place", "crates/print/src/tests.rs::cutting_and_stacking_recovers_every_selected_page", "crates/automation/tests/automation.rs::cut_stack_printing_through_tools", "crates/ui-egui/tests/print.rs::cut_stack_dialog_previews_saves_and_refuses_duplex"]
|
||
notes = "2/4/6/9/16, four sequential orders, borders, auto-rotate; also single-sided cut-and-stack order with aligned gutter cut marks, blank padding and range/reverse support (an extension, not an Acrobat parity claim). Duplex cut-and-stack is refused."
|
||
|
||
[[feature]]
|
||
id = "print.booklet"
|
||
title = "Booklet printing"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::booklets_pair_pages_for_folding"]
|
||
notes = "both/front/back, left/right binding"
|
||
|
||
[[feature]]
|
||
id = "print.poster"
|
||
title = "Poster / tiled printing with overlap and cut marks"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::posters_tile_with_overlap", "crates/automation/tests/automation.rs::printing_through_tools"]
|
||
notes = "tile scale, overlap, cut marks (labels pending)"
|
||
|
||
[[feature]]
|
||
id = "print.orientation"
|
||
title = "Auto portrait/landscape"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::size_modes", "crates/print/src/tests.rs::architectural_sheets_preserve_full_scale", "crates/automation/tests/automation.rs::architectural_and_iso_papers_through_tools", "crates/ui-egui/tests/print.rs::architectural_page_can_be_matched_at_100_percent"]
|
||
notes = "auto portrait/landscape"
|
||
|
||
[[feature]]
|
||
id = "print.comments-and-forms"
|
||
title = "Print document, markups, stamps or form fields only"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::imposed_pdf_has_the_sheets_and_honours_comments_and_forms"]
|
||
notes = "Print flag honoured"
|
||
|
||
[[feature]]
|
||
id = "print.print-with-comment-summary"
|
||
title = "Print with a comments summary"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.print-preview"
|
||
title = "Print preview"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf"]
|
||
notes = "live sheet preview with sheet navigation"
|
||
|
||
[[feature]]
|
||
id = "print.grayscale"
|
||
title = "Print in grayscale"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "asks the printer for monochrome (lp -o, and the Windows page settings as the job's); the PDF itself stays in colour"
|
||
|
||
[[feature]]
|
||
id = "print.print-as-image"
|
||
title = "Print as image"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.page-setup"
|
||
title = "Page setup"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.print-ready-pdf"
|
||
title = "Produce a print-ready imposed PDF"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf", "crates/automation/tests/automation.rs::printing_through_tools", "crates/engine/src/tests.rs::printing_lays_out_sheets_that_render"]
|
||
notes = "Save as PDF from the dialog"
|
||
|
||
[[feature]]
|
||
id = "print.print-pages-from-thumbnails"
|
||
title = "Print selected pages from thumbnails"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::print_shortcut_offers_the_pages_picked_in_the_pages_panel", "crates/ui-egui/tests/editing.rs::pages_panel_command_click_picks_pages_without_moving_the_document", "crates/ui-egui/tests/editing.rs::pages_panel_shift_click_picks_a_range_and_a_plain_click_starts_over", "crates/ui-egui/tests/print.rs::print_dialog_prints_the_selected_pages", "crates/print/src/tests.rs::page_selection"]
|
||
notes = "Ctrl/⌘-click and ⇧-click pick thumbnails in the Pages panel (and the Organize grid); Print opens on \"Selected pages\" and prints them in page order. Missing: the thumbnails' own context-menu Print command, picking by dragging a marquee, arrow-key selection, and a printer on Windows (Save as PDF only there, see print.printer-selection)."
|
||
|
||
[[feature]]
|
||
id = "print.postscript-output"
|
||
title = "PostScript Level 3 output"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M10"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.output-preview"
|
||
title = "Output preview"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.separations"
|
||
title = "Separations preview with area coverage"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.total-area-coverage"
|
||
title = "Total area coverage warning"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.color-warnings"
|
||
title = "Overprint and rich-black warnings"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.object-inspector"
|
||
title = "Object inspector"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.simulate-paper-ink"
|
||
title = "Simulate paper colour and black ink"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.overprint-preview"
|
||
title = "Overprint preview"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.convert-colors"
|
||
title = "Convert colours"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.ink-manager"
|
||
title = "Ink manager"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.ink-aliases"
|
||
title = "Ink aliases and spot-to-process"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.printer-marks"
|
||
title = "Add printer marks (crop, bleed, registration, colour bars)"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.fix-hairlines"
|
||
title = "Fix hairlines"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.flattener-preview"
|
||
title = "Flattener preview"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.color-management"
|
||
title = "Colour management settings (working spaces, intents)"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.advanced-print-separations"
|
||
title = "Print separations and in-RIP separations"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.print-marks-bleeds"
|
||
title = "Marks and bleeds when printing"
|
||
area = "L"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.trap-presets"
|
||
title = "Trap presets (stored as data)"
|
||
area = "L"
|
||
tier = "P3"
|
||
milestone = "M11"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.jdf"
|
||
title = "JDF job definitions (read-only)"
|
||
area = "L"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
# ==================================================================================================
|
||
# M. Accessibility
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "a11y.app-screen-reader"
|
||
title = "Screen-reader support for the app (AccessKit)"
|
||
area = "M"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::inspect_and_click_by_label_and_id"]
|
||
notes = "AccessKit labels on all custom widgets; full audit in M14.3"
|
||
|
||
[[feature]]
|
||
id = "a11y.keyboard-only"
|
||
title = "Full keyboard operation of the app"
|
||
area = "M"
|
||
tier = "P0"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.checker"
|
||
title = "Accessibility checker (full check)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::an_untagged_document_fails_the_document_rules", "crates/a11y/src/tests.rs::a_well_tagged_document_passes", "crates/a11y/src/tests.rs::structure_findings_identify_indirect_elements_without_guessing_inline_objects", "crates/a11y/src/tests.rs::figures_are_listed_with_their_alt_text_and_place", "crates/a11y/src/tests.rs::annotation_findings_only_locate_valid_rectangles", "crates/automation/tests/automation.rs::accessibility_check_locates_field_description_on_page", "crates/ui-egui/tests/accessibility.rs::checking_fixing_skipping_and_reporting", "crates/ui-egui/tests/accessibility.rs::checker_shows_a_figure_finding_on_its_page", "crates/ui-egui/tests/accessibility.rs::checker_summary_does_not_hide_manual_review", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "full check: options dialog (report, page range, 32 rules by category, 31 on by default), results panel by category with Passed / Failed / Needs manual check / Skipped and findings that go to their page. A zero-failure check reports manual or skipped rules rather than claiming no issues. Locatable figure, annotation and form-widget findings flash their bounds on the page; accessibility_check exposes those bounds in PDF user space. Other findings navigate to their page. Structure findings expose the indirect tag object number and generation through accessibility_check, when available, for precise follow-up in the Tags Panel."
|
||
|
||
[[feature]]
|
||
id = "a11y.checker-report"
|
||
title = "Accessibility report"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.report"]
|
||
tools = ["accessibility_report", "accessibility_review_record", "accessibility_review_html"]
|
||
evidence = ["crates/a11y/src/tests.rs::rules_have_ids_and_the_report_lists_them", "crates/ui-egui/tests/accessibility.rs::checking_fixing_skipping_and_reporting", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools", "crates/automation/tests/automation.rs::accessibility_review_record_compares_two_versions_without_changing_them", "crates/automation/src/a11y.rs::document_names_are_escaped_in_review_html"]
|
||
notes = "HTML checker report: summary counts and a table per category with descriptions and findings. The headline shows manual or skipped rules when there are no automated failures. Headless comparison of two open versions exports the same before-and-after evidence as JSON or accessible, self-contained HTML with hashes, rule changes, and remaining review items."
|
||
|
||
[[feature]]
|
||
id = "a11y.checker-fix"
|
||
title = "Fix, skip, explain and re-check rules"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_fix", "accessibility_review_record"]
|
||
evidence = ["crates/ui-egui/tests/accessibility.rs::checking_fixing_skipping_and_reporting", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools", "crates/automation/tests/automation.rs::accessibility_review_record_compares_two_versions_without_changing_them"]
|
||
notes = "right-click a rule: Fix (language via Document Properties, title, tab order), Skip Rule (kept on Check Again), Explain, Check Again, Show Report, Options. The panel compares outcome counts and changed rules with the previous check of the same document and options; the headless review record compares two open revisions."
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-permission-flag"
|
||
title = "Rule: accessibility permission flag"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::an_untagged_document_fails_the_document_rules"]
|
||
notes = "rule permission-flag: the file's /P bit 10 whichever password opened it"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-image-only"
|
||
title = "Rule: image-only PDF"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::scans_identity_fonts_and_long_documents"]
|
||
notes = "rule image-only: images and no text on the checked pages"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-pdf"
|
||
title = "Rule: tagged PDF"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::an_untagged_document_fails_the_document_rules"]
|
||
notes = "rule tagged-pdf: StructTreeRoot and MarkInfo Marked true"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-logical-order"
|
||
title = "Rule: logical reading order (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule logical-reading-order: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-primary-language"
|
||
title = "Rule: primary language"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule primary-language: catalog /Lang; fix via Document Properties or accessibility_fix"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-title"
|
||
title = "Rule: title shown in the window"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule title: Info Title and DisplayDocTitle; Fix sets both"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-bookmarks"
|
||
title = "Rule: bookmarks in long documents"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::scans_identity_fonts_and_long_documents"]
|
||
notes = "rule bookmarks: more than 20 pages need bookmarks"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-color-contrast"
|
||
title = "Rule: colour contrast (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule color-contrast: needs manual check; off by default as in Acrobat"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-content"
|
||
title = "Rule: all content tagged or artifact"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule tagged-content: painting outside MCID or Artifact marked content, through form XObjects; counted per page"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-annotations"
|
||
title = "Rule: tagged annotations"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule tagged-annotations: annotations other than popups, widgets and multimedia need /StructParent"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tab-order"
|
||
title = "Rule: tab order follows structure"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule tab-order: pages with annotations need /Tabs /S; Fix sets it on every page"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-character-encoding"
|
||
title = "Rule: reliable character encoding"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::scans_identity_fonts_and_long_documents"]
|
||
notes = "rule character-encoding: ToUnicode, a standard encoding, or a predefined CJK collection; each font once"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-multimedia"
|
||
title = "Rule: tagged multimedia"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule tagged-multimedia: Screen, Movie, Sound, RichMedia and 3D annotations need /StructParent"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-screen-flicker"
|
||
title = "Rule: screen flicker (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule screen-flicker: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-scripts"
|
||
title = "Rule: scripts (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule scripts: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-timed-responses"
|
||
title = "Rule: timed responses (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule timed-responses: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-navigation-links"
|
||
title = "Rule: navigation links (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule navigation-links: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-fields"
|
||
title = "Rule: tagged form fields"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule tagged-form-fields: widgets need /StructParent"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-field-descriptions"
|
||
title = "Rule: field descriptions"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule field-descriptions: fields need a tooltip (/TU)"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-figure-alt"
|
||
title = "Rule: figures have alternate text"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule figures-alt-text: Figure elements need Alt or ActualText (or an ancestor's)"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-nested-alt"
|
||
title = "Rule: no nested alternate text"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule nested-alt-text: Alt inside an element with Alt"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-alt-associated"
|
||
title = "Rule: alternate text associated with content"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule alt-text-associated: Alt on an element with no content beneath it"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-alt-hides-annot"
|
||
title = "Rule: alternate text does not hide annotations"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule alt-text-hides-annotation: Alt on an element containing an OBJR"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-other-alt"
|
||
title = "Rule: other elements have alternate text"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule other-elements-alt-text: Formula elements need Alt"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-rows"
|
||
title = "Rule: table rows"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-rows: TR parent is Table, THead, TBody or TFoot"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-cells"
|
||
title = "Rule: TH and TD in rows"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-cells: TH/TD parent is TR"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-headers"
|
||
title = "Rule: tables have headers"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-headers: at least one TH"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-regularity"
|
||
title = "Rule: table regularity"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::row_and_column_spans_keep_a_table_regular"]
|
||
notes = "rule table-regularity: equal row widths counting ColSpan and RowSpan"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-summary"
|
||
title = "Rule: table summary"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-summary: Summary table attribute"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-list-items"
|
||
title = "Rule: list items"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule list-items: LI parent is L"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-lbl-lbody"
|
||
title = "Rule: Lbl and LBody"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule lbl-lbody: Lbl/LBody parent is LI (TOCI and notes allowed)"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-heading-nesting"
|
||
title = "Rule: heading nesting"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule heading-nesting: H1–H6 never skip a level going down; the first is H1"
|
||
|
||
[[feature]]
|
||
id = "a11y.autotag"
|
||
title = "Automatically tag a document"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["accessibility_autotag_preview"]
|
||
evidence = ["crates/automation/tests/automation.rs::autotag_preview_proposes_text_without_changing_the_pdf"]
|
||
notes = "Preview proposes paragraph or heading roles for bounded visible text blocks on one page and flags them for review. It does not write tags, mark content, build a parent tree or classify images, tables, lists and annotations."
|
||
|
||
[[feature]]
|
||
id = "a11y.autotag-fields"
|
||
title = "Autotag form fields"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.reading-order-tool"
|
||
title = "Reading order tool"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["accessibility_move_tag"]
|
||
evidence = ["crates/a11y/src/tests.rs::sibling_tags_move_in_reading_order_without_changing_content", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "The headless tool can move a reachable indirect tag before or after a direct sibling in their parent's /K array as one undoable edit. It preserves page content and rejects ambiguous children. The visual Reading Order tool, inline tags, arbitrary retagging, and moving across parents remain planned."
|
||
|
||
[[feature]]
|
||
id = "a11y.tags-panel"
|
||
title = "Tags panel (new, delete, find, change to artifact)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["a11y.tags"]
|
||
tools = ["accessibility_tags"]
|
||
evidence = ["crates/ui-egui/tests/accessibility.rs::tags_panel_edits_a_tag_property_and_undoes_it", "crates/ui-egui/tests/accessibility.rs::checker_finding_opens_its_structure_tag"]
|
||
notes = "The right-side Tags panel lists reachable indirect tags as a collapsible hierarchy, filters by type or title with ancestors, navigates to their page and edits properties. Checker findings linked to indirect tags open the matching tag without losing unsaved edits. Inline tags, adding/deleting tags, artifact marking and finding content remain planned in #982."
|
||
|
||
[[feature]]
|
||
id = "a11y.tag-properties"
|
||
title = "Tag properties (type, title, actual text, alternate text, language)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["a11y.tags"]
|
||
tools = ["accessibility_tags", "accessibility_set_tag_property"]
|
||
evidence = ["crates/a11y/src/tests.rs::reachable_tag_properties_round_trip_and_reject_unrelated_objects", "crates/a11y/src/tests.rs::tag_hierarchy_keeps_indirect_parent_through_inline_tags", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools", "crates/ui-egui/tests/accessibility.rs::tags_panel_edits_a_tag_property_and_undoes_it"]
|
||
notes = "The engine, headless tools and right-side editor list reachable indirect tags with parent and depth and edit type, title, actual text, alternate text and language with undo. Inline tag dictionaries and structural operations remain planned with the Tags Panel (#982)."
|
||
|
||
[[feature]]
|
||
id = "a11y.role-map"
|
||
title = "Role map editor"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.class-map"
|
||
title = "Class map editor"
|
||
area = "M"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.order-panel"
|
||
title = "Order panel"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["a11y.tags"]
|
||
tools = ["accessibility_move_tag"]
|
||
evidence = ["crates/ui-egui/tests/accessibility.rs::tags_panel_moves_a_tag_within_its_siblings", "crates/a11y/src/tests.rs::sibling_tags_move_in_reading_order_without_changing_content", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "The Tags panel moves a selected reachable indirect tag earlier or later among direct siblings, preserving page appearance and making one undoable reading-order edit. It does not yet show page-content order visually, edit direct tag dictionaries, move across parents, or reorder arbitrary content."
|
||
|
||
[[feature]]
|
||
id = "a11y.content-panel"
|
||
title = "Content panel"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.create-tag-from-selection"
|
||
title = "Create a tag from a selection"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.artifact-marking"
|
||
title = "Mark content as artifact"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.find-untagged"
|
||
title = "Find unmarked content, comments and links"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.table-editor"
|
||
title = "Table editor (headers, scope, spans)"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.alt-text-workflow"
|
||
title = "Set alternate text figure by figure (with decorative option)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.alt_text"]
|
||
tools = ["accessibility_figures", "accessibility_set_alt"]
|
||
evidence = ["crates/a11y/src/tests.rs::figures_are_listed_with_their_alt_text_and_place", "crates/a11y/src/tests.rs::setting_alt_text_and_marking_decorative", "crates/ui-egui/tests/accessibility.rs::setting_alternate_text_figure_by_figure", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "Set Alternate Text dialog: each tagged figure (role map honoured) with a preview of where it is drawn, its alternate text, Decorative figure (content becomes /Artifact, element leaves the tags and parent tree), Previous/Next, Save & Close as one undo step"
|
||
|
||
[[feature]]
|
||
id = "a11y.alt-text-suggestions"
|
||
title = "Suggest alternate text via an AI provider"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "optional provider; off by default"
|
||
|
||
[[feature]]
|
||
id = "a11y.set-language"
|
||
title = "Set document language"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/automation/tests/automation.rs::initial_view_through_tools", "crates/ui-egui/tests/viewer.rs::initial_view_is_edited_and_honoured_on_open"]
|
||
notes = "Document Properties ▸ Initial View ▸ Language (catalog /Lang)"
|
||
|
||
[[feature]]
|
||
id = "a11y.set-title-display"
|
||
title = "Show the document title in the window"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/automation/tests/automation.rs::initial_view_through_tools", "crates/ui-egui/tests/viewer.rs::tab_and_window_show_the_document_title_when_asked"]
|
||
notes = "Initial View ▸ Show: Document Title (/DisplayDocTitle); the tab and window then show the title"
|
||
|
||
[[feature]]
|
||
id = "a11y.make-accessible-action"
|
||
title = "Make Accessible guided action"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Headless review sidecar tools save and reopen human decisions against the exact working PDF SHA-256. The guided GUI action and step-by-step remediation are still missing."
|
||
|
||
[[feature]]
|
||
id = "a11y.setup-assistant"
|
||
title = "Accessibility setup assistant"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Review decisions can be saved and reopened through accessibility_review_save and accessibility_review_load. A GUI assistant is still missing."
|
||
|
||
[[feature]]
|
||
id = "a11y.read-aloud"
|
||
title = "Read out loud (platform TTS)"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.read-aloud-options"
|
||
title = "Read-aloud voice, pitch and speed"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.replace-page-colors"
|
||
title = "Replace page colours (high contrast)"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["view_reading_colors"]
|
||
evidence = ["crates/automation/tests/automation.rs::reading_colors_through_tools", "crates/render/src/raster.rs::reading_colors_map_solid_paints_and_leave_images", "crates/render/src/raster.rs::reading_colors_tiles_match_full_render", "crates/render/src/raster.rs::print_preview_requests_render_with_reading_colors_off", "crates/ui-egui/tests/reading_colors.rs::reading_colours_preference_round_trips", "crates/ui-egui/tests/reading_colors.rs::reading_colours_seed_on_open"]
|
||
notes = "View only, off by default: Preferences ▸ Reading colours holds the app-wide preference, seeded into every document as it opens (including created ones), and view_reading_colors reads and sets it per document (Acrobat's Replace Document Colors). Solid text paints and, by default, line-art paints (including stencil-mask image paints) are remapped by luminance — each lerps between the preset's text and background colour by how dark or light the original paint is — while images, shadings and tiling patterns pass through untouched. Presets: black on white, white on black, yellow on black, green on black, or a custom #RRGGBB pair; only_black limits the mapping to near-black paints (a 10% luminance threshold — our choice; Acrobat's was not observed). Display only: the document is not changed and print and export are unaffected; print previews and view snapshots render with the document's true colours (a preview matches the uncoloured printout, and a snapshot is an export surface), so reading colours apply to the reading view only. Closes #897 (duplicate #488)."
|
||
|
||
[[feature]]
|
||
id = "a11y.reflow-tagged"
|
||
title = "Reflow tagged content"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "a11y.keyboard-selection-cursor"
|
||
title = "Always show the keyboard selection cursor"
|
||
area = "M"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
# ==================================================================================================
|
||
# N. Compare, automation, AI, preferences, misc
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "misc.compare-text"
|
||
title = "Compare files: text differences"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["doc.compare"]
|
||
tools = ["doc_compare", "doc_compare_mark"]
|
||
evidence = ["crates/compare/src/lib.rs::insertions_deletions_and_replacements", "crates/engine/src/tests.rs::comparing_two_versions_of_a_document", "crates/automation/tests/automation.rs::comparing_documents_through_tools", "crates/ui-egui/tests/compare.rs::compare_two_versions"]
|
||
notes = "Word-level Myers diff with patience anchors for long files; replaced/inserted/deleted shaded on the new file, listed in the Compare panel, or added as comments."
|
||
|
||
[[feature]]
|
||
id = "misc.compare-visual"
|
||
title = "Compare files: visual differences"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["doc.compare"]
|
||
tools = ["doc_compare"]
|
||
evidence = ["crates/compare/src/lib.rs::finds_changed_regions", "crates/engine/src/tests.rs::comparing_two_versions_of_a_document", "crates/automation/tests/automation.rs::comparing_documents_through_tools"]
|
||
notes = "Pages rendered at 72 dpi and compared page by page; differing regions shaded orange where no text change explains them."
|
||
|
||
[[feature]]
|
||
id = "misc.compare-side-by-side"
|
||
title = "Side-by-side compare view with synchronised scrolling"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.compare-report"
|
||
title = "Compare summary report as PDF"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["doc.compare"]
|
||
tools = ["doc_compare_report"]
|
||
evidence = ["crates/engine/src/tests.rs::comparing_two_versions_of_a_document", "crates/automation/tests/automation.rs::comparing_documents_through_tools", "crates/ui-egui/tests/compare.rs::compare_two_versions"]
|
||
notes = "Summary report as a new PDF (counts and every change with pages)."
|
||
|
||
[[feature]]
|
||
id = "misc.compare-filters"
|
||
title = "Filter changes by type (text, images, formatting)"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.compare-page-ranges"
|
||
title = "Compare page ranges"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.compare-scanned"
|
||
title = "Compare scanned documents via OCR"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.action-wizard-run"
|
||
title = "Run guided actions on files"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = ["actions.wizard"]
|
||
tools = ["action_list", "action_run"]
|
||
evidence = ["crates/ui-egui/tests/actions.rs::desktop_actions_preserve_duplicate_names_and_results_from_earlier_runs", "crates/engine/src/tests.rs::actions_run_their_steps_on_files", "crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "Desktop output filenames receive numbered suffixes when occupied, preserving every batch result and previous outputs. Pick an action and files; results go to a chosen folder, in the background with progress."
|
||
|
||
[[feature]]
|
||
id = "misc.action-wizard-create"
|
||
title = "Create and edit actions"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = ["actions.wizard"]
|
||
tools = ["action_run"]
|
||
evidence = ["crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "New/Edit/Delete actions with 12 step kinds (OCR, reduce size, remove hidden information, sanitize, flatten, watermark, header, footer, title, detect fields, JavaScript); remembered between runs."
|
||
|
||
[[feature]]
|
||
id = "misc.action-wizard-manage"
|
||
title = "Import, export, copy and delete actions"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.action-prepare-distribution"
|
||
title = "Built-in action: prepare for distribution"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = ["actions.distribution"]
|
||
tools = ["action_run"]
|
||
evidence = ["crates/engine/src/tests.rs::actions_run_their_steps_on_files", "crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "Remove hidden information, flatten comments and fields."
|
||
|
||
[[feature]]
|
||
id = "misc.action-optimize-scans"
|
||
title = "Built-in action: optimize scanned documents"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = ["actions.optimize_scans"]
|
||
tools = ["action_run"]
|
||
evidence = ["crates/engine/src/tests.rs::actions_run_their_steps_on_files", "crates/automation/tests/automation.rs::actions_through_tools"]
|
||
notes = "Recognize text, then reduce file size."
|
||
|
||
[[feature]]
|
||
id = "misc.action-archive"
|
||
title = "Built-in action: archive documents"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.action-publish-web"
|
||
title = "Built-in action: prepare for web publishing"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.batch-processing"
|
||
title = "Batch processing over files and folders"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "partial"
|
||
commands = ["actions.wizard", "ocr.recognize_batch"]
|
||
tools = ["action_run", "ocr_recognize_files"]
|
||
evidence = ["crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "Actions and OCR run over chosen files; whole folders (with subfolders) not yet."
|
||
|
||
[[feature]]
|
||
id = "misc.action-prompt-steps"
|
||
title = "Prompt-user and instruction steps"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.action-javascript-step"
|
||
title = "Execute JavaScript step"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.custom-commands"
|
||
title = "Custom commands"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.custom-tools"
|
||
title = "Custom tool sets"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.command-registry"
|
||
title = "One command registry for menus, shortcuts, palette and automation"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["command_list"]
|
||
evidence = ["crates/engine/src/commands.rs::ids_and_shortcuts_are_unique", "crates/ui-egui/tests/commands.rs::every_registered_command_is_implemented"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.disabled-command-reasons"
|
||
title = "Disabled commands explain why"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/commands.rs::disabled_commands_explain_themselves"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.cli-inspect"
|
||
title = "CLI: inspect, render and extract text"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M2"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info", "page_render", "text_extract"]
|
||
evidence = ["crates/automation/tests/automation.rs::open_inspect_render_and_find"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.cli-edit"
|
||
title = "CLI: edit, combine, extract, split"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["page_rotate", "page_delete", "doc_combine", "page_extract", "doc_split"]
|
||
evidence = ["crates/automation/tests/automation.rs::combine_extract_and_split", "crates/automation/tests/automation.rs::edit_undo_redo_and_save_round_trip"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.cli-run"
|
||
title = "CLI run: call any automation tool or a JSON script"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/automation/tests/automation.rs::tool_table_is_well_formed", "crates/automation/tests/automation.rs::edit_undo_redo_and_save_round_trip", "apps/pdfcraft-cli/tests/stdout.rs::output_commands_exit_cleanly_when_the_reader_has_closed"]
|
||
notes = "CLI stdout exits cleanly if a pipe reader closes early; other output errors and command failures remain errors."
|
||
|
||
[[feature]]
|
||
id = "misc.cli-completeness"
|
||
title = "CLI covers every shipped command"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M13"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/automation/tests/automation.rs::command_list_reports_enablement_and_tools"]
|
||
notes = "tools grow as features land"
|
||
|
||
[[feature]]
|
||
id = "misc.automation-tools"
|
||
title = "Headless JSON-Schema tool table"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["command_list"]
|
||
evidence = ["crates/automation/tests/automation.rs::tool_table_is_well_formed", "crates/automation/tests/automation.rs::errors_are_specific_and_safe"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.command-list-tool"
|
||
title = "List commands with enablement and their tools"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["command_list"]
|
||
evidence = ["crates/automation/tests/automation.rs::command_list_reports_enablement_and_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.mcp-server"
|
||
title = "Opt-in MCP server over stdio"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["command_list", "command_run", "command_batch", "doc_inspect", "render_preview"]
|
||
evidence = ["crates/automation/tests/automation.rs::mcp_session_over_stdio", "crates/automation/tests/automation.rs::mcp_errors", "crates/automation/tests/automation.rs::conventions_core_tools_preserve_root_and_batch_errors", "crates/automation/tests/automation.rs::conventions_annotations_and_strict_keys_in_both_modes"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.automation-root-confinement"
|
||
title = "Confine automation file access to a root directory"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/automation/tests/automation.rs::errors_are_specific_and_safe", "crates/automation/tests/automation.rs::root_refusals_do_not_reveal_what_exists_outside", "apps/pdfcraft-cli/tests/run_root.rs::script_image_output_is_confined_to_the_root", "crates/automation/tests/automation.rs::file_names_from_documents_stay_in_the_output_folder"]
|
||
notes = "One refusal for every path outside the root, existing or not (#136). `run --script` images (`out`) are confined too (#137). Check-then-write, not openat-style: a process racing the tool on the same disk could swap a folder for a link between the check and the write."
|
||
|
||
[[feature]]
|
||
id = "misc.mcp-resources"
|
||
title = "MCP resources for page images and text"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info", "text_extract", "page_render"]
|
||
evidence = ["crates/automation/tests/automation.rs::mcp_resources_expose_open_documents", "crates/automation/src/mcp.rs::resource_uris_parse", "crates/automation/tests/automation.rs::conventions_resources"]
|
||
notes = "resources/list, resources/templates/list and resources/read: pdfcraft://doc/{doc}/info (JSON), /text, /page/{page}/text, /page/{page}/image?dpi= (PNG)"
|
||
|
||
[[feature]]
|
||
id = "misc.ui-control-channel"
|
||
title = "UI control channel (inspect, click, type, keys, commands)"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::inspect_and_click_by_label_and_id", "crates/ui-egui/tests/control.rs::commands_keys_and_typing", "crates/ui-egui/tests/control.rs::loopback_transport_requires_the_token"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ui-screenshots"
|
||
title = "Screenshots of the window or a region via the control channel"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::screenshots_of_window_and_region"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-provider"
|
||
title = "AI provider interface (local model or own endpoint), off by default"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-summary"
|
||
title = "Summarize a document"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-ask"
|
||
title = "Ask questions with citations to pages"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-conversational-edit"
|
||
title = "Natural-language editing through registry commands"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-presentation"
|
||
title = "Generate a presentation from documents"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-podcast"
|
||
title = "Generate an audio overview"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.ai-charts"
|
||
title = "Charts from document data"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.contract-analysis"
|
||
title = "Contract key terms and multi-contract compare"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M13"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.adobe-ai-assistant"
|
||
title = "Adobe AI Assistant"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; replaced by the optional local/BYO AiProvider"
|
||
|
||
[[feature]]
|
||
id = "misc.pdf-spaces"
|
||
title = "PDF Spaces"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; no local equivalent planned"
|
||
|
||
[[feature]]
|
||
id = "misc.adobe-express"
|
||
title = "Adobe Express integration and generated images"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud"
|
||
|
||
[[feature]]
|
||
id = "misc.document-cloud-storage"
|
||
title = "Adobe Document Cloud storage"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; local files and native dialogs (pluggable storage later)"
|
||
|
||
[[feature]]
|
||
id = "misc.share-links"
|
||
title = "Share links and invitations"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; pluggable share provider"
|
||
|
||
[[feature]]
|
||
id = "misc.connected-storage"
|
||
title = "Connected third-party storage accounts in-app"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe cloud; use OS file dialogs and synced folders, which can be pinned to Home (view.home-view)"
|
||
|
||
[[feature]]
|
||
id = "misc.cross-device-sync"
|
||
title = "Sync signatures, stamps and preferences across devices"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Adobe account service"
|
||
|
||
[[feature]]
|
||
id = "misc.adobe-account"
|
||
title = "Adobe account sign-in"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "not applicable; no account needed"
|
||
|
||
[[feature]]
|
||
id = "misc.rich-media-preserve"
|
||
title = "Preserve 3D, video and sound content"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.rich-media-poster"
|
||
title = "Show posters for rich media"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.rich-media-playback"
|
||
title = "Play video and sound via the platform"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.3d-view"
|
||
title = "View 3D (U3D/PRC) content"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.legacy-multimedia"
|
||
title = "Legacy multimedia (screen annotations, renditions)"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.flash-swf"
|
||
title = "Flash/SWF content"
|
||
area = "N"
|
||
tier = "N/A"
|
||
milestone = "none"
|
||
status = "na"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Flash is end-of-life; content is preserved but not played"
|
||
|
||
[[feature]]
|
||
id = "misc.preferences-dialog"
|
||
title = "Preferences dialog generated from a schema"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "partial"
|
||
commands = ["app.preferences"]
|
||
tools = ["js_enabled"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/ui.rs::preferences_selects_default_workspace_for_next_open", "crates/ui-egui/tests/control.rs::control_default_workspace_and_session_override"]
|
||
notes = "Preferences dialog with Interface language, Documents and view (persistent default workspace with session override), Identity, Fill & Sign (flatten on save, off by default) and JavaScript; other categories still live in their tools, and it isn't generated from a schema yet."
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-general"
|
||
title = "Preferences: general"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-page-display"
|
||
title = "Preferences: page display"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::default_page_display_is_used_for_new_documents", "crates/ui-egui/tests/view.rs::default_zoom_is_used_for_new_documents", "crates/ui-egui/tests/view.rs::preferences_set_the_default_zoom"]
|
||
notes = "Preferences ▸ Documents and view: Default page display and Default zoom (Fit to width, Zoom to page level or a percentage) for every new tab; a PDF that asks for a layout or zoom gets it. Missing: Acrobat's resolution, rendering and page-content options"
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-documents"
|
||
title = "Preferences: documents (autosave interval, recent count)"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-commenting"
|
||
title = "Preferences: commenting"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-forms"
|
||
title = "Preferences: forms"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-identity"
|
||
title = "Preferences: identity"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-full-screen"
|
||
title = "Preferences: full screen"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-search"
|
||
title = "Preferences: search"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-signatures"
|
||
title = "Preferences: signatures"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-security"
|
||
title = "Preferences: security and trust"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-spelling"
|
||
title = "Preferences: spelling"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-language"
|
||
title = "Preferences: language"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "partial"
|
||
commands = ["app.preferences"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/src/commands.rs::localized_shortcut_labels_follow_language_and_platform", "crates/ui-egui/src/i18n/mod.rs::keyboard_key_names_have_a_separate_context_and_english_fallback", "crates/ui-egui/tests/commands.rs::localized_shortcuts_update_immediately_and_keep_command_ids_and_key_bindings", "crates/ui-egui/src/i18n/mod.rs::windows_display_language_resolves_chinese_scripts", "crates/ui-egui/src/i18n/mod.rs::neutral_locale_does_not_override_display_language", "crates/ui-egui/src/i18n/mod.rs::windows_display_language_query_returns_a_locale_tag", "crates/ui-egui/src/i18n/mod.rs::language_persists_and_invalid_input_keeps_current_language", "crates/ui-egui/src/i18n/mod.rs::bundled_catalogs_are_consistent", "crates/ui-egui/src/i18n/mod.rs::arabic_is_registered_and_persists", "crates/ui-egui/src/i18n/mod.rs::arabic_lookups_are_in_display_order", "crates/ui-egui/src/i18n/mod.rs::arabic_plurals_follow_the_six_cldr_forms", "crates/ui-egui/src/i18n/mod.rs::arabic_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::arabic_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::arabic_labels_have_glyphs", "crates/ui-egui/src/bidi.rs::rtl_labels_keep_placeholders_whole", "crates/ui-egui/src/i18n/mod.rs::japanese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_history_and_diagnostics_preserve_user_values", "crates/ui-egui/tests/control.rs::simplified_chinese_signature_prompts_and_errors_keep_document_state", "crates/ui-egui/tests/control.rs::language_switch_preserves_document_and_command_ids", "crates/ui-egui/tests/control.rs::japanese_controls_and_search_keep_command_ids", "crates/ui-egui/tests/control.rs::japanese_dialogs_errors_and_custom_action_names", "crates/ui-egui/src/i18n/mod.rs::french_is_registered", "crates/ui-egui/src/i18n/mod.rs::french_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::french_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::french_history_and_diagnostics_preserve_user_values", "crates/ui-egui/src/i18n/mod.rs::german_certificate_subject_has_a_distinct_context", "crates/ui-egui/src/widgets.rs::search_placeholder_never_overlaps_the_shortcut", "crates/ui-egui/src/home.rs::translated_card_description_leaves_room_for_the_action", "crates/ui-egui/tests/control.rs::german_preferences_and_search_keep_command_ids", "crates/ui-egui/src/i18n/mod.rs::telugu_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::telugu_labels_have_glyphs", "crates/ui-egui/src/i18n/mod.rs::hungarian_is_registered", "crates/ui-egui/src/i18n/mod.rs::hungarian_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::hungarian_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::hungarian_history_and_diagnostics_preserve_user_values", "crates/ui-egui/src/i18n/mod.rs::ukrainian_is_registered_and_persists", "crates/ui-egui/src/i18n/mod.rs::ukrainian_integer_plurals_cover_teens_and_large_counts", "crates/ui-egui/src/i18n/mod.rs::ukrainian_calendar_uses_month_case_for_dates", "crates/ui-egui/src/i18n/mod.rs::ukrainian_catalog_does_not_leave_english_ui_text", "crates/ui-egui/src/i18n/mod.rs::ukrainian_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::ukrainian_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::ukrainian_history_and_diagnostics_preserve_user_values", "crates/ui-egui/src/i18n/mod.rs::spanish_covers_what_the_engine_says", "crates/ui-egui/src/i18n/mod.rs::spanish_keeps_the_holes_in_what_the_engine_says", "crates/print/src/tests.rs::wordings_are_said"]
|
||
notes = "Shortcut modifiers and key names use a dedicated catalog context across toolbar, menus, palette, Help and tooltips, with English fallback and macOS symbols preserved. Preferences switches Auto (system language)/English/Japanese/Simplified Chinese/Traditional Chinese/Czech/Brazilian Portuguese/German/Spanish/French/Telugu/Hungarian/Ukrainian/Italian/Arabic and persists; ui.set language auto|en|ja|zh-hans|zh-hant|cs|pt-br|de|es|fr|te|hu|uk|it|ar and ui.state expose it. TSV catalogs per language (same system as PhotoCraft); every dialog, panel and notice goes through tl!. Japanese, Traditional Chinese, Simplified Chinese, German, Spanish, French and Hungarian cover commands, dialogs, panels, shortcuts and history labels; Czech and Brazilian Portuguese the menus. French locales (fr, fr_FR, fr_CA, fr_BE) select the French catalog, German locales (de, de_DE, de_AT, de_CH) the German one, Hungarian locales (hu, hu_HU) the Hungarian one. German search hints reserve shortcut space, Home cards bound translated text, certificate Subject has its own context, and inserted tool names keep their capitalization. The sentences the layers below word for themselves (accessibility findings and the saved report, PDF/A problems, signature details, page-range mistakes, edit labels) are handed over apart from the document's own values and go through the catalog too, through said(&i18n::words); Spanish translates them and the other catalogs fall back to English. OS error details, and faults rather than mistakes, stay as they are. Auto detects the Windows display language after explicitly named supported locale overrides; neutral C/POSIX launcher locales are skipped on Windows, with Chinese script/region mapping and English fallback. Arabic (ar and every ar-* locale) covers commands, dialogs and panels with the six CLDR plural forms; its labels are put into display order when the catalog loads, because egui joins Arabic letters but lays words out left to right. Missing: a right-to-left layout (Arabic labels sit in the left-to-right layout, a wrapped Arabic paragraph breaks at the wrong end, and text typed into a field is not reordered), Czech and Brazilian Portuguese dialogs, Chinese and Telugu font delivery in releases (the pinned craft-fonts revision has an Arabic face; its Noto Sans CJK SC is never embedded, AGENTS.md §1.1), the engine's sentences in the other catalogs, a full localization audit."
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-reading"
|
||
title = "Preferences: reading"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-accessibility"
|
||
title = "Preferences: accessibility"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-color-management"
|
||
title = "Preferences: colour management"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-javascript"
|
||
title = "Preferences: JavaScript"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "shipped"
|
||
commands = ["app.preferences"]
|
||
tools = ["js_enabled"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences"]
|
||
notes = "Enable Acrobat JavaScript, remembered between runs."
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-measuring"
|
||
title = "Preferences: measuring"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-units-guides"
|
||
title = "Preferences: units and guides"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-content-editing"
|
||
title = "Preferences: content editing"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-convert"
|
||
title = "Preferences: convert to and from PDF"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.prefs-catalog"
|
||
title = "Preferences: catalog / index"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.admin-preferences"
|
||
title = "Admin-deployable preference defaults"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.localization"
|
||
title = "Localization (Fluent catalogs)"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.rtl-ui"
|
||
title = "Right-to-left user interface"
|
||
area = "N"
|
||
tier = "P2"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Only a first step exists: Arabic and Hebrew file names and paths in the tab strip, the recent-files list and Properties have glyphs (a craft-fonts Arab face, or an installed font on desktop) and are shown in display order (crates/ui-egui/src/bidi.rs). Missing: multi-line text (bookmarks, comments, search results), text input, a mirrored layout and a right-to-left interface language."
|
||
|
||
[[feature]]
|
||
id = "misc.installers"
|
||
title = "Signed installers for macOS, Windows and Linux"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Windows MSI creates a Start menu shortcut, an optional (default on) desktop shortcut and an optional (default on, INSTALLPDFCONTEXTMENU) \"Edit with PdfCraft\" File Explorer menu entry for PDF files (#682), and confirms full-UI completion with Finish (#143); packaging/windows/test-msi.ps1 checks the compiled MSI and windows-arm64.yml checks installation/removal. PDF files use a dedicated embedded document icon when PdfCraft is chosen as the default reader (confirmed in Windows Explorer); first-page thumbnails remain unsupported. Signed installers across platforms remain planned."
|
||
|
||
[[feature]]
|
||
id = "misc.web-deploy"
|
||
title = "Hosted web app deployment"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.performance-budgets"
|
||
title = "Meet performance budgets on Tier-1 platforms"
|
||
area = "N"
|
||
tier = "P0"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.send-email"
|
||
title = "Send the file by email (system mail client)"
|
||
area = "N"
|
||
tier = "P3"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "misc.user-docs"
|
||
title = "User documentation and keyboard reference"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = ""
|