diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 2ede8f1f89..f5308792d9 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -4195,6 +4195,18 @@ fn quick_bar(app: &mut PdfCraftApp, area: Rect, ui: &mut egui::Ui) { } }); } + // Edit text & images, while the Edit workspace is open (Acrobat's Edit toolbar) + // or the tool is in use. + if app.mode == crate::Mode::Edit || app.quick_tool == QuickTool::EditText { + let on = app.quick_tool == QuickTool::EditText; + if icons::button(ui, "text-cursor-input", 32.0, on, tl!("Edit text & images")).clicked() { + if on { + app.put_down_edit_text(); + } else { + app.quick_tool = QuickTool::EditText; + } + } + } // Fill & Sign ▸ (text, marks, date, signature). let current_fill = match app.quick_tool { QuickTool::Fill(f) => Some(f), diff --git a/crates/ui-egui/src/edit_text_ui.rs b/crates/ui-egui/src/edit_text_ui.rs index 620ba2b30b..b1629318a6 100644 --- a/crates/ui-egui/src/edit_text_ui.rs +++ b/crates/ui-egui/src/edit_text_ui.rs @@ -1284,18 +1284,23 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - match done { Some(apply) => { let ed = view.line_editor.take()?; - let style = ed.style(); - (apply && (ed.text != ed.original || style != pdfcraft_engine::BlockStyle::default())).then_some(Edit::EditTextBlock { - page: ed.page, - block: ed.block, - text: ed.text, - style, - }) + if apply { finished_edit(ed) } else { None } } None => None, } } +/// The edit a closed paragraph editor applies, or `None` when nothing in it changed. +pub(crate) fn finished_edit(ed: LineEditor) -> Option { + let style = ed.style(); + (ed.text != ed.original || style != pdfcraft_engine::BlockStyle::default()).then_some(Edit::EditTextBlock { + page: ed.page, + block: ed.block, + text: ed.text, + style, + }) +} + /// Caret colour that stays visible on a field filled with `fill`: black on light fills, white on dark ones. pub(crate) fn caret_color_on(fill: Color32) -> Color32 { let luma = 299 * u32::from(fill.r()) + 587 * u32::from(fill.g()) + 114 * u32::from(fill.b()); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 6a1ddd4f7b..a40e3e4b1a 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -2131,7 +2131,9 @@ impl PdfCraftApp { } } - /// Select the workspace and its matching tool panel, just like the mode bar. + /// Select the workspace and its matching tool panel, just like the mode bar. Edit turns on + /// Edit text & images at once, as Acrobat does: every paragraph and image on the page gets its + /// box and is editable with one click. Leaving Edit puts the editing tools down again. pub(crate) fn select_mode(&mut self, mode: Mode) { self.mode = mode; self.reveal_tools(); @@ -2144,6 +2146,23 @@ impl PdfCraftApp { Mode::Sign => LeftPanel::Tool("fill_sign"), _ => LeftPanel::AllTools, }; + if mode == Mode::Edit { + self.quick_tool = QuickTool::EditText; + } else if matches!(self.quick_tool, QuickTool::EditText | QuickTool::AddText) { + self.put_down_edit_text(); + } + } + + /// Back to the Select tool from an editing tool. A paragraph still open in the editor is + /// applied, as clicking away from it does; left open, its page would keep showing the + /// paragraph's text removed with no editor over it. + pub(crate) fn put_down_edit_text(&mut self) { + self.quick_tool = QuickTool::Select; + if let Some(ed) = self.active.and_then(|i| self.views.get_mut(i)).and_then(|v| v.line_editor.take()) + && let Some(edit) = edit_text_ui::finished_edit(ed) + { + self.apply_edit(edit); + } } /// Switches workspace because the user chose to (mode bar, Read mode command). Entering Read diff --git a/crates/ui-egui/tests/control.rs b/crates/ui-egui/tests/control.rs index afa4d72bbb..847ffcc754 100644 --- a/crates/ui-egui/tests/control.rs +++ b/crates/ui-egui/tests/control.rs @@ -1346,7 +1346,8 @@ fn explicit_panel_requests_reveal_inactive_group_members() { h.get_by_label("This document has no bookmarks."); ok(&mut h, &c, "ui.command", json!({"id":"edit.edit_text"})); h.run_steps(4); - h.get_by_label("Edit text & images"); + // The Edit panel's own row: the tool's name is on the floating toolbar too while it is on. + h.get_by_label("Header and footer"); } #[test] diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs index 077b3ccece..a40ac422c8 100644 --- a/crates/ui-egui/tests/ui.rs +++ b/crates/ui-egui/tests/ui.rs @@ -1308,3 +1308,27 @@ fn the_combine_tab_does_not_move_and_documents_stay_in_order() { drag_named_tab(&mut h, "Combine files", first.left_center() - egui::vec2(10.0, 0.0), true); assert_eq!(h.state().views.iter().map(|view| view.id).collect::>(), ids); } + +/// The Edit tab turns on Edit text & images at once, as Acrobat does, so every paragraph and image +/// is boxed and editable with one click; another tab puts the tool down again. +#[test] +fn the_edit_tab_turns_on_edit_text_and_images() { + use pdfcraft_ui_egui::{Mode, QuickTool}; + let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens")); + assert_eq!(h.state().quick_tool, QuickTool::Select); + h.get_by_label("Edit").click(); + h.run_steps(3); + assert_eq!(h.state().mode, Mode::Edit); + assert_eq!(h.state().quick_tool, QuickTool::EditText); + h.get_by_label("All tools").click(); + h.run_steps(3); + assert_eq!(h.state().mode, Mode::AllTools); + assert_eq!(h.state().quick_tool, QuickTool::Select); + // Another tool picked inside Edit is not taken away by leaving it. + h.get_by_label("Edit").click(); + h.run_steps(3); + h.state_mut().quick_tool = QuickTool::Hand; + h.get_by_label("Convert").click(); + h.run_steps(3); + assert_eq!(h.state().quick_tool, QuickTool::Hand); +} diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 2c84db6eb2..d0d0564c31 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -1866,8 +1866,8 @@ milestone = "M7" status = "partial" commands = ["edit.edit_text"] tools = ["text_lines", "text_paragraphs", "text_edit", "text_edit_layout", "text_font_files", "text_font_import", "text_fonts"] -evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools", "crates/edit/src/tests.rs::a_line_whose_operator_starts_the_next_stream_can_be_edited", "crates/automation/tests/automation.rs::a_line_split_across_content_streams_is_listed_and_edited", "crates/ui-egui/tests/editing.rs::selected_text_formatting_keeps_the_rest_of_the_paragraph_and_reopens", "crates/edit/src/tests.rs::marked_paragraph_boundaries_and_cross_stream_transforms_survive_rewrite", "crates/automation/tests/automation.rs::explicit_font_and_disjoint_ranges_save_reopen_through_tools", "crates/edit/src/tests.rs::disjoint_style_ranges_preserve_unselected_runs_and_validate_utf8_atomically", "crates/engine/tests/document_fonts.rs::opentype_cff_document_font_preserves_program_codes_and_rendering_after_reopen", "crates/ui-egui/tests/editing.rs::cropped_images_do_not_intercept_text_and_stay_inside_each_page", "crates/automation/tests/automation.rs::clipped_image_bounds_and_text_edit_save_reopen_through_tools", "crates/edit/src/tests.rs::mixed_styles_and_matrix_sizes_form_visual_regions_without_merging_columns", "crates/edit/src/tests.rs::rich_region_edits_preserve_styles_positions_and_delete_all_runs_after_reopen", "crates/ui-egui/tests/editing.rs::one_editor_contains_mixed_styles_and_visual_rows", "crates/ui-egui/tests/editing.rs::inline_editor_preserves_source_font_and_previews_live_bounds", "crates/fonts/src/pdf.rs::mac_subset_cmap_uses_pdf_unicode_without_changing_source_program", "crates/automation/tests/automation.rs::mixed_visual_region_edit_preview_save_reopen_and_undo_through_tools"] -notes = "click a paragraph box and retype it in place (font reused or Helvetica substituted, colour and spacing kept, rewrapped); a page's /Contents pieces are parsed joined, so a line whose operands and operator sit in different pieces is listed and edited (lines and paragraphs still don't join across pieces; Edit image still parses each piece alone); drag a box to move it or its right-edge handle to rewrap it (edit.text-move-resize); per-character formatting changes are next Since #1116: mixed-style visual regions retain source rows, subset encodings, colour and baseline anchors in one textless-background editor. Explicit paragraph/heading scopes constrain grouping; compatible graphics planes permit cross-stream regions. Full matching document fonts complete absent subset glyphs; installed/file selection, TrueType/OpenType-CFF embedding and disjoint UTF-8 range styles have save/reopen coverage. Single-face OpenType shaping/bidi retains logical text through ToUnicode/ActualText. Legacy non-SFNT preview, mixed-font/style contextual runs, full structure-tree preservation and author intent in untagged PDFs remain limited; see docs/editing-existing-text.md" +evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools", "crates/edit/src/tests.rs::a_line_whose_operator_starts_the_next_stream_can_be_edited", "crates/automation/tests/automation.rs::a_line_split_across_content_streams_is_listed_and_edited", "crates/ui-egui/tests/editing.rs::selected_text_formatting_keeps_the_rest_of_the_paragraph_and_reopens", "crates/edit/src/tests.rs::marked_paragraph_boundaries_and_cross_stream_transforms_survive_rewrite", "crates/automation/tests/automation.rs::explicit_font_and_disjoint_ranges_save_reopen_through_tools", "crates/edit/src/tests.rs::disjoint_style_ranges_preserve_unselected_runs_and_validate_utf8_atomically", "crates/engine/tests/document_fonts.rs::opentype_cff_document_font_preserves_program_codes_and_rendering_after_reopen", "crates/ui-egui/tests/editing.rs::cropped_images_do_not_intercept_text_and_stay_inside_each_page", "crates/automation/tests/automation.rs::clipped_image_bounds_and_text_edit_save_reopen_through_tools", "crates/edit/src/tests.rs::mixed_styles_and_matrix_sizes_form_visual_regions_without_merging_columns", "crates/edit/src/tests.rs::rich_region_edits_preserve_styles_positions_and_delete_all_runs_after_reopen", "crates/ui-egui/tests/editing.rs::one_editor_contains_mixed_styles_and_visual_rows", "crates/ui-egui/tests/editing.rs::inline_editor_preserves_source_font_and_previews_live_bounds", "crates/fonts/src/pdf.rs::mac_subset_cmap_uses_pdf_unicode_without_changing_source_program", "crates/automation/tests/automation.rs::mixed_visual_region_edit_preview_save_reopen_and_undo_through_tools", "crates/ui-egui/tests/ui.rs::the_edit_tab_turns_on_edit_text_and_images"] +notes = "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); click a paragraph box and retype it in place (font reused or Helvetica substituted, colour and spacing kept, rewrapped); a page's /Contents pieces are parsed joined, so a line whose operands and operator sit in different pieces is listed and edited (lines and paragraphs still don't join across pieces; Edit image still parses each piece alone); drag a box to move it or its right-edge handle to rewrap it (edit.text-move-resize); per-character formatting changes are next Since #1116: mixed-style visual regions retain source rows, subset encodings, colour and baseline anchors in one textless-background editor. Explicit paragraph/heading scopes constrain grouping; compatible graphics planes permit cross-stream regions. Full matching document fonts complete absent subset glyphs; installed/file selection, TrueType/OpenType-CFF embedding and disjoint UTF-8 range styles have save/reopen coverage. Single-face OpenType shaping/bidi retains logical text through ToUnicode/ActualText. Legacy non-SFNT preview, mixed-font/style contextual runs, full structure-tree preservation and author intent in untagged PDFs remain limited; see docs/editing-existing-text.md" [[feature]] id = "edit.text-reflow"