diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md
index c70d84e624..e8fa4adac6 100644
--- a/ATTRIBUTION.md
+++ b/ATTRIBUTION.md
@@ -4,7 +4,7 @@
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) ยง1. The machine-readable list, with the SHA-256 of every asset, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
-## In this repository (256)
+## In this repository (257)
| Asset | Title | Author | Licence | Source | Used for |
|---|---|---|---|---|---|
@@ -263,6 +263,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
| `assets/icons/superscript.svg` | Lucide icon "superscript" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/superscript.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/icons/subscript.svg` | Lucide icon "subscript" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/subscript.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/icons/pilcrow.svg` | Lucide icon "pilcrow" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pilcrow.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
+| `assets/icons/a-large-small.svg` | Lucide icon "a-large-small" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/a-large-small.svg) | UI icon, embedded via crates/ui-egui/src/widgets/icon_data.rs |
| `assets/icons/move-horizontal.svg` | Lucide icon "move-horizontal" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/move-horizontal.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
## Compiled in through dependencies (6)
diff --git a/ATTRIBUTION.toml b/ATTRIBUTION.toml
index 9638749929..f998b6fc3d 100644
--- a/ATTRIBUTION.toml
+++ b/ATTRIBUTION.toml
@@ -3200,6 +3200,17 @@ kind = "icon"
usage = "UI icon, embedded via crates/ui-egui/src/icon_data.rs"
sha256 = "d344d9f9222f6c1b698c0c82a753a2212d9bd7785f06c00cd6e4910fe2fee8f8"
+[[asset]]
+path = "assets/icons/a-large-small.svg"
+title = "Lucide icon \"a-large-small\""
+author = "Lucide Contributors (portions Cole Bemis, Feather)"
+source = "https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/a-large-small.svg)"
+licence = "ISC AND MIT"
+licence_file = "assets/icons/LICENSE-lucide.txt"
+kind = "icon"
+usage = "UI icon, embedded via crates/ui-egui/src/widgets/icon_data.rs"
+sha256 = "d4985fc71174d108e322c3b25cb6abe2837a115fe3f239a1918645db436743ec"
+
[[asset]]
path = "assets/icons/move-horizontal.svg"
title = "Lucide icon \"move-horizontal\""
diff --git a/assets/icons/a-large-small.svg b/assets/icons/a-large-small.svg
new file mode 100644
index 0000000000..7716db3cf2
--- /dev/null
+++ b/assets/icons/a-large-small.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/crates/ui-egui/src/canvas/bars.rs b/crates/ui-egui/src/canvas/bars.rs
index dd3fe8c498..fbd4200217 100644
--- a/crates/ui-egui/src/canvas/bars.rs
+++ b/crates/ui-egui/src/canvas/bars.rs
@@ -315,7 +315,7 @@ mod tests {
#[test]
fn a_wider_quick_bar_centres_by_its_own_width_and_needs_a_wider_margin() {
- // Fill & Sign's Text tool widens the bar to 70 pt: it centres by that, and 72 pt no longer holds it.
+ // A 70 pt bar centres by that, and 72 pt no longer holds it.
assert_eq!(quick_bar_offset(120.0, 70.0), 15.0);
assert_eq!(quick_bar_offset(72.0, 70.0), 14.0);
}
diff --git a/crates/ui-egui/src/hotbar.rs b/crates/ui-egui/src/hotbar.rs
index fce1ac39ce..269f0a12c2 100644
--- a/crates/ui-egui/src/hotbar.rs
+++ b/crates/ui-egui/src/hotbar.rs
@@ -299,6 +299,30 @@ fn core_tool_buttons(ui: &mut egui::Ui, app: &mut PdfCraftApp, cell: f32, popup_
});
}
+fn controls_divider(ui: &mut egui::Ui, t: &Tokens, vertical: bool) {
+ let (r, _) = if vertical {
+ ui.allocate_exact_size(vec2(crate::canvas::bars::QUICK_BAR_WIDTH, 9.0), Sense::hover())
+ } else {
+ ui.allocate_exact_size(vec2(9.0, crate::canvas::bars::QUICK_BAR_WIDTH), Sense::hover())
+ };
+ if vertical {
+ ui.painter().hline(r.x_range().shrink(6.0), r.center().y, Stroke::new(1.0, t.divider));
+ } else {
+ ui.painter().vline(r.center().x, r.y_range().shrink(6.0), Stroke::new(1.0, t.divider));
+ }
+}
+
+fn selected_fill_text(app: &PdfCraftApp) -> Option<(usize, usize)> {
+ let (index, id) = app.active_ids()?;
+ let (page, annotation) = app.views.get(index)?.comments.selected?;
+ let doc = app.session.get(id).filter(|doc| doc.allows_annotation())?;
+ doc.info
+ .annotations
+ .iter()
+ .any(|a| a.page == page && a.index == annotation && a.fill_sign && a.subtype == "FreeText" && !a.locked)
+ .then_some((page, annotation))
+}
+
/// Extra controls rendered below/beside the buttons when a comment or fill tool is active.
fn active_tool_controls(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, vertical: bool) {
let current_fill = match app.quick_tool {
@@ -306,47 +330,44 @@ fn active_tool_controls(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, ve
_ => None,
};
if matches!(current_fill, Some(crate::fill_sign::FillTool::Text | crate::fill_sign::FillTool::Date)) {
- if vertical {
- ui.label(tl!("Font size:"));
- }
- let size = ui.add(
- egui::DragValue::new(&mut app.fill_text_size)
- .range(crate::fill_sign::MIN_TEXT_SIZE..=crate::fill_sign::MAX_TEXT_SIZE)
- .speed(0.25)
- .suffix(" pt"),
- );
- if ((size.changed() && !size.dragged()) || size.drag_stopped())
- && let Some((index, id)) = app.active_ids()
- && let Some((page, annotation)) = app.views.get(index).and_then(|view| view.comments.selected)
- && app.session.get(id).is_some_and(|doc| {
- doc.allows_annotation()
- && doc
- .info
- .annotations
- .iter()
- .any(|a| a.page == page && a.index == annotation && a.fill_sign && a.subtype == "FreeText" && !a.locked)
- })
- {
- app.apply_edit(Edit::FontSizeAnnotation { page, index: annotation, size: app.fill_text_size });
- }
- ui.menu_button(tl!("Colour"), |ui| {
- if let Some(color) = comments::swatch_grid(ui, Some(app.fill_text_color)) {
- app.fill_text_color = color;
- ui.close();
+ controls_divider(ui, t, vertical);
+ if let Some(color) = comments::quick_bar_colour(ui, app.fill_text_color) {
+ app.fill_text_color = color;
+ if let Some((page, index)) = selected_fill_text(app) {
+ app.apply_edit(Edit::StyleAnnotation { page, index, color: Some(color), opacity: None, width: None, endings: None });
}
- });
+ }
+ let resp = icons::button(ui, "a-large-small", 32.0, false, tl!("Font size"));
+ let resized = egui::Popup::menu(&resp)
+ .align(egui::RectAlign::RIGHT_START)
+ .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
+ .show(|ui| {
+ ui.set_min_width(180.0);
+ ui.label(egui::RichText::new(tl!("Font size")).font(theme::semibold(12.0)));
+ let size = ui.add(
+ egui::Slider::new(&mut app.fill_text_size, crate::fill_sign::MIN_TEXT_SIZE..=crate::fill_sign::MAX_TEXT_SIZE)
+ .logarithmic(true)
+ .suffix(" pt"),
+ );
+ // #1274: the size also applies to the Fill & Sign text that is selected,
+ // once per change (a typed value, or the end of a drag), so a drag adds
+ // one undo step and not one per frame.
+ (size.changed() && !size.dragged()) || size.drag_stopped()
+ })
+ .is_some_and(|popup| popup.inner);
+ let size = app.fill_text_size;
+ if resized
+ && let Some((page, index)) = selected_fill_text(app)
+ && app
+ .active_ids()
+ .and_then(|(_, id)| app.session.get(id)?.comment_props(page, index)?.font_size)
+ .is_none_or(|stored| format!("{stored:.3}") != format!("{size:.3}"))
+ {
+ app.apply_edit(Edit::FontSizeAnnotation { page, index, size });
+ }
}
if let QuickTool::Comment(tool) = app.quick_tool {
- let (r, _) = if vertical {
- ui.allocate_exact_size(vec2(crate::canvas::bars::QUICK_BAR_WIDTH, 9.0), Sense::hover())
- } else {
- ui.allocate_exact_size(vec2(9.0, crate::canvas::bars::QUICK_BAR_WIDTH), Sense::hover())
- };
- if vertical {
- ui.painter().hline(r.x_range().shrink(6.0), r.center().y, Stroke::new(1.0, t.divider));
- } else {
- ui.painter().vline(r.center().x, r.y_range().shrink(6.0), Stroke::new(1.0, t.divider));
- }
+ controls_divider(ui, t, vertical);
if let Some(color) = comments::quick_bar_controls(ui, tool, &mut app.comment_prefs)
&& let Some((index, id)) = app.active_ids()
&& let Some((page, annotation)) = app.views.get(index).and_then(|view| view.comments.selected)
diff --git a/crates/ui-egui/src/tools/comments.rs b/crates/ui-egui/src/tools/comments.rs
index 8d118ac3c9..5d945136e7 100644
--- a/crates/ui-egui/src/tools/comments.rs
+++ b/crates/ui-egui/src/tools/comments.rs
@@ -41,7 +41,7 @@ pub(crate) use input::{anchor, context, context_hit, describe, hit, keys, moving
pub use menu::{CanvasAction, fill_picker, recent_custom_colours, remember_custom_colour, status_badge, swatch_grid};
#[cfg(test)]
use menu::{RECENT_CUSTOM, same_colour};
-pub(crate) use menu::{attachment_items, context_menu, quick_bar_controls};
+pub(crate) use menu::{attachment_items, context_menu, quick_bar_colour, quick_bar_controls};
pub(crate) use paint::paint_page;
#[cfg(test)]
use paint::{changing_points, drawn_shape, edited_in_view};
diff --git a/crates/ui-egui/src/tools/comments/menu.rs b/crates/ui-egui/src/tools/comments/menu.rs
index b577fbbd29..0e0da5caf3 100644
--- a/crates/ui-egui/src/tools/comments/menu.rs
+++ b/crates/ui-egui/src/tools/comments/menu.rs
@@ -286,25 +286,32 @@ pub fn fill_picker(ui: &mut egui::Ui, current: Option) -> Option