Merge upstream/main into feat/recent-reveal-5452

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76andClaude Sonnet 5.5 committed 2026-11-10 22:55:22 +01:00
commit 9f96a6f222
290 files changed
+24594 -1708

No files matched your search

+72 -49
View File
@@ -34,8 +34,8 @@ milestone = "M1"
status = "shipped"
commands = ["file.open"]
tools = ["doc_open"]
evidence = ["crates/automation/tests/it/automation.rs::open_inspect_render_and_find", "crates/ui-egui/tests/it/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/it/ui.rs::files_and_quit_from_the_operating_system", "crates/ui-egui/src/platform/open_worker/tests.rs::a_chosen_tool_runs_on_its_file_only_after_native_preparation_commits", "crates/ui-egui/tests/it/editing.rs::opening_the_same_path_again_focuses_its_tab", "crates/ui-egui/tests/it/editing.rs::opening_a_file_whose_copy_vanished_from_disk_focuses_its_tab"]
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); an already-open file focuses its tab instead of opening a second copy (#1620). Not yet: a first-level Windows 11 menu entry (needs package identity) or Combine from the file manager (#585)"
evidence = ["crates/automation/tests/it/automation.rs::open_inspect_render_and_find", "crates/ui-egui/tests/it/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/it/ui.rs::files_and_quit_from_the_operating_system", "crates/ui-egui/src/platform/open_worker/tests.rs::a_chosen_tool_runs_on_its_file_only_after_native_preparation_commits", "crates/ui-egui/tests/it/editing.rs::opening_the_same_path_again_focuses_its_tab", "crates/ui-egui/tests/it/editing.rs::opening_a_file_whose_copy_vanished_from_disk_focuses_its_tab", "crates/ui-egui/tests/it/open_action.rs::a_file_named_with_open_parameters_opens_at_their_destination", "crates/ui-egui/tests/it/open_action.rs::open_parameters_handed_to_the_running_app_show_their_destination", "crates/engine/src/open_parameters.rs::several_parameters_apply_in_turn_and_unknown_ones_are_ignored", "crates/automation/tests/it/automation.rs::doc_open_reads_open_parameters_after_the_file_name"]
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); an already-open file focuses its tab instead of opening a second copy (#1620). A path that ends in open parameters, as help systems pass them (`manual.pdf#nameddest=Intro`, `#page=12`, or `#Intro`; #3425), opens the file at that destination, or goes there in its tab when it is open, and `doc_open` reports that destination; other parameters (zoom, view, pagemode, search…) are ignored. 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"
@@ -819,7 +819,7 @@ status = "shipped"
commands = []
tools = []
evidence = ["crates/render/src/raster/tests.rs::widget_appearance_states", "crates/render/src/raster/tests.rs::highlights_without_an_appearance_are_drawn", "crates/engine/src/tests.rs::comments_without_appearances_are_drawn_but_not_saved", "crates/engine/src/tests.rs::cli_display_adds_missing_appearances_without_changing_the_source", "crates/engine/src/tests.rs::fields_left_to_the_viewer_are_displayed_from_their_values", "crates/forms/src/tests.rs::need_appearances_draws_every_text_and_choice_field_in_the_display_copy", "crates/forms/src/tests.rs::the_field_appearance_cache_sees_every_input", "crates/engine/src/tests.rs::buttons_and_combs_left_to_the_viewer_are_displayed", "crates/forms/src/tests.rs::check_boxes_without_an_appearance_show_their_caption_symbol", "crates/forms/src/tests.rs::push_buttons_without_an_appearance_show_their_caption_and_frame", "crates/forms/src/tests.rs::need_appearances_draws_check_boxes_and_radio_buttons_from_their_captions", "crates/engine/src/tests.rs::need_appearances_buttons_are_displayed_from_their_captions", "crates/render/src/raster/tests.rs::invisible_unknown_annotations_and_popups_paint_nothing", "crates/print/src/tests.rs::invisible_annotations_of_unknown_types_do_not_print", "crates/engine/src/tests.rs::stamps_without_appearances_draw_the_stamp_they_name", "crates/automation/tests/it/automation.rs::page_render_draws_stamps_without_an_appearance", "crates/annot/src/tests.rs::a_four_entry_colour_with_a_null_is_cmyk_with_zero", "crates/forms/src/tests.rs::a_four_entry_mk_colour_with_a_null_is_cmyk_with_zero", "crates/annot/src/tests.rs::text_markup_whose_quads_reach_past_its_rect_is_drawn_over_them", "crates/annot/src/tests.rs::text_markup_reach_stops_at_the_page_and_the_quad_cap", "crates/annot/src/tests.rs::text_markup_reach_reads_quads_like_the_drawing", "crates/annot/src/tests.rs::ink_whose_strokes_reach_past_its_rect_is_drawn_through_them", "crates/annot/src/tests.rs::polyline_whose_vertices_reach_past_its_rect_is_drawn_through_them", "crates/annot/src/tests.rs::polygon_whose_vertices_reach_past_its_rect_is_drawn_through_them", "crates/annot/src/tests.rs::line_whose_points_reach_past_its_rect_is_drawn_through_them", "crates/annot/src/tests.rs::path_reach_stops_at_the_point_cap_and_is_saved_on_restyle", "crates/annot/src/tests.rs::indirect_border_style_and_colour_are_drawn", "crates/annot/src/tests.rs::callout_without_rd_is_drawn", "crates/forms/src/tests.rs::normal_appearances_that_give_no_stream_are_drawn", "crates/engine/src/tests.rs::text_boxes_and_redaction_marks_without_appearances_show_their_outlines", "crates/annot/src/tests.rs::free_text_da_fonts_draw_in_their_standard_face", "crates/annot/src/tests.rs::free_text_ds_style_is_drawn", "crates/annot/src/tests.rs::redaction_marks_with_quadpoints_take_their_outline_from_oc_then_c", "crates/annot/src/tests.rs::unapplied_redaction_marks_follow_their_oc_outline_colour"]
notes = "comments without /AP (FreeText, Ink, notes, stamps, shapes, markup; text markup, lines, polylines, polygons and ink through their /QuadPoints, /L, /Vertices or /InkList where these reach past /Rect, as far as the crop box) are drawn from their dictionaries in the page view, page_render and page images, including the CLI render command, never saved, with style values given by reference (a /BS, colour, /RD or /CL as an indirect object, ISO 32000-2 §7.3.10) followed and a callout without /RD (optional, Table 177) boxed at its /Rect; a four-entry colour with a non-number in it (/C, /IC, /MK /BC and /BG written [1 0 0 null]) is read as CMYK with that entry 0; a stamp from another program draws the stamp its /Name names (the fourteen standard names of ISO 32000-2 Table 184, Draft when there is none) or, for any other name, a grey frame with both diagonals; text boxes without /BS or /Border get the /Border default 1-pt border (ISO 32000-2 Table 166), their /DA font when /DR or its resource name makes it one of the twelve standard text faces (others stay Helvetica), and /DS font, size and colour over /DA (no /RC rich text yet); unapplied redaction marks without /AP are outlined in an array /OC colour (three finite numbers) when they have one, with or without /QuadPoints; without one, marks with /QuadPoints keep /C (else red) and marks without /QuadPoints (Table 195: /Rect is the region) draw nothing; an /OC dictionary stays optional content (Table 166). Basis: ISO 32000-2 §12.5.6.23 says only that before redaction is applied the user can see, move and redefine these annotations, and Table 195 has no outline-colour entry, so the colour comes from black-box observation of two synthetic fixtures. noap3.pdf (generator attached to #4364), rows J/K/L, unapplied, without /QuadPoints: Adobe Acrobat Pro J nothing, K red outline, L nothing; Chrome (PDFium) and Edge nothing on all three; PDF-XChange Editor and Right PDF Editor a red outline on all three. oc.pdf, six marks without /AP (1 /OC blue; 2 /C green; 3 /OC blue + /C green; 4 /OC blue + /IC black; 5 no colour; 6 /OC blue + /QuadPoints; rebuilt inline in unapplied_redaction_marks_follow_their_oc_outline_colour): Acrobat blue on 1, 3, 4 and 6 and nothing on 2 and 5; Chrome and Edge nothing on all six; PDF-XChange Editor green on 2 and 3, nothing on the others; Right PDF Editor red on all six. The three readers tested that support redaction marks disagree, so PdfCraft follows the /OC colour, the one Acrobat uses; text and choice fields are drawn from their values when the form sets /NeedAppearances (every one) or a widget's /N gives no stream to draw (none, null, a dictionary without its stream data, or a state subdictionary without a stream for /AS; #3470), also display-only and cached per widget (a field edit on a 1000-field /NeedAppearances form takes ~13 ms against ~7 ms on a form that needs none) (a read-only document still gets the renderer's Highlight fallback only); check boxes and radio buttons with no appearance for their /AS are drawn display-only from /MK (background, border, and when on the /MK /CA ZapfDingbats symbol in the /DA colour, a check or a dot by default), and when the form sets /NeedAppearances so is every one that has an /MK, over the appearance the file carries (ISO 32000-2 Table 224; one without /MK keeps its own), and push buttons with none from /MK and /BS with their caption; Link borders without /AP and printing are not covered yet; an annotation of a non-standard type with the Invisible flag (ISO 32000-2 Table 167) is neither drawn nor printed, and a popup's own /AP is never painted on the page (its parent's text shows in the popup window, §12.5.6.14)"
notes = "comments without /AP (FreeText, Ink, notes, stamps, shapes, markup; text markup, lines, polylines, polygons and ink through their /QuadPoints, /L, /Vertices or /InkList where these reach past /Rect, as far as the crop box) are drawn from their dictionaries in the page view, page_render and page images, including the CLI render command, never saved, with style values given by reference (a /BS, colour, /RD or /CL as an indirect object, ISO 32000-2 §7.3.10) followed and a callout without /RD (optional, Table 177) boxed at its /Rect; a four-entry colour with a non-number in it (/C, /IC, /MK /BC and /BG written [1 0 0 null]) is read as CMYK with that entry 0; a stamp from another program draws the stamp its /Name names (the fourteen standard names of ISO 32000-2 Table 184, Draft when there is none) or, for any other name, a grey frame with both diagonals; text boxes without /BS or /Border get the /Border default 1-pt border (ISO 32000-2 Table 166), their /DA font when /DR or its resource name makes it one of the twelve standard text faces (others stay Helvetica), and /DS font, size and colour over /DA (no /RC rich text yet); unapplied redaction marks without /AP are outlined in an array /OC colour (three finite numbers) when they have one, with or without /QuadPoints; without one, marks with /QuadPoints keep /C (else red) and marks without /QuadPoints (Table 195: /Rect is the region) draw nothing; an /OC dictionary stays optional content (Table 166). Basis: ISO 32000-2 §12.5.6.23 says only that before redaction is applied the user can see, move and redefine these annotations, and Table 195 has no outline-colour entry, so the colour comes from black-box observation of two synthetic fixtures. noap3.pdf (generator attached to #4364), rows J/K/L, unapplied, without /QuadPoints: Adobe Acrobat Pro J nothing, K red outline, L nothing; Chrome (PDFium) and Edge nothing on all three; PDF-XChange Editor and Right PDF Editor a red outline on all three. oc.pdf, six marks without /AP (1 /OC blue; 2 /C green; 3 /OC blue + /C green; 4 /OC blue + /IC black; 5 no colour; 6 /OC blue + /QuadPoints; rebuilt inline in unapplied_redaction_marks_follow_their_oc_outline_colour): Acrobat blue on 1, 3, 4 and 6 and nothing on 2 and 5; Chrome and Edge nothing on all six; PDF-XChange Editor green on 2 and 3, nothing on the others; Right PDF Editor red on all six. The three readers tested that support redaction marks disagree, so PdfCraft follows the /OC colour, the one Acrobat uses; text and choice fields are drawn from their values when the form sets /NeedAppearances (every one) or a widget's /N gives no stream to draw (none, null, a dictionary without its stream data, or a state subdictionary without a stream for /AS; #3470), also display-only and cached per widget (a field edit on a 1000-field /NeedAppearances form takes ~13 ms against ~7 ms on a form that needs none) (a read-only document still gets the renderer's Highlight fallback only); check boxes and radio buttons with no appearance for their /AS are drawn display-only from /MK (background, border, and when on the /MK /CA ZapfDingbats symbol in the /DA colour, a check or a dot by default), and when the form sets /NeedAppearances so is every one that has an /MK, over the appearance the file carries (ISO 32000-2 Table 224; one without /MK keeps its own), and push buttons with none from /MK and /BS with their caption; Link borders without /AP are not synthesized for display or printing; an annotation of a non-standard type with the Invisible flag (ISO 32000-2 Table 167) is neither drawn nor printed, and a popup's own /AP is never painted on the page (its parent's text shows in the popup window, §12.5.6.14)"
[[feature]]
id = "view.render-error-isolation"
@@ -1202,8 +1202,8 @@ milestone = "M3"
status = "shipped"
commands = []
tools = ["link_open"]
evidence = ["crates/ui-egui/tests/it/view.rs::clicking_a_link_goes_to_its_destination", "crates/ui-egui/tests/it/view.rs::link_destinations_scroll_to_their_position", "crates/ui-egui/tests/it/view.rs::a_link_without_a_position_keeps_the_place_on_the_next_page", "crates/ui-egui/src/canvas/tests.rs::a_null_xyz_coordinate_keeps_the_readers_place_on_the_new_page", "crates/render/src/inspect/tests.rs::bookmarks_and_links_keep_their_destination_view", "crates/render/src/inspect/tests.rs::links_read_bounded_local_remote_go_to_actions", "crates/automation/tests/it/automation.rs::local_gotor_link_open_applies_destinations_and_honors_root", "crates/ui-egui/tests/it/document_links.rs::a_local_gotor_link_opens_in_a_tab_and_applies_its_destination", "crates/ui-egui/tests/it/document_links.rs::an_absolute_gotor_waits_for_consent_and_keeps_its_destination_across_tabs", "crates/ui-egui/tests/it/document_links.rs::cancelling_an_absolute_gotor_by_button_escape_or_dismissal_changes_nothing", "crates/automation/tests/it/automation.rs::absolute_gotor_consent_does_not_bypass_validation_or_passwords", "crates/render/src/inspect/tests.rs::launch_actions_that_name_a_pdf_open_it_like_a_remote_link", "crates/automation/tests/it/automation.rs::a_launch_link_to_a_pdf_opens_it_like_a_remote_link", "crates/render/src/inspect/tests.rs::relative_uri_actions_resolve_against_the_catalog_base", "crates/render/src/inspect/tests.rs::links_are_active_in_their_quad_points", "crates/automation/tests/it/automation.rs::links_resolve_relative_addresses_and_report_their_quad_points"]
notes = "A relative URI action resolves against the catalog's /URI /Base (ISO 32000-2 Table 211) when that base is an http(s) address, then goes through the usual check and confirmation; without a base, a relative address is still refused (resolving against the document's own location is not done). A link with valid /QuadPoints is active only inside them, otherwise over /Rect (Table 176); link_list reports resolved_url and active_areas. IsMap is not supported. 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. Absolute local GoToR targets require desktop Allow/Cancel confirmation or an explicit allow_absolute opt-in in link_open; relative links open normally. link_open honors --root. Named remote destinations open at default view with a warning; /NewWindow is a tab hint. A Launch action whose /F names a .pdf file (and has no /Win launch parameters) opens that PDF the same way, backslash separators read as / (#4168); URL/network file systems, GoToE, other Launch actions 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. A null /XYZ left or top keeps the reader's place, as ISO 32000-2 §12.3.2.2 says and Acrobat Reader was observed to do (synthetic fixture): the user-space point at the window's top-left stays there on the target page at the same zoom, along each axis the page overflows; CAD exports (WSCAD) write every cross-reference this way. Null /FitH top and /FitV left still go to the page edge."
evidence = ["crates/ui-egui/tests/it/view.rs::clicking_a_link_goes_to_its_destination", "crates/ui-egui/tests/it/view.rs::link_destinations_scroll_to_their_position", "crates/ui-egui/tests/it/view.rs::a_link_without_a_position_keeps_the_place_on_the_next_page", "crates/ui-egui/src/canvas/tests.rs::a_null_xyz_coordinate_keeps_the_readers_place_on_the_new_page", "crates/render/src/inspect/tests.rs::bookmarks_and_links_keep_their_destination_view", "crates/render/src/inspect/tests.rs::links_read_bounded_local_remote_go_to_actions", "crates/automation/tests/it/automation.rs::local_gotor_link_open_applies_destinations_and_honors_root", "crates/ui-egui/tests/it/document_links.rs::a_local_gotor_link_opens_in_a_tab_and_applies_its_destination", "crates/ui-egui/tests/it/document_links.rs::a_gotor_link_to_a_named_destination_opens_at_its_page", "crates/render/src/inspect/navigation.rs::a_named_destination_is_looked_up_by_its_name", "crates/ui-egui/tests/it/document_links.rs::an_absolute_gotor_waits_for_consent_and_keeps_its_destination_across_tabs", "crates/ui-egui/tests/it/document_links.rs::cancelling_an_absolute_gotor_by_button_escape_or_dismissal_changes_nothing", "crates/automation/tests/it/automation.rs::absolute_gotor_consent_does_not_bypass_validation_or_passwords", "crates/render/src/inspect/tests.rs::launch_actions_that_name_a_pdf_open_it_like_a_remote_link", "crates/automation/tests/it/automation.rs::a_launch_link_to_a_pdf_opens_it_like_a_remote_link", "crates/render/src/inspect/tests.rs::relative_uri_actions_resolve_against_the_catalog_base", "crates/render/src/inspect/tests.rs::links_are_active_in_their_quad_points", "crates/automation/tests/it/automation.rs::links_resolve_relative_addresses_and_report_their_quad_points"]
notes = "A relative URI action resolves against the catalog's /URI /Base (ISO 32000-2 Table 211) when that base is an http(s) address, then goes through the usual check and confirmation; without a base, a relative address is still refused (resolving against the document's own location is not done). A link with valid /QuadPoints is active only inside them, otherwise over /Rect (Table 176); link_list reports resolved_url and active_areas. IsMap is not supported. 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. Absolute local GoToR targets require desktop Allow/Cancel confirmation or an explicit allow_absolute opt-in in link_open; relative links open normally. link_open honors --root. A named remote destination opens at its page and view, in the app and in link_open, looked up in the target's /Names /Dests tree or catalog /Dests (§12.3.2.4); a name the target doesn't define opens it at the default view with a warning; /NewWindow is a tab hint. A Launch action whose /F names a .pdf file (and has no /Win launch parameters) opens that PDF the same way, backslash separators read as / (#4168); URL/network file systems, GoToE, other Launch actions 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. A null /XYZ left or top keeps the reader's place, as ISO 32000-2 §12.3.2.2 says and Acrobat Reader was observed to do (synthetic fixture): the user-space point at the window's top-left stays there on the target page at the same zoom, along each axis the page overflows; CAD exports (WSCAD) write every cross-reference this way. Null /FitH top and /FitV left still go to the page edge."
[[feature]]
id = "view.annotation-hover"
@@ -1598,8 +1598,8 @@ milestone = "M3"
status = "shipped"
commands = ["edit.snapshot"]
tools = ["page_render"]
evidence = ["crates/ui-egui/tests/it/viewer.rs::marquee_zoom_and_snapshot", "crates/ui-egui/tests/it/viewer.rs::a_snapshot_keeps_its_resolution_when_zoomed_out", "crates/ui-egui/src/canvas/zoom_snap.rs::a_snapshot_is_drawn_at_the_finer_of_the_zoom_and_the_minimum", "crates/ui-egui/src/canvas/zoom_snap.rs::a_snapshot_stays_within_what_the_renderer_draws_at_once", "crates/ui-egui/tests/it/viewer.rs::a_snapshot_of_a_large_page_reaches_its_far_corner"]
notes = "copies the area to the clipboard as an image, at 300 ppi or the current zoom if that is finer (#5716), or as fine as the renderer draws in one tile (4096 pixels a side), drawn while the app waits. Missing: a choice of resolution, and a selection that stays active to copy later"
evidence = ["crates/ui-egui/tests/it/viewer.rs::marquee_zoom_and_snapshot", "crates/ui-egui/tests/it/viewer.rs::a_snapshot_keeps_its_resolution_when_zoomed_out", "crates/ui-egui/src/canvas/zoom_snap.rs::a_snapshot_is_drawn_at_the_finer_of_the_zoom_and_the_minimum", "crates/ui-egui/src/canvas/zoom_snap.rs::a_snapshot_stays_within_what_the_renderer_draws_at_once", "crates/ui-egui/tests/it/viewer.rs::a_snapshot_of_a_large_page_reaches_its_far_corner", "crates/ui-egui/tests/it/viewer.rs::a_snapshot_area_prints_alone_or_saves_as_an_image"]
notes = "copies the area to the clipboard as an image, at 300 ppi or the current zoom if that is finer (#5716), or as fine as the renderer draws in one tile (4096 pixels a side), drawn while the app waits; the area then stays outlined with Print area… and Save as image… (PNG) under it until another tool is chosen (print.selected-area). Missing: a choice of resolution, and a selection that stays active to copy later"
[[feature]]
id = "view.copy-image"
@@ -1907,8 +1907,8 @@ milestone = "M7"
status = "partial"
commands = ["edit.edit_text"]
tools = ["text_prepare", "text_lines", "text_paragraphs", "text_edit", "text_edit_layout", "text_font_files", "text_font_import", "text_fonts"]
evidence = ["crates/edit/src/tests.rs::editing_legacy_thai_keeps_the_embedded_face_weight_size_spacing_and_paint_after_reopen", "crates/edit/src/tests.rs::editing_legacy_thai_shapes_a_run_across_subsets_of_the_same_face", "crates/edit/src/tests.rs::grouped_text_nested_past_the_level_limit_is_reported", "crates/edit/src/tests.rs::grouped_image_artwork_is_not_prepared", "crates/edit/src/tests.rs::grouped_text_in_an_rgb_group_needs_a_page_that_blends_in_rgb", "crates/fonts/src/pdf/thai.rs::recovered_thai_map_checks_every_kept_glyph_against_the_font_program", "crates/automation/tests/it/automation.rs::text_prepare_refuses_pages_without_grouped_text", "crates/fonts/src/pdf/tests.rs::composite_subset_without_cmap_uses_pdf_glyph_ids_in_editor_and_shaper_without_replacing_the_program", "crates/automation/tests/it/automation.rs::grouped_existing_thai_body_edits_saves_reopens_and_undo_restores_original_glyphs", "crates/edit/src/tests.rs::grouped_text_preparation_keeps_unsafe_compositing_paths_and_malformed_forms_intact", "crates/automation/tests/it/automation.rs::explicit_bundled_thai_font_choice_layout_edit_undo_and_reopen", "crates/edit/src/tests.rs::existing_thai_subset_uses_shaped_matching_face_without_embedding_variable_font", "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/it/editing.rs::editing_existing_text_in_place", "crates/automation/tests/it/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/it/automation.rs::a_line_split_across_content_streams_is_listed_and_edited", "crates/ui-egui/tests/it/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/it/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/it/document_fonts.rs::opentype_cff_document_font_preserves_program_codes_and_rendering_after_reopen", "crates/ui-egui/tests/it/editing.rs::cropped_images_do_not_intercept_text_and_stay_inside_each_page", "crates/automation/tests/it/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/it/editing.rs::one_editor_contains_mixed_styles_and_visual_rows", "crates/ui-egui/tests/it/editing.rs::inline_editor_preserves_source_font_and_previews_live_bounds", "crates/fonts/src/pdf/tests.rs::mac_subset_cmap_uses_pdf_unicode_without_changing_source_program", "crates/automation/tests/it/automation.rs::mixed_visual_region_edit_preview_save_reopen_and_undo_through_tools", "crates/ui-egui/tests/it/ui.rs::the_edit_tab_turns_on_edit_text_and_images", "crates/ui-egui/tests/it/editing.rs::the_caret_on_a_small_row_is_as_tall_as_that_row", "crates/edit/src/tests.rs::uzbek_and_kazakh_cyrillic_use_the_bundled_latin_face", "crates/automation/tests/it/automation.rs::editing_uzbek_and_kazakh_cyrillic_through_tools", "crates/edit/src/tests.rs::styled_paragraph_batches_cyrillic_without_changing_source_fonts", "crates/edit/src/tests.rs::styled_paragraph_over_240_fallback_glyphs_splits_and_refuses_atomically", "crates/edit/src/tests.rs::styled_fallback_batches_keep_face_choices_and_range_styles", "crates/edit/src/tests.rs::styled_fallback_shards_keep_text_advances_and_alignment", "crates/edit/src/tests.rs::styled_fallback_budget_is_enforced_before_objects_are_written", "crates/edit/src/tests.rs::styled_fallback_same_base_font_and_character_keep_both_weights", "crates/automation/tests/it/automation.rs::styled_cyrillic_fallback_is_batched_through_tools_and_reopen", "crates/ui-egui/tests/it/editing.rs::escape_in_an_open_paragraph_selects_its_box_for_delete", "crates/edit/src/tests.rs::ungrouping_form_artwork_makes_its_text_editable", "crates/automation/tests/it/automation.rs::ungrouping_a_table_drawn_in_a_form_makes_its_text_editable", "crates/ui-egui/tests/it/editing.rs::ungroup_in_the_artwork_menu_makes_a_forms_text_editable", "crates/edit/src/tests.rs::ungroup_carries_the_placement_into_the_patterns_it_selects", "crates/automation/tests/it/automation.rs::ungrouping_keeps_patterns_where_the_forms_placement_put_them", "crates/edit/src/tests.rs::ungroup_refuses_a_form_with_its_own_default_colour_space", "crates/edit/src/tests.rs::ungroup_renames_the_name_operand_the_renderer_reads", "crates/ui-egui/tests/it/editing.rs::one_click_on_grouped_text_opens_it_for_editing", "crates/edit/src/tests.rs::grouped_text_at_says_which_artwork_preparation_would_open", "crates/edit/src/testsLine truncated
notes = "In Edit text, one click on text in a supported grouped Form prepares its page (one undoable step) and opens the paragraph clicked, so its existing source glyphs are edited directly; nothing is prepared while pages are only shown, a drag still moves the selected group, and a click on grouped artwork that preparation wouldn't open (a logo, a refused group) selects it as before. text_prepare does the same headlessly, with the same code as Ungroup. Unsafe groups remain intact and are reported by text_prepare and in the repair log when the page is prepared, as is text grouped more than eight levels deep; image-only forms are left grouped. Narrow legacy THSarabunPSK character-map recovery, checked glyph by glyph against the subset's own advances, is recorded, also for text that was never grouped (text_prepare). Legacy Thai source editing reuses original font programs, positional glyphs and same-face subsets (a Thai run split across subsets is shaped cluster by cluster; what no subset can show in its codes is shaped whole by a complete program of the face, such as one from Load font), preserving Regular/Bold, point size, spacing and paint. A different Thai family requires explicit selection; the Noto replacement is Regular only; mixed contextual style runs remain limited. The Edit tab turns Edit text & images on at once, as in Acrobat (leaving the tab puts the tool down and applies an edit in progress); Esc in an open paragraph leaves its text and selects its box, so Delete removes it (the unapplied draft is discarded); 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. Replacement text the paragraph's font can't show takes the craft-fonts Japanese faces when they draw all of it (Uzbek қ, Kazakh ә are in none of them), else the bundled Inter face, so those letters write in every build (#2593). Legacy non-SFNT preview, mixed-font/style contextual runs, full structure-tree preservation and author intent in untagged PDFs remain limited; Styled-path outline fallback batches by actual face with 240 glyphs per font and preserves source styles; adjacent equal-font/style characters form runs. At most 15360 distinct fallback glyphs per styled paragraph; missing glyphs are refused before fallback objects are written. The Unicode row breaks (U+2028, U+2029, U+0085, vertical tab, form feed and the information separators) break the row instead of being looked up as glyphs (#6272). Glyph coverage, mixed contextual shaping, single-style fallback and stale-font cleanup are unchanged; see docs/editing-existing-text.md. Text drawn inside a Form XObject (HTML-to-PDF output, stamped or imposed pages) is grouped artwork until Ungroup (artwork right-click menu; image_edit action ungroup) draws that placement in place with its resources merged; knockout groups and transparency groups that blend inside, are drawn translucent or blend in a colour space other than the page's are refused, as are forms with resources of their own whose /DefaultGray, /DefaultRGB or /DefaultCMYK differs from the page's, either side lacking it included (ISO 32000-2 §8.6.5.6), and forms drawn inside a text object or an unfinished path, each resource name is read from the operand the renderer uses (a surplus operand before it is ignored; an operator whose operands can't be read is refused), and a pattern the form fills with is drawn from a copy whose /Matrix carries the placement (ISO 32000-2 §8.7.2), so ungrouping never changes the pixels."
evidence = ["crates/edit/src/tests.rs::editing_legacy_thai_keeps_the_embedded_face_weight_size_spacing_and_paint_after_reopen", "crates/edit/src/tests.rs::editing_legacy_thai_shapes_a_run_across_subsets_of_the_same_face", "crates/edit/src/tests.rs::grouped_text_nested_past_the_level_limit_is_reported", "crates/edit/src/tests.rs::grouped_image_artwork_is_not_prepared", "crates/edit/src/tests.rs::grouped_text_in_an_rgb_group_needs_a_page_that_blends_in_rgb", "crates/fonts/src/pdf/thai.rs::recovered_thai_map_checks_every_kept_glyph_against_the_font_program", "crates/automation/tests/it/automation.rs::text_prepare_refuses_pages_without_grouped_text", "crates/fonts/src/pdf/tests.rs::composite_subset_without_cmap_uses_pdf_glyph_ids_in_editor_and_shaper_without_replacing_the_program", "crates/automation/tests/it/automation.rs::grouped_existing_thai_body_edits_saves_reopens_and_undo_restores_original_glyphs", "crates/edit/src/tests.rs::grouped_text_preparation_keeps_unsafe_compositing_paths_and_malformed_forms_intact", "crates/automation/tests/it/automation.rs::explicit_bundled_thai_font_choice_layout_edit_undo_and_reopen", "crates/edit/src/tests.rs::existing_thai_subset_uses_shaped_matching_face_without_embedding_variable_font", "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/it/editing.rs::editing_existing_text_in_place", "crates/automation/tests/it/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/it/automation.rs::a_line_split_across_content_streams_is_listed_and_edited", "crates/ui-egui/tests/it/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/it/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/it/document_fonts.rs::opentype_cff_document_font_preserves_program_codes_and_rendering_after_reopen", "crates/ui-egui/tests/it/editing.rs::cropped_images_do_not_intercept_text_and_stay_inside_each_page", "crates/automation/tests/it/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/it/editing.rs::one_editor_contains_mixed_styles_and_visual_rows", "crates/ui-egui/tests/it/editing.rs::inline_editor_preserves_source_font_and_previews_live_bounds", "crates/fonts/src/pdf/tests.rs::mac_subset_cmap_uses_pdf_unicode_without_changing_source_program", "crates/automation/tests/it/automation.rs::mixed_visual_region_edit_preview_save_reopen_and_undo_through_tools", "crates/ui-egui/tests/it/ui.rs::the_edit_tab_turns_on_edit_text_and_images", "crates/ui-egui/tests/it/editing.rs::the_caret_on_a_small_row_is_as_tall_as_that_row", "crates/edit/src/tests.rs::uzbek_and_kazakh_cyrillic_use_the_bundled_latin_face", "crates/automation/tests/it/automation.rs::editing_uzbek_and_kazakh_cyrillic_through_tools", "crates/edit/src/tests.rs::styled_paragraph_batches_cyrillic_without_changing_source_fonts", "crates/edit/src/tests.rs::styled_paragraph_over_240_fallback_glyphs_splits_and_refuses_atomically", "crates/edit/src/tests.rs::styled_fallback_batches_keep_face_choices_and_range_styles", "crates/edit/src/tests.rs::styled_fallback_shards_keep_text_advances_and_alignment", "crates/edit/src/tests.rs::styled_fallback_budget_is_enforced_before_objects_are_written", "crates/edit/src/tests.rs::styled_fallback_same_base_font_and_character_keep_both_weights", "crates/automation/tests/it/automation.rs::styled_cyrillic_fallback_is_batched_through_tools_and_reopen", "crates/ui-egui/tests/it/editing.rs::escape_in_an_open_paragraph_selects_its_box_for_delete", "crates/edit/src/tests.rs::ungrouping_form_artwork_makes_its_text_editable", "crates/automation/tests/it/automation.rs::ungrouping_a_table_drawn_in_a_form_makes_its_text_editable", "crates/ui-egui/tests/it/editing.rs::ungroup_in_the_artwork_menu_makes_a_forms_text_editable", "crates/edit/src/tests.rs::ungroup_carries_the_placement_into_the_patterns_it_selects", "crates/automation/tests/it/automation.rs::ungrouping_keeps_patterns_where_the_forms_placement_put_them", "crates/edit/src/tests.rs::ungroup_refuses_a_form_with_its_own_default_colour_space", "crates/edit/src/tests.rs::ungroup_renames_the_name_operand_the_renderer_reads", "crates/ui-egui/tests/it/editing.rs::one_click_on_grouped_text_opens_it_for_editing", "crates/edit/src/tests.rs::grouped_text_at_says_which_artwork_preparation_would_open", "crates/edit/src/testsLine truncated
notes = "Upright single-style Keep source fonts edits without a line break preserve encodable source runs and batch only missing glyphs in Type 3 fallbacks (#2750); wrapping/alignment use both faces and keep source resources. Unencodable spaces at source/fallback boundaries are extractable glyphs, not numeric-only positioning; encodable spaces stay in the source face. In Edit text, one click on text in a supported grouped Form prepares its page (one undoable step) and opens the paragraph clicked, so its existing source glyphs are edited directly; nothing is prepared while pages are only shown, a drag still moves the selected group, and a click on grouped artwork that preparation wouldn't open (a logo, a refused group) selects it as before. text_prepare does the same headlessly, with the same code as Ungroup. Unsafe groups remain intact and are reported by text_prepare and in the repair log when the page is prepared, as is text grouped more than eight levels deep; image-only forms are left grouped. Narrow legacy THSarabunPSK character-map recovery, checked glyph by glyph against the subset's own advances, is recorded, also for text that was never grouped (text_prepare). Legacy Thai source editing reuses original font programs, positional glyphs and same-face subsets (a Thai run split across subsets is shaped cluster by cluster; what no subset can show in its codes is shaped whole by a complete program of the face, such as one from Load font), preserving Regular/Bold, point size, spacing and paint. A different Thai family requires explicit selection; the Noto replacement is Regular only; mixed contextual style runs remain limited. The Edit tab turns Edit text & images on at once, as in Acrobat, and so does All tools ▸ Edit a PDF (also from Home, the command palette and the control channel); leaving the tab, or Edit a PDF for another tool from All tools, puts the tool down and applies an edit in progress; Esc in an open paragraph leaves its text and selects its box, so Delete removes it (the unapplied draft is discarded); 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. Replacement text the paragraph's font can't show takes the craft-fonts Japanese faces when they draw all of it (Uzbek қ, Kazakh ә are in none of them), else the bundled Inter face, so those letters write in every build (#2593). Legacy non-SFNT preview, mixed-font/style contextual runs, full structure-tree preservation and author intent in untagged PDFs remain limited; Styled-path outline fallback batches by actual face with 240 glyphs per font and preserves source styles; adjacent equal-font/style characters form runs. At most 15360 distinct fallback glyphs per styled paragraph; missing glyphs are refused before fallback objects are written. The Unicode row breaks (U+2028, U+2029, U+0085, vertical tab, form feed and the information separators) break the row instead of being looked up as glyphs (#6272). Glyph coverage, mixed contextual shaping and stale-font cleanup are unchanged; the single-style source/fallback path above is handled separately; see docs/editing-existing-text.md. Text drawn inside a Form XObject (HTML-to-PDF output, stamped or imposed pages) is grouped artwork until Ungroup (artwork right-click menu; image_edit action ungroup) draws that placement in place with its resources merged; knockout groups and transparency groups that blend inside, are drawn translucent or blend in a colour space other than the page's are refused, as are forms with resources of their own whose /DefaultGray, /DefaultRGB or /DefaultCMYK differs from the page's, either side lacking it included (ISO 32000-2 §8.6.5.6), and forms drawn inside a text object or an unfinished path, each resource name is read from the operand the renderer uses (a surplus operand before it is ignored; an operator whose operands can't be read is refused), and a pattern the form fills with is drawn from a copy whose /Matrix carries the placement (ISO 32000-2 §8.7.2), so ungrouping never changes the pixels."
[[feature]]
id = "edit.text-reflow"
@@ -2068,11 +2068,11 @@ title = "Find and replace in edit mode"
area = "C"
tier = "P2"
milestone = "M7"
status = "planned"
commands = []
tools = []
evidence = []
notes = ""
status = "partial"
commands = ["edit.replace"]
tools = ["text_replace"]
evidence = ["crates/engine/src/find_replace/tests.rs::a_replaced_word_keeps_its_paragraphs_font_and_size", "crates/engine/src/find_replace/tests.rs::a_mixed_style_paragraph_keeps_the_other_runs_styles", "crates/engine/src/find_replace/tests.rs::replace_all_across_pages_is_one_undo_step", "crates/engine/src/find_replace/tests.rs::whole_words_and_case_follow_the_find_options", "crates/engine/src/find_replace/tests.rs::a_match_spanning_two_paragraphs_is_skipped_and_counted", "crates/engine/src/find_replace/tests.rs::a_character_the_font_cannot_draw_is_skipped_and_counted", "crates/engine/src/find_replace/tests.rs::a_character_the_font_cannot_draw_is_skipped_in_a_paragraph_of_several_runs", "crates/engine/src/find_replace/tests.rs::a_replacement_outside_the_fonts_encoding_is_reported_and_changes_nothing", "crates/engine/src/find_replace/tests.rs::a_longer_replacement_adds_rows_and_moves_nothing_else", "crates/engine/src/find_replace/tests.rs::a_longer_replacement_grows_one_row_to_the_margin", "crates/engine/src/find_replace/tests.rs::case_insensitive_replace_all_takes_a_match_across_a_hyphenated_row_end", "crates/engine/src/find_replace/tests.rs::invisible_text_over_a_scan_is_skipped", "crates/engine/src/find_replace/tests.rs::the_highlighted_match_is_the_one_replaced", "crates/engine/src/find_replace/tests.rs::matches_the_paragraphs_dont_hold_are_counted_as_not_editable", "crates/engine/src/find_replace/tests.rs::first_replaces_only_the_first_match_in_document_order", "crates/edit/src/tests.rs::replacing_words_apart_in_a_paragraph_keeps_every_run_between_them", "crates/edit/src/tests.rs::style_sources_must_cover_the_text_with_the_paragraphs_runs", "crates/render/src/text/tests.rs::text_search_agrees_with_the_page_search", "crates/ui-egui/tests/it/find_replace.rs::replace_all_from_the_find_bar_is_one_undo_step", "crates/ui-egui/tests/it/find_replace.rs::replace_changes_the_highlighted_match", "crates/automation/tests/it/automation.rs::replacing_text_through_tools"]
notes = "in Edit text mode the floating Find box (⌘F) gains a Replace with row (⇧⌘H / Ctrl+Shift+H, Edit ▸ Replace…, which turns Edit text on; ⌘H is macOS's Hide and Ctrl+H Read mode): Replace changes the highlighted match, Replace all every match in the document as one undo step. Matching is the find bar's own (case-sensitive and whole-words options, whitespace runs as one space). Each match is rewritten inside its own paragraph through Edit text's paragraph rewrite, which keeps the paragraph's fonts, sizes and colours and rewraps it; the replacement takes the run it lands in and every other run keeps its own style. A longer replacement is not shrunk or hyphenated: one row grows to the page's margin and then wraps, several rows keep their width, and new rows go below without moving anything else, so they can overlap what follows and that is not reported. Left alone and counted in the notice (and text_replace's skipped_by): a match spanning two paragraphs, invisible OCR text, a paragraph whose own font can't draw the new text (no silent switch to another font), a paragraph the engine refuses, and matches the find bar sees outside editable paragraphs (form fields, comments, text inside form XObjects). Missing: replacing inside form fields, comments and form XObjects, matches across paragraphs, a word broken by a hyphen at a row's end found by its unhyphenated spelling (the find bar reads it with a space there), regular expressions and Acrobat's other search options (diacritics, bookmarks, comments), offering a substitute font for a skipped paragraph, and right-to-left (Hebrew) paragraphs of several styles (the paragraph rewrite rewrites them in one style). The highlighted match is mapped to its paragraph by position (paragraph box and an estimate along its run), so two overlapping paragraphs may pick the nearer one."
[[feature]]
id = "edit.rtl-cjk-editing"
@@ -3550,8 +3550,8 @@ milestone = "M3"
status = "shipped"
commands = ["file.properties"]
tools = ["doc_info"]
evidence = ["crates/ui-egui/tests/it/editing.rs::document_properties_edit_is_one_undoable_step", "crates/ui-egui/tests/it/editing.rs::cancelling_properties_discards_the_draft", "crates/organize/src/tests.rs::document_info_falls_back_to_the_xmp_packet", "crates/engine/src/tests.rs::document_information_stated_only_in_xmp_is_shown", "crates/ui-egui/tests/it/dialog_chrome.rs::document_properties_values_line_up_and_long_fields_wrap", "crates/ui-egui/tests/it/dialog_chrome.rs::space_audit_tab_measures_the_file_and_stays_in_the_card"]
notes = "Description, Initial View, Security, Fonts, Custom (organize.custom-properties) and Advanced, plus a Space Audit tab Acrobat does not have (its audit is only in the PDF Optimizer); the audit runs when the tab opens and again after an edit. Title, Author, Subject and Keywords (and Creator, Producer in doc_info) come from /Info, else from the XMP packet (dc:title, dc:creator, dc:description, pdf:Keywords, xmp:CreatorTool, pdf:Producer) for files that state them only there; edits write both. Missing: a UTF-16 XMP packet isn't read, and /Info kept only in an older revision's trailer isn't looked up"
evidence = ["crates/ui-egui/tests/it/properties_session.rs::properties_session_does_not_overwrite_a_newer_document_edit", "crates/ui-egui/tests/it/properties_session.rs::properties_session_rejects_undo_back_to_the_original_value", "crates/ui-egui/tests/it/editing.rs::document_properties_edit_is_one_undoable_step", "crates/ui-egui/tests/it/editing.rs::cancelling_properties_discards_the_draft", "crates/organize/src/tests.rs::document_info_falls_back_to_the_xmp_packet", "crates/engine/src/tests.rs::document_information_stated_only_in_xmp_is_shown", "crates/ui-egui/tests/it/dialog_chrome.rs::document_properties_values_line_up_and_long_fields_wrap", "crates/ui-egui/tests/it/dialog_chrome.rs::space_audit_tab_measures_the_file_and_stays_in_the_card"]
notes = "Document Properties binds its draft to document identity, generation and permissions; newer edits, Undo/Redo and reload dismiss stale values before commit. Other dialogs are outside this guard. Description, Initial View, Security, Fonts, Custom (organize.custom-properties) and Advanced, plus a Space Audit tab Acrobat does not have (its audit is only in the PDF Optimizer); the audit runs when the tab opens and again after an edit. Title, Author, Subject and Keywords (and Creator, Producer in doc_info) come from /Info, else from the XMP packet (dc:title, dc:creator, dc:description, pdf:Keywords, xmp:CreatorTool, pdf:Producer) for files that state them only there; edits write both. Missing: a UTF-16 XMP packet isn't read, and /Info kept only in an older revision's trailer isn't looked up"
[[feature]]
id = "organize.edit-metadata"
@@ -3562,8 +3562,8 @@ 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/organize/src/tests.rs::document_info_edits_update_the_xmp_packet", "crates/ui-egui/tests/it/editing.rs::document_properties_edit_is_one_undoable_step"]
notes = "When the file has an XMP packet, the matching property (dc:title, dc:creator, dc:description, pdf:Keywords, xmp:CreatorTool, pdf:Producer) is changed or removed with the /Info entry; a packet that isn't UTF-8 is left as it was. Not yet: xmp:ModifyDate and xmp:MetadataDate on save, other languages of a title kept apart (the new value replaces them)."
evidence = ["crates/ui-egui/tests/it/properties_session.rs::properties_session_does_not_overwrite_a_newer_document_edit", "crates/ui-egui/tests/it/properties_session.rs::properties_session_rejects_undo_back_to_the_original_value", "crates/organize/src/tests.rs::document_info_edits_round_trip_with_unicode", "crates/organize/src/tests.rs::document_info_edits_update_the_xmp_packet", "crates/ui-egui/tests/it/editing.rs::document_properties_edit_is_one_undoable_step"]
notes = "Document Properties binds its draft to document identity, generation and permissions; newer edits, Undo/Redo and reload dismiss stale values before commit. Other dialogs are outside this guard. When the file has an XMP packet, the matching property (dc:title, dc:creator, dc:description, pdf:Keywords, xmp:CreatorTool, pdf:Producer) is changed or removed with the /Info entry; a packet that isn't UTF-8 is left as it was. Not yet: xmp:ModifyDate and xmp:MetadataDate on save, other languages of a title kept apart (the new value replaces them)."
[[feature]]
id = "organize.fonts-list"
@@ -3686,8 +3686,8 @@ milestone = "M5"
status = "shipped"
commands = ["comment.note"]
tools = ["comment_add"]
evidence = ["crates/ui-egui/tests/it/comments.rs::sticky_note_composer_posts_and_returns_to_select", "crates/ui-egui/tests/it/comments.rs::the_comment_composer_shortcut_only_posts_the_focused_editor", "crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/annot/src/tests.rs::every_note_icon_draws_its_own_shape", "crates/annot/src/tests.rs::a_star_note_keeps_its_star_when_recoloured", "crates/engine/src/tests.rs::sticky_notes_stay_upright_where_they_are_placed_on_rotated_pages", "crates/render/src/raster/tests.rs::no_rotate_notes_stay_upright_at_their_corner_on_rotated_pages", "crates/ui-egui/tests/it/comments.rs::a_sticky_note_on_a_turned_page_is_picked_where_it_is_drawn"]
notes = "our own icon drawings for the seven standard /Name values of ISO 32000-2 Table 175 (Comment, Note, Help, Insert, Key, NewParagraph, Paragraph) and seven further names (Check, Circle, Cross, CrossHairs, Star, RightArrow, UpArrow), each its own shape, chosen in Properties or comment_add, and kept when the colour changes; another name draws as Note. With a pop-up; Ctrl+Enter (also Command+Enter) posts from the focused editor, Shift+Enter adds a newline. Notes are saved with the NoRotate flag, and it is honoured: on a page with /Rotate the icon stays upright with its top-left where it was placed, and is selected and hovered there (#2583). Not yet: opening the pop-ups a file saves open (/Open); NoZoom (the icon scales with the zoom); printing and flattening still place the icon in its /Rect, turned with the page."
evidence = ["crates/ui-egui/tests/it/comments.rs::sticky_note_composer_posts_and_returns_to_select", "crates/ui-egui/tests/it/comments.rs::the_comment_composer_shortcut_only_posts_the_focused_editor", "crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/annot/src/tests.rs::every_note_icon_draws_its_own_shape", "crates/annot/src/tests.rs::a_star_note_keeps_its_star_when_recoloured", "crates/engine/src/tests.rs::sticky_notes_stay_upright_where_they_are_placed_on_rotated_pages", "crates/render/src/raster/tests.rs::no_rotate_notes_stay_upright_at_their_corner_on_rotated_pages", "crates/ui-egui/tests/it/comments.rs::a_sticky_note_on_a_turned_page_is_picked_where_it_is_drawn", "crates/print/src/tests/appearances.rs::vector_print_preserves_no_rotate_appearance_anchors"]
notes = "our own icon drawings for the seven standard /Name values of ISO 32000-2 Table 175 (Comment, Note, Help, Insert, Key, NewParagraph, Paragraph) and seven further names (Check, Circle, Cross, CrossHairs, Star, RightArrow, UpArrow), each its own shape, chosen in Properties or comment_add, and kept when the colour changes; another name draws as Note. With a pop-up; Ctrl+Enter (also Command+Enter) posts from the focused editor, Shift+Enter adds a newline. Notes are saved with the NoRotate flag, and it is honoured: on a page with /Rotate the icon stays upright with its top-left where it was placed, and is selected and hovered there (#2583). Not yet: opening the pop-ups a file saves open (/Open); NoZoom (the icon scales with the zoom). Vector printing preserves existing printable Text appearances upright even when NoRotate is implicit and the flag is clear; imported Text appearances with that flag clear still turn in the native view. Missing print appearances are not synthesized. Edit flattening is tracked separately."
[[feature]]
id = "comment.highlight"
@@ -4806,8 +4806,8 @@ 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/it/automation.rs::javascript_through_tools", "crates/engine/src/tests.rs::a_calculate_script_setting_its_own_field_object_keeps_the_value", "crates/engine/src/tests.rs::a_calculate_script_assigning_event_value_wins_and_a_combo_keeps_its_selection", "crates/forms/src/tests.rs::a_calculated_combo_stores_the_export_value_of_the_option_it_names", "crates/forms/src/tests.rs::an_export_value_wins_over_an_earlier_options_display_text"]
notes = "Custom keystroke (commit), validate, calculate and format scripts run in Acrobat's event order; AF calls still run natively. A calculate script may set its own field through the field object (getField(\"Total\").value = …) instead of event.value; that value is kept, text included, unless the script also assigns event.value (even the value it already had). A combo box a calculation moves to another option, named by export value (first) or display text, stores the export value in /V and selects it in /I."
evidence = ["crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/it/automation.rs::javascript_through_tools", "crates/engine/src/tests.rs::a_calculate_script_setting_its_own_field_object_keeps_the_value", "crates/engine/src/tests.rs::a_calculate_script_assigning_event_value_wins_and_a_combo_keeps_its_selection", "crates/forms/src/tests.rs::a_calculated_combo_stores_the_export_value_of_the_option_it_names", "crates/forms/src/tests.rs::an_export_value_wins_over_an_earlier_options_display_text", "crates/ui-egui/tests/it/prepare.rs::the_calculate_tab_shows_a_custom_calculation_script", "crates/ui-egui/tests/it/prepare.rs::an_edited_custom_script_is_written_and_no_calculation_removes_it", "crates/ui-egui/tests/it/prepare.rs::an_edit_past_256_kib_is_refused_and_the_next_edit_is_taken", "crates/ui-egui/tests/it/prepare.rs::a_script_past_256_kib_shows_its_start_and_is_kept", "crates/forms/src/tests.rs::a_custom_calculation_script_is_written_as_given_and_a_blank_one_removes_it"]
notes = "Custom keystroke (commit), validate, calculate and format scripts run in Acrobat's event order; AF calls still run natively. Field Properties ▸ Calculate ▸ Custom calculation script shows a field's calculate script and edits it (of one past 256 KiB the start is shown, read-only, and the script is kept); the Format and Validate tabs don't offer their custom scripts yet, so those are set with form_set_script. A calculate script may set its own field through the field object (getField(\"Total\").value = …) instead of event.value; that value is kept, text included, unless the script also assigns event.value (even the value it already had). A combo box a calculation moves to another option, named by export value (first) or display text, stores the export value in /V and selects it in /I."
[[feature]]
id = "form.calculation-order"
@@ -5279,8 +5279,8 @@ 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/it/automation.rs::tab_order_and_field_appearance_through_tools", "crates/forms/src/tests.rs::ordering_tabs_manually", "crates/ui-egui/tests/it/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"
evidence = ["crates/forms/src/tests.rs::tab_order_follows_the_page_setting", "crates/automation/tests/it/automation.rs::tab_order_and_field_appearance_through_tools", "crates/forms/src/tests.rs::ordering_tabs_manually", "crates/ui-egui/tests/it/prepare.rs::the_fields_panel_orders_tabs_manually", "crates/xfa/src/tests.rs::widgets_are_written_in_the_templates_tab_order", "crates/xfa/src/tests.rs::links_to_hidden_subforms_are_ignored_and_other_non_stops_pass_tab_on", "crates/xfa/src/tests.rs::class_and_dotted_traversal_references_resolve_as_som_paths", "crates/xfa/src/tests.rs::looping_and_dangling_traversal_links_keep_every_field_once", "crates/xfa/src/tests.rs::repeated_subform_instances_are_tabbed_one_after_another", "crates/xfa/src/tests.rs::traversal_links_are_parsed", "crates/engine/src/tests.rs::xfa_forms_tab_in_their_templates_traversal_order"]
notes = "rows, columns, structure/annotation order (page /Tabs), and manual order from the Fields panel while preparing a form; honoured by Tab. A laid-out dynamic XFA form tabs as its template's <traversal> says, as measured on two synthetic forms in another viewer: next links, a container's first link, rows then columns without links, hidden, invisible and protected fields passed over (a link to one goes on by its own link), a hidden subform passed over as if absent (a link to it is ignored), and the stops a chain never reached afterwards. Missing for XFA: read-only fields are stops there but Tab skips them here; the arrow-key traversal links; traversal scripts; the order across pages follows the pages"
[[feature]]
id = "form.show-tab-numbers"
@@ -5363,8 +5363,8 @@ 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/it/automation.rs::javascript_through_tools", "crates/js/src/tests.rs::an_xfa_form_sees_the_xfa_plugin_installed", "crates/js/src/tests.rs::a_plain_form_sees_the_xfa_version_but_not_installed", "crates/engine/src/tests.rs::scripts_in_an_xfa_form_see_the_xfa_plugin_installed", "crates/js/src/tests.rs::scand_allows_separators_between_fields_but_nothing_before_or_after_them", "crates/js/src/tests.rs::app_alert_keeps_its_title_icon_and_buttons", "crates/js/src/tests.rs::app_alert_reads_the_object_form", "crates/js/src/tests.rs::app_alert_falls_back_on_bad_icon_and_button_numbers", "crates/js/src/tests.rs::app_alert_titles_are_clipped_and_charged", "crates/ui-egui/tests/it/javascript.rs::every_message_a_script_shows_takes_the_toast_in_turn", "crates/js/src/tests.rs::widget_rect_reads_and_writes_a_four_number_array", "crates/engine/src/tests.rs::javascript_field_rect_moves_first_widget_and_undoes"]
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes, rect), app (with execMenuItem for the side panels and Save As, from a button only, and Print where this.print() works), util.printf/printd/printx/scand (scand reads dates as another viewer was observed to, in AcroForm and XFA scripts), console, display, color; xfa_version (3.5) in every document and xfa_installed in XFA forms, so their version check passes. field.rect reads and writes the first widget in PDF user space. no annotations, layers or dialogs (app.response returns null) yet, and no app.findComponent. app.alert keeps its title, icon (error, warning, question, status) and buttons (OK, OK/Cancel, Yes/No, Yes/No/Cancel), given in order or as one object; js_run reports them with each message, and the app shows every message in turn in the toast with its title and icon. Still missing: answers. Every app.alert returns 1 (OK), so a script is never told No or Cancel (#4291)."
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/it/automation.rs::javascript_through_tools", "crates/js/src/tests.rs::an_xfa_form_sees_the_xfa_plugin_installed", "crates/js/src/tests.rs::a_plain_form_sees_the_xfa_version_but_not_installed", "crates/engine/src/tests.rs::scripts_in_an_xfa_form_see_the_xfa_plugin_installed", "crates/js/src/tests.rs::scand_allows_separators_between_fields_but_nothing_before_or_after_them", "crates/ui-egui/tests/it/forms.rs::a_form_button_opens_its_attached_instructions_in_a_new_tab", "crates/js/src/tests.rs::app_alert_keeps_its_title_icon_and_buttons", "crates/js/src/tests.rs::app_alert_reads_the_object_form", "crates/js/src/tests.rs::app_alert_falls_back_on_bad_icon_and_button_numbers", "crates/js/src/tests.rs::app_alert_titles_are_clipped_and_charged", "crates/ui-egui/tests/it/javascript.rs::every_message_a_script_shows_takes_the_toast_in_turn", "crates/js/src/tests.rs::widget_rect_reads_and_writes_a_four_number_array", "crates/engine/src/tests.rs::javascript_field_rect_moves_first_widget_and_undoes", "crates/engine/src/tests.rs::js_get_page_box_reads_effective_boxes_without_editing_the_document", "crates/js/src/tests.rs::get_page_box_returns_supported_boxes_in_rotated_user_space"]
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes, rect), this.getPageBox(cBox, nPage) for effective Media, Crop (default), Trim, Bleed and Art boxes in rotated user space, app (with execMenuItem for the side panels and Save As, from a button only, and Print where this.print() works), util.printf/printd/printx/scand (scand reads dates as another viewer was observed to, in AcroForm and XFA scripts), console, display, color, exportDataObject (from a button only: an attached PDF opens in a new tab, other files are offered to save, also as event.target in XFA scripts); xfa_version (3.5) in every document and xfa_installed in XFA forms, so their version check passes. field.rect reads and writes the first widget in PDF user space. no annotations, layers or dialogs (app.response returns null) yet, and no app.findComponent. app.alert keeps its title, icon (error, warning, question, status) and buttons (OK, OK/Cancel, Yes/No, Yes/No/Cancel), given in order or as one object; js_run reports them with each message, and the app shows every message in turn in the toast with its title and icon. Still missing: answers. Every app.alert returns 1 (OK), so a script is never told No or Cancel (#4291)."
[[feature]]
id = "form.js-console"
@@ -5375,8 +5375,8 @@ milestone = "M6"
status = "shipped"
commands = ["tools.js_console"]
tools = ["js_run"]
evidence = ["crates/ui-egui/tests/it/javascript.rs::console_runs_scripts_against_the_document", "crates/automation/tests/it/automation.rs::javascript_through_tools"]
notes = "\u2318J: run JavaScript against the document, with console output, results and errors."
evidence = ["crates/ui-egui/tests/it/javascript.rs::console_runs_scripts_against_the_document", "crates/automation/tests/it/automation.rs::javascript_through_tools", "crates/engine/src/tests.rs::calculate_now_updates_live_fields_in_one_undo_step"]
notes = "\u2318J: run JavaScript against the document, with console output, results and errors. calculateNow() runs calculated fields synchronously in /CO order against writes already made by the script."
[[feature]]
id = "form.js-debugger"
@@ -5411,8 +5411,8 @@ 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/it/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/it/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/it/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)."
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/it/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/it/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/it/document_links.rs::a_script_cannot_open_other_kinds_of_address", "crates/engine/src/tests.rs::automatic_field_scripts_cannot_print", "crates/engine/src/tests.rs::automatic_print_suppression_preserves_document_errors", "crates/engine/src/tests.rs::form_calculations_cannot_print_but_buttons_and_console_can", "crates/automation/src/tests.rs::form_fill_cannot_print_but_js_run_can"]
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). Automatic AcroForm Keystroke, Validate, Calculate and Format scripts cannot request printing; an ignored Print request is reported in the JavaScript console. Explicit button and console printing remain available (#4088). Integration with main f137a289 (0.16.1), validated on 2026-10-31 on Windows x64 / Rust 1.95: 761 engine/automation tests passed, 0 failed, 4 existing ignored; formatting, scoped all-target Clippy, layers/assets/parity, scoped engine/automation WebAssembly and a fresh CLI build passed. The 13-step synthetic CLI exercise preserved Qty=4, Total=8 and explicit button/console Print requests through save/render; the rendered values were inspected. Existing main warnings remain (100 headless parity notices, 5 renderer WebAssembly warnings, 3 optional egui patch notices). Native printer transport remains untested."
[[feature]]
id = "form.folder-level-js"
@@ -5943,8 +5943,8 @@ milestone = "M8"
status = "shipped"
commands = ["redact.apply"]
tools = ["redact_apply"]
evidence = ["crates/redact/src/tests.rs::images_are_removed_or_have_their_pixels_cleared", "crates/redact/src/tests.rs::redaction_clears_pixels_intersected_without_covering_their_centres", "crates/redact/src/tests.rs::a_fax_image_has_its_covered_pixels_cleared_instead_of_being_removed", "crates/redact/src/tests.rs::group_3_on_bytes_is_removed_whole_and_other_fax_images_cleared", "crates/redact/src/tests.rs::an_image_whose_predictor_is_given_by_reference_has_the_covered_pixel_cleared", "crates/redact/src/tests.rs::a_jbig2_image_has_its_covered_pixels_cleared_instead_of_being_removed", "crates/redact/src/tests.rs::a_jbig2_image_that_cannot_be_cleared_for_certain_is_removed_whole", "crates/redact/src/tests.rs::a_redacted_jbig2_scan_keeps_neither_its_code_nor_its_globals", "crates/redact/src/tests.rs::a_cleared_black_and_white_scan_is_stored_as_group_4", "crates/redact/src/tests.rs::globals_a_redacted_jbig2_image_shares_with_another_page_leave_the_file"]
notes = "intersected pixel footprints are cleared conservatively in a copy, CCITT fax and JBIG2 images (scans stored as Group 4 or JBIG2, with their /JBIG2Globals) included; DCT/JPX images, Group 3 on bytes (which the bootstrap renderer may draw as other rows than the decoder reads), and a JBIG2 image whose page isn't its size, removed whole. The copy is Flate, or for a one-bit image of one component Group 4, or JBIG2 when it was JBIG2, whichever is smallest as stored, and refers to no globals; every other image using a redacted image's globals is stored again without them (Flate, Group 4 or JBIG2, whichever is smallest), so they leave the file (one that can't be decoded refuses the redaction)"
evidence = ["crates/redact/src/tests.rs::images_are_removed_or_have_their_pixels_cleared", "crates/redact/src/tests.rs::a_redacted_scans_shared_jbig2_dictionary_leaves_the_file", "crates/redact/src/tests.rs::redaction_clears_pixels_intersected_without_covering_their_centres", "crates/redact/src/tests.rs::a_fax_image_has_its_covered_pixels_cleared_instead_of_being_removed", "crates/redact/src/tests.rs::group_3_on_bytes_is_removed_whole_and_other_fax_images_cleared", "crates/redact/src/tests.rs::an_image_whose_predictor_is_given_by_reference_has_the_covered_pixel_cleared", "crates/redact/src/tests.rs::a_jbig2_image_has_its_covered_pixels_cleared_instead_of_being_removed", "crates/redact/src/tests.rs::a_jbig2_image_that_cannot_be_cleared_for_certain_is_removed_whole", "crates/redact/src/tests.rs::a_redacted_jbig2_scan_keeps_neither_its_code_nor_its_globals", "crates/redact/src/tests.rs::a_cleared_black_and_white_scan_is_stored_as_group_4", "crates/redact/src/tests.rs::globals_a_redacted_jbig2_image_shares_with_another_page_leave_the_file"]
notes = "intersected pixel footprints are cleared conservatively in a copy, CCITT fax and JBIG2 images (scans stored as Group 4 or JBIG2, with their /JBIG2Globals) included; DCT/JPX images, Group 3 on bytes (which the bootstrap renderer may draw as other rows than the decoder reads), and a JBIG2 image whose page isn't its size, removed whole. The copy is Flate, or for a one-bit image of one component Group 4, or JBIG2 when it was JBIG2, whichever is smallest as stored, and refers to no globals; whatever left the file takes its globals with it: what else names them, or an exact copy, is stored again without them (Flate, Group 4 or JBIG2, whichever is smallest), and apply refuses when one can't be. Not found: a dictionary holding the same shapes in other bytes. An image that hasn't left the file keeps its globals with its pixels: one another page draws, or one a pattern, soft mask or structure element refers to"
[[feature]]
id = "protect.redact-vectors"
@@ -8189,8 +8189,8 @@ milestone = "M10"
status = "shipped"
commands = ["print.dialog"]
tools = ["doc_print", "printers"]
evidence = ["crates/ui-egui/tests/it/print.rs::paper_choice_survives_cancel_and_settings_restore", "crates/ui-egui/tests/it/print.rs::restored_paper_keeps_preview_and_saved_sheet_dimensions", "crates/ui-egui/tests/it/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/workflows/web_print.rs::a_job_draws_every_sheet_at_its_planned_resolution_then_builds_the_page", "crates/ui-egui/src/workflows/web_print.rs::the_print_page_turns_sideways_sheets_names_other_sizes_and_ends_without_a_blank_page", "crates/print/src/tests.rs::every_paper_reaches_the_spooler_by_a_name_of_its_size", "crates/automation/src/printing.rs::printing_to_a_printer_feeds_the_laid_out_paper", "crates/ui-egui/tests/it/print.rs::print_preferences_survive_a_restart_but_the_jobs_own_choices_reset"]
notes = "⌘P; settings and live sheet preview; on the web the same dialog prints through the browser's print dialog; last paper selection, including cancelled Print dialogs, persists by canonical name. Two-sided (including the printer's own default), grayscale, collate, page sizing and Comments & forms are remembered across restarts (#2001); copies, pages, layout and orientation start from the defaults each time the dialog opens (ISO 32000-2 Table 147 initialises them per document). CUPS jobs from the dialog and doc_print ask for the chosen paper (media) in every size: PPD keywords for the common ones, PWG 5101.1 names for A6, A0–A2, ARCH and ANSI C–E, checked against a synthetic PPD with cupsfilter, not on a physical large-format printer (#3752). On macOS jobs still take the PPD's default paper tray until craft-print sends Auto Select (#3752). Not yet: the document's own NumCopies, PrintPageRange, Duplex and PrintScaling viewer preferences as the dialog's initial values."
evidence = ["crates/ui-egui/tests/it/print.rs::paper_choice_survives_cancel_and_settings_restore", "crates/ui-egui/tests/it/print.rs::restored_paper_keeps_preview_and_saved_sheet_dimensions", "crates/ui-egui/tests/it/print.rs::each_printer_keeps_what_it_last_printed_with_across_a_restart", "crates/ui-egui/tests/it/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/workflows/web_print.rs::a_job_draws_every_sheet_at_its_planned_resolution_then_builds_the_page", "crates/ui-egui/src/workflows/web_print.rs::the_print_page_turns_sideways_sheets_names_other_sizes_and_ends_without_a_blank_page", "crates/print/src/tests.rs::every_paper_reaches_the_spooler_by_a_name_of_its_size", "crates/automation/src/printing.rs::printing_to_a_printer_feeds_the_laid_out_paper", "crates/ui-egui/tests/it/print.rs::print_preferences_survive_a_restart_but_the_jobs_own_choices_reset"]
notes = "⌘P; settings and live sheet preview; on the web the same dialog prints through the browser's print dialog; last paper selection, including cancelled Print dialogs, persists by canonical name. Two-sided (including the printer's own default), grayscale, collate, page sizing and Comments & forms are remembered across restarts (#2001); copies, pages, layout and orientation start from the defaults each time the dialog opens (ISO 32000-2 Table 147 initialises them per document). CUPS jobs from the dialog and doc_print ask for the chosen paper (media) in every size: PPD keywords for the common ones, PWG 5101.1 names for A6, A0–A2, ARCH and ANSI C–E, checked against a synthetic PPD with cupsfilter, not on a physical large-format printer (#3752). On macOS jobs still take the PPD's default paper tray until craft-print sends Auto Select (#3752). Not yet: the document's own NumCopies, PrintPageRange, Duplex and PrintScaling viewer preferences as the dialog's initial values. Each printer printed to also keeps its own paper, page sizing (Fit, Actual size, Shrink, Custom %) and two-sided choice across restarts, brought back when it is chosen, and the printer printed to last is preselected while it is listed, else the system default (at most 32 printers)."
[[feature]]
id = "print.printer-selection"
@@ -8201,8 +8201,8 @@ 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::a_job_names_its_title_and_owner_to_the_spooler", "crates/print/src/tests.rs::a_job_keeps_the_title_and_owner_it_already_sets", "crates/print/src/tests.rs::a_submitted_job_reaches_lp_named", "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/it/print.rs::print_dialog_shows_the_printer_driver_options", "crates/ui-egui/tests/it/print.rs::printers_listed_in_the_background_preselect_the_default_unless_one_was_picked", "crates/ui-egui/src/workflows/web_print.rs::resolution_falls_with_the_sheet_count_and_long_jobs_are_refused", "crates/ui-egui/src/workflows/web_print.rs::large_sheets_are_drawn_within_the_pixel_cap_and_bad_sizes_are_refused", "crates/ui-egui/src/workflows/web_print.rs::a_sink_error_or_a_page_that_isnt_there_stops_the_job", "crates/print/src/tests.rs::a_command_that_never_answers_is_stopped_at_the_deadline", "crates/ui-egui/tests/it/print.rs::printer_options_are_read_off_the_gui_thread_and_a_dead_read_offers_nothing"]
notes = "CUPS queues via lpstat -p -d and driverless destinations without a queue via lpstat -e; on Windows shared craft-print lists the queues (EnumPrinters), the default (GetDefaultPrinter) and each driver's two-sided support in this process, and draws jobs as vectors with Windows.Data.Pdf into the XPS print API with a driver-validated PrintTicket (exact paper, orientation, no scaling; portrait and landscape sheets in one job via /Rotate), starting no PowerShell, so printing works under App Control script enforcement (#1107); the printer list and submission run on workers; 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). Properties… on CUPS reads the driver options off the GUI thread and gives lpoptions ten seconds before the PPD defaults stand (#1818); on macOS lp jobs also carry the job name and owner (com.apple.print.JobInfo.PMJobName and PMJobOwner) that the system print dialog sends and some vendors' filters need (#5743). Not yet checked on a physical Kyocera printer."
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list", "crates/print/src/tests.rs::a_job_names_its_title_and_owner_to_the_spooler", "crates/print/src/tests.rs::a_job_keeps_the_title_and_owner_it_already_sets", "crates/print/src/tests.rs::a_submitted_job_reaches_lp_named", "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/it/print.rs::print_dialog_shows_the_printer_driver_options", "crates/ui-egui/tests/it/print.rs::printers_listed_in_the_background_preselect_the_default_unless_one_was_picked", "crates/ui-egui/src/workflows/web_print.rs::resolution_falls_with_the_sheet_count_and_long_jobs_are_refused", "crates/ui-egui/src/workflows/web_print.rs::large_sheets_are_drawn_within_the_pixel_cap_and_bad_sizes_are_refused", "crates/ui-egui/src/workflows/web_print.rs::a_sink_error_or_a_page_that_isnt_there_stops_the_job", "crates/print/src/tests.rs::a_command_that_never_answers_is_stopped_at_the_deadline", "crates/ui-egui/tests/it/print.rs::printer_options_are_read_off_the_gui_thread_and_a_dead_read_offers_nothing", "crates/optimize/tests/tiles.rs::a_grid_of_tiles_keeps_its_rows_and_columns_in_place", "crates/engine/src/spooler.rs::a_large_image_goes_to_the_spooler_as_tiles"]
notes = "CUPS queues via lpstat -p -d and driverless destinations without a queue via lpstat -e; on Windows shared craft-print lists the queues (EnumPrinters), the default (GetDefaultPrinter) and each driver's two-sided support in this process, and draws jobs as vectors with Windows.Data.Pdf into the XPS print API with a driver-validated PrintTicket (exact paper, orientation, no scaling; portrait and landscape sheets in one job via /Rotate; an image of more than 2²² pixels as tiles under that size, which Windows.Data.Pdf would draw from a shrunk copy, #5131), starting no PowerShell, so printing works under App Control script enforcement (#1107); the printer list and submission run on workers; 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). Properties… on CUPS reads the driver options off the GUI thread and gives lpoptions ten seconds before the PPD defaults stand (#1818); on macOS lp jobs also carry the job name and owner (com.apple.print.JobInfo.PMJobName and PMJobOwner) that the system print dialog sends and some vendors' filters need (#5743). Not yet checked on a physical Kyocera printer. The copy sent to the Windows spooler renames every embedded TrueType/OpenType program (name table, /BaseFont, /FontName), because Windows drew a subset's renumbered glyphs with the installed font of the same name and printed shifted letters (#3132); not yet checked on a physical Windows printer."
[[feature]]
id = "print.copies-collate"
@@ -8228,6 +8228,18 @@ tools = ["doc_print"]
evidence = ["crates/print/src/tests.rs::page_selection", "crates/print/src/tests.rs::a_number_in_parentheses_names_the_page_at_that_position", "crates/automation/tests/it/automation.rs::printing_through_tools"]
notes = "all, current, ranges with labels and open ends; (n) names the nth page whatever its label (ISO 32000-2 §12.4.2: labels and page indices need not coincide), alone or as a range end; labels are looked up first, so a page labelled exactly \"(n)\" keeps that name. The same parser serves Combine files ranges. Organize Pages range selection still takes positions only, without labels or (n)."
[[feature]]
id = "print.selected-area"
title = "Print a selected area"
area = "L"
tier = "P2"
milestone = "M10"
status = "shipped"
commands = ["edit.snapshot", "print.dialog"]
tools = ["doc_print"]
evidence = ["crates/print/src/tests.rs::a_selected_area_prints_alone_fitted_or_at_actual_size", "crates/ui-egui/tests/it/viewer.rs::a_snapshot_area_prints_alone_or_saves_as_an_image", "crates/automation/tests/it/automation.rs::doc_print_a_selected_area"]
notes = "Requested in #2051. The area Take a Snapshot copies stays outlined with a small menu under it, Print area… and Save as image…; Print then offers Selected area (page N, size), printed alone as a page of that size with Fit, Actual size, Shrink or a custom scale, clipped to the area. Multiple, Booklet, Poster and Label are for pages: the dialog offers only Size while the area is chosen and the print crate refuses the others. Print as image, the paper and the printer's options apply as usual. doc_print takes area [x0, y0, x1, y1] in points from the top-left of the displayed page, with one page in pages. Missing: an area spanning two pages, drawing the area from inside the Print dialog, and adjusting it after it is drawn."
[[feature]]
id = "print.odd-even-reverse"
title = "Odd/even pages and reverse order"
@@ -8273,8 +8285,8 @@ milestone = "M10"
status = "shipped"
commands = ["print.dialog"]
tools = ["doc_print"]
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list", "crates/ui-egui/src/workflows/print_ui.rs::two_sided_follows_the_printer_until_chosen", "crates/automation/src/printing.rs::duplex_printer_leaves_two_sided_to_the_printer"]
notes = "long/short edge via the spooler: lp -o sides=…, and the Windows driver's Duplex setting. Two-sided starts at Printer default, as in Acrobat: nothing is sent, so the printer's own default (its Properties) applies until Off or an edge is chosen (doc_print: duplex \"printer\"). Missing: the document's /ViewerPreferences /Duplex preset isn't read yet"
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list", "crates/ui-egui/src/workflows/print_ui.rs::two_sided_follows_the_printer_until_chosen", "crates/automation/src/printing.rs::duplex_printer_leaves_two_sided_to_the_printer", "crates/print/src/spool/windows_duplex.rs::printer_default_reaches_the_windows_job_for_each_binding", "crates/print/src/spool/windows_duplex.rs::failed_or_invalid_settings_fall_back_and_keep_the_resolved_queue", "crates/print/src/spool/windows_duplex.rs::manual_duplex_defaults_remain_unspecified_without_automatic_support"]
notes = "Manual two-sided defaults stay unspecified unless driver capabilities confirm automatic duplex (#5772); long/short edge via the spooler: lp -o sides=…, and the Windows driver's Duplex setting. Two-sided starts at Printer default: Windows reads the driver's current DEVMODE duplex and sends it explicitly (#5130), logging lookup failures and leaving duplex unspecified as a fallback; CUPS leaves duplex unspecified so the queue default applies (doc_print: duplex \"printer\"). Off or an edge overrides either. Physical output still needs hardware validation. Missing: the document's /ViewerPreferences /Duplex preset isn't read yet"
[[feature]]
id = "print.n-up"
@@ -8297,8 +8309,8 @@ milestone = "M10"
status = "shipped"
commands = ["print.dialog"]
tools = ["doc_print"]
evidence = ["crates/print/src/tests.rs::booklets_pair_pages_for_folding", "crates/ui-egui/tests/it/print.rs::a_booklet_decides_two_sided_itself", "crates/automation/src/printing.rs::a_booklet_decides_two_sided_itself", "crates/print/src/tests.rs::a_booklet_prints_both_sides_on_the_short_edge"]
notes = "both/front/back, left/right binding; a booklet decides two-sided itself, as Acrobat does (its Print dialog hides Two-sided while Booklet is chosen; Using Acrobat X Pro: Acrobat automatically enables duplex printing for booklet printing): both sides flip on the short edge, front or back only prints one-sided, and the Two-sided choice is kept for the other layouts (#2707)"
evidence = ["crates/print/src/tests.rs::booklets_pair_pages_for_folding", "crates/ui-egui/tests/it/print.rs::a_booklet_decides_two_sided_itself", "crates/automation/src/printing.rs::a_booklet_decides_two_sided_itself", "crates/print/src/tests.rs::a_booklet_prints_both_sides_on_the_short_edge", "crates/print/src/tests.rs::booklet_creep_moves_inner_sheets_toward_the_fold", "crates/print/src/tests.rs::a_booklet_leaves_two_sided_to_a_driver_that_keeps_its_own", "crates/ui-egui/src/workflows/print_ui.rs::a_booklet_leaves_two_sided_to_a_driver_that_keeps_its_own"]
notes = "both/front/back, left/right binding; a booklet decides two-sided itself, as Acrobat does (its Print dialog hides Two-sided while Booklet is chosen; Using Acrobat X Pro: Acrobat automatically enables duplex printing for booklet printing): both sides flip on the short edge (as Acrobat, \"if available\": a printer whose driver doesn't take two-sided settings, such as one with only manual two-sided in its Properties or Microsoft Print to PDF, is asked for nothing, so its own setting applies rather than the job being refused, #5772), front or back only prints one-sided, and the Two-sided choice is kept for the other layouts (#2707). Not yet: a guided manual two-sided pass (front, turn the stack, back) on such a printer; creep compensation (points per sheet, default 0) moves each sheet's pages toward the fold by their sheet's place from the outside, clipped at the fold (#6777); the amount is entered, not worked out from the paper's thickness"
[[feature]]
id = "print.poster"
@@ -8333,8 +8345,8 @@ 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", "crates/print/src/tests/appearances.rs::annotation_appearances_do_not_replace_page_xobjects"]
notes = "Print flag honoured; appearance resource names preserve existing page XObjects and distinct annotations, including inherited resources"
evidence = ["crates/print/src/tests.rs::imposed_pdf_has_the_sheets_and_honours_comments_and_forms", "crates/print/src/tests/appearances.rs::annotation_appearances_do_not_replace_page_xobjects", "crates/print/src/tests/appearances.rs::printable_link_appearance_survives_vector_print", "crates/print/src/tests/appearances.rs::annotation_placement_preserves_small_transform_coefficients", "crates/print/src/tests/appearances.rs::indirect_annotation_rectangles_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_rectangles_are_skipped_when_printing", "crates/print/src/tests/appearances.rs::indirect_annotation_states_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_states_do_not_select_an_appearance", "crates/print/src/tests/appearances.rs::chained_annotation_values_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_reference_chains_are_skipped_when_printing", "crates/print/src/tests/appearances.rs::vector_print_preserves_no_rotate_appearance_anchors", "crates/print/src/tests/appearances.rs::no_rotate_keeps_user_units_and_sheet_scaling", "crates/print/src/tests/optional_content.rs::vector_print_preserves_annotation_layer_membership"]
notes = "Existing field and comment appearances retain direct or indirect rectangle coordinates and appearance-state names, including reference chains of up to 64 objects with cycle detection. State dictionaries require a usable AS name; missing/invalid/unresolved/cyclic/overly deep or unknown names select no appearance, while a single normal stream needs no AS. Print content modes and source objects are preserved. Malformed rectangles are skipped; missing appearances are not generated. Print and Hidden flags honoured; existing Link appearances print as document content, but missing Link appearances are not synthesized. Appearance resource names preserve existing page XObjects and distinct annotations, including inherited resources. Placement retains small transform coefficients for large appearance bounding boxes and matrices. Existing NoRotate appearances retain orientation and the rectangle upper-left anchor through source page rotation, including Text annotations with implicit NoRotate when the flag is clear; CropBox, UserUnit, sheet scaling and later imposition rotation still apply. NoZoom does not cancel print scale. Vector output preserves per-annotation OCG/OCMD membership without changing shared appearances, including existing layer overrides; nested policy data remains reader-evaluated, and the separate raster annotation-visibility gap remains. Missing or unusable appearance streams remain outside this path."
[[feature]]
id = "print.print-with-comment-summary"
@@ -8369,8 +8381,8 @@ milestone = "M10"
status = "shipped"
commands = ["print.dialog"]
tools = ["doc_print"]
evidence = ["crates/engine/src/tests.rs::printing_as_image_sends_pictures_instead_of_pages", "crates/ui-egui/tests/it/print.rs::print_as_image_is_an_advanced_choice_that_saves_pictures", "crates/automation/tests/it/automation.rs::doc_print_as_image"]
notes = "asks the printer for monochrome (lp -o, and the Windows page settings as the job's); with Print as image the pictures themselves are rendered grey (Rec. 601 luma), so the print-ready PDF is grayscale too. Still missing against Acrobat: a grey conversion of the vector pages without rendering them."
evidence = ["crates/engine/src/tests.rs::printing_as_image_sends_pictures_instead_of_pages", "crates/ui-egui/tests/it/print.rs::print_as_image_is_an_advanced_choice_that_saves_pictures", "crates/automation/tests/it/automation.rs::doc_print_as_image", "crates/automation/src/printing.rs::grey_pictures_dont_ask_the_driver_for_grayscale", "crates/ui-egui/src/workflows/print_ui.rs::grey_pictures_dont_ask_the_driver_for_grayscale"]
notes = "asks the printer for monochrome (lp -o, and the Windows page settings as the job's); with Print as image the pictures themselves are rendered grey (Rec. 601 luma), so the print-ready PDF is grayscale too and the printer isn't asked for it: a driver that keeps no grayscale setting (Microsoft Print to PDF) prints them (#5806). Still missing against Acrobat: a grey conversion of the vector pages without rendering them, so vector pages to such a driver are refused rather than printed in colour."
[[feature]]
id = "print.print-as-image"
@@ -8390,11 +8402,22 @@ title = "Page setup"
area = "L"
tier = "P1"
milestone = "M10"
status = "planned"
commands = []
tools = []
evidence = []
notes = ""
status = "partial"
commands = ["print.dialog"]
tools = ["doc_print", "printer_papers"]
evidence = [
"crates/ui-egui/tests/it/print.rs::printer_papers_and_properties_selection",
"crates/automation/tests/it/automation.rs::printer_papers_through_tools",
"crates/print/src/tests.rs::printer_papers_and_default_paper_api",
"crates/print/src/tests.rs::overlapping_paper_discovery_two_sided_and_queue_queries",
"crates/ui-egui/tests/it/print.rs::a_custom_paper_prints_at_its_size_and_its_printer_keeps_it",
"crates/ui-egui/src/workflows/print_paper.rs::the_paper_list_is_the_standard_sizes_then_the_custom_ones",
"crates/ui-egui/src/workflows/print_paper.rs::settings_are_read_as_untrusted",
"crates/ui-egui/src/workflows/print_ui/setups.rs::setups_round_trip_through_the_settings_file",
"crates/ui-egui/tests/it/print_custom_paper_names.rs::a_custom_paper_cannot_take_a_standard_name",
"crates/automation/tests/it/automation.rs::doc_print_takes_a_paper_size_of_its_own",
]
notes = "In the Print dialog: Custom… beside Paper adds a named size in the preferences' unit (1 to 200 inches a side), kept with the settings and removable. A printer printed to on a custom paper starts on it next time (#2001). The Page Setup button opens the driver's page setup to choose paper size and orientation. printer_papers enumerates driver-reported papers and default paper headlessly. doc_print takes sizes such as 8.5x14in or 210x297mm or driver papers. Missing: margins."
[[feature]]
id = "print.print-ready-pdf"