Keep the font size when making text box properties default (#5784)

This commit is contained in:
NetterSilver authored and GitHub committed 2026-11-09 08:39:16 +08:00
1 parent 607f79e5e0
commit 67a6037be1
5 files changed
+125 -6

No files matched your search

+16
View File
@@ -127,6 +127,12 @@ impl PdfCraftApp {
"border_shape": s.border_shape.name(),
}))
.collect::<Vec<_>>(),
// Default font sizes of new text boxes and callouts (#5767).
"comment_font_sizes": self
.comment_prefs
.font_sizes()
.map(|(t, size)| serde_json::json!({ "tool": t.command(), "font_size": size }))
.collect::<Vec<_>>(),
// Drawn signatures keep their original form (older settings read the same).
"signature": match &self.signature { Some(fill_sign::SavedSig::Drawn(s)) => Some(s), _ => None },
"signature_text": match &self.signature { Some(fill_sign::SavedSig::Typed(t)) => Some(t), _ => None },
@@ -420,6 +426,16 @@ impl PdfCraftApp {
}
}
}
// Default font sizes (#5767), clamped by the setter; a missing or malformed size keeps
// the tool's own.
if let Some(sizes) = v["comment_font_sizes"].as_array() {
for e in sizes {
let Some(tool) = e["tool"].as_str().and_then(crate::comments::CommentTool::from_command) else { continue };
if let Some(size) = e["font_size"].as_f64() {
self.comment_prefs.set_font_size(tool, size);
}
}
}
}
/// Saved signatures, stamps, actions and trusted certificates.
+4 -1
View File
@@ -97,7 +97,7 @@ impl PdfCraftApp {
}
/// New comments of the kind at `(page, index)` take the look in `p`: its colour, opacity,
/// line width and (for shapes) fill. `p` may be a Comment Properties draft not applied yet.
/// line width, (for shapes) fill and (for text boxes and callouts) font size. `p` may be a Comment Properties draft not applied yet.
pub fn make_props_default(&mut self, page: usize, index: usize, p: &CommentProps) {
let Some((_, id)) = self.active_ids() else { return };
let Some(doc) = self.session.get(id) else { return };
@@ -143,6 +143,9 @@ impl PdfCraftApp {
if let Some(w) = p.width {
self.comment_prefs.set_width(tool, w);
}
if let Some(size) = p.font_size {
self.comment_prefs.set_font_size(tool, size);
}
self.notify_fmt("New {tool} comments will look like this one", &[("tool", &crate::i18n::in_sentence(tl!(tool.label())))]);
}
+34
View File
@@ -56,6 +56,9 @@ const SELECT_BLUE: Color32 = Color32::from_rgb(0x14, 0x73, 0xE6);
/// Maximum stroke thickness offered by commenting controls, in PDF points.
pub const MAX_LINE_WIDTH: f64 = 100.0;
/// The font sizes a text box or callout default may take (Comment Properties' range).
const MIN_FONT_SIZE: f64 = 1.0;
const MAX_FONT_SIZE: f64 = 400.0;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CommentTool {
@@ -258,6 +261,14 @@ impl CommentTool {
matches!(self, Self::Rectangle | Self::Oval | Self::Polygon | Self::Cloud | Self::TextBox | Self::Callout)
}
/// The font size new comments of this kind start with (`None`: no text).
fn default_font_size(self) -> Option<f64> {
match self.sample() {
Shape::TextBox { font_size, .. } | Shape::Callout { font_size, .. } => Some(font_size),
_ => None,
}
}
/// A placeholder shape of this kind (for per-tool default styles).
fn sample(self) -> Shape {
match self {
@@ -309,6 +320,8 @@ pub struct CommentPrefs {
/// `/T` of new comments (Acrobat: Preferences ▸ Identity, default the login name).
pub author: String,
styles: Vec<(CommentTool, Style)>,
/// The font size of new text boxes and callouts (Make Properties Default).
font_sizes: Vec<(CommentTool, f64)>,
/// Keep the tool selected after use (the pin).
pub pinned: bool,
/// The tool each flyout group shows (the last one used).
@@ -322,6 +335,7 @@ impl Default for CommentPrefs {
Self {
author: login_name(),
styles: ALL.iter().map(|t| (*t, Style::default_for(&t.sample()))).collect(),
font_sizes: ALL.iter().filter_map(|t| t.default_font_size().map(|s| (*t, s))).collect(),
pinned: false,
group_tool: [CommentTool::Note, CommentTool::Highlight, CommentTool::Ink],
hide_empty_highlight_hover: false,
@@ -390,6 +404,26 @@ impl CommentPrefs {
}
}
/// The font size of new comments of `tool` (`None`: the tool has no text).
pub fn font_size(&self, tool: CommentTool) -> Option<f64> {
self.font_sizes.iter().find(|(t, _)| *t == tool).map(|(_, s)| *s)
}
/// Every text tool with its default font size (the persisted settings read this).
pub fn font_sizes(&self) -> impl Iterator<Item = (CommentTool, f64)> + '_ {
self.font_sizes.iter().copied()
}
/// Clamp and store the font size (1–400 pt, as Comment Properties allows) for a text tool;
/// a non-finite size keeps the current one.
pub fn set_font_size(&mut self, tool: CommentTool, size: f64) {
if let Some((_, s)) = self.font_sizes.iter_mut().find(|(t, _)| *t == tool)
&& size.is_finite()
{
*s = size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE);
}
}
pub fn set_style(&mut self, tool: CommentTool, style: Style) {
if let Some((_, s)) = self.styles.iter_mut().find(|(t, _)| *t == tool) {
*s = style;
@@ -125,16 +125,18 @@ pub(crate) fn composer(ctx: &egui::Context, view: &mut DocView, info: &DocInfo,
view.comments.tool_done = true;
let rotation = page_rotation(info, page);
let border = prefs.style(CommentTool::TextBox).width;
let font_size = prefs.font_size(CommentTool::TextBox).unwrap_or(12.0);
let rect = match (size, info.pages.get(page)) {
(Some(size), Some(p)) => drawn_text_box_rect(p, c.at, size, &text, 12.0, border),
_ => text_box_rect(c.at, &text, 12.0, rotation),
(Some(size), Some(p)) => drawn_text_box_rect(p, c.at, size, &text, font_size, border),
_ => text_box_rect(c.at, &text, font_size, rotation),
};
Some(new_comment(&cx, CommentTool::TextBox, Shape::TextBox { rect, font_size: 12.0 }, text))
Some(new_comment(&cx, CommentTool::TextBox, Shape::TextBox { rect, font_size }, text))
}
ComposerKind::Callout { point } => {
view.comments.tool_done = true;
// A callout's leader and box stay in user-space axes (its appearance isn't turned).
let rect = text_box_rect(c.at, &text, 10.0, 0);
let font_size = prefs.font_size(CommentTool::Callout).unwrap_or(10.0);
let rect = text_box_rect(c.at, &text, font_size, 0);
let side = if point[0] < rect[0] {
rect[0]
} else if point[0] > rect[2] {
@@ -143,7 +145,7 @@ pub(crate) fn composer(ctx: &egui::Context, view: &mut DocView, info: &DocInfo,
(rect[0] + rect[2]) / 2.0
};
let knee = [(point[0] + side) / 2.0, (rect[1] + rect[3]) / 2.0];
Some(new_comment(&cx, CommentTool::Callout, Shape::Callout { rect, knee, point, font_size: 10.0, ending: LineEnding::OpenArrow }, text))
Some(new_comment(&cx, CommentTool::Callout, Shape::Callout { rect, knee, point, font_size, ending: LineEnding::OpenArrow }, text))
}
ComposerKind::Replace => {
view.comments.tool_done = true;
+64
View File
@@ -4355,3 +4355,67 @@ fn escape_with_a_fill_tool_deselects_every_comment_then_drops_the_tool() {
h.run_steps(2);
assert_eq!(h.state().quick_tool, QuickTool::Select);
}
/// #5767: Make Properties Default on a text box or callout keeps its font size too, so the next
/// one (also after a restart) takes it, along with the colour.
#[test]
fn properties_dialog_makes_the_text_box_font_size_default() {
let blue = [0.0, 0.47, 0.84];
let mut h = harness(|_| {});
let font_size_of_last = |h: &Harness<'static, PdfCraftApp>| {
let s = h.state();
let doc = s.session.get(s.views[0].id).unwrap();
let last = doc.info.annotations.len() - 1;
doc.comment_props(0, last).unwrap()
};
for (quick, size, first, second, third) in
[("freetext", 24.0, (40.0, 60.0), (40.0, 120.0), (40.0, 100.0)), ("callout", 18.0, (150.0, 40.0), (150.0, 100.0), (150.0, 60.0))]
{
h.state_mut().set_option("quick", quick).unwrap();
if quick == "callout" {
drag_pt(&mut h, first, (first.0 + 60.0, first.1 + 20.0));
} else {
click_pt(&mut h, first);
}
post_text(&mut h, "First");
let index = comments(&h).len() - 1;
h.state_mut().open_comment_props(0, index);
h.run_steps(2);
{
let d = h.state_mut().comment_props.as_mut().expect("open");
d.edited.font_size = Some(size);
d.edited.color = Some(blue);
}
h.get_by_label("Make Properties Default").click();
h.run_steps(1);
h.get_by_label("OK").click();
h.run_steps(3);
assert_eq!(font_size_of_last(&h).font_size, Some(size));
// The next one takes the font size and the colour.
h.state_mut().set_option("quick", quick).unwrap();
if quick == "callout" {
drag_pt(&mut h, second, (second.0 + 60.0, second.1 + 20.0));
} else {
click_pt(&mut h, second);
}
post_text(&mut h, "Second");
let p = font_size_of_last(&h);
assert_eq!(p.font_size, Some(size), "{quick}: the next one takes the default font size");
assert_eq!(p.color, Some(blue), "{quick}: and the default colour");
// The default survives a restart.
let json = h.state().persist();
let mut fresh = harness(move |app| {
app.restore(&json);
app.set_option("quick", quick).unwrap();
});
if quick == "callout" {
drag_pt(&mut fresh, third, (third.0 + 60.0, third.1 + 20.0));
} else {
click_pt(&mut fresh, third);
}
post_text(&mut fresh, "Third");
assert_eq!(font_size_of_last(&fresh).font_size, Some(size), "{quick}: the default font size persisted");
}
}