fix(automation): content_update refuses text styling on an image (#1331) (#1391)

font, size, bold, italic, color and align were never read for an added
image, so content_update reported success, wrote the same image back,
marked the document dirty and added an "Edit content" undo step. They
are now refused with an error, as rotate, flip, crop and image already
are on text, before anything is applied.

Adds automation::text_styling_on_an_image_is_refused_without_an_edit,
which fails before this change.
This commit is contained in:
Marco Arnulfo authored and GitHub committed 2026-10-12 07:19:06 -04:00
1 parent b6a1402bf3
commit 0584425e67
2 files changed
+50

No files matched your search

+3
View File
@@ -164,6 +164,9 @@ impl Automation {
if a.get("text").is_some() {
return Err(bad("images have no text"));
}
if ["font", "size", "bold", "italic", "color", "align"].iter().any(|k| a.get(k).is_some()) {
return Err(bad("font, size, bold, italic, color and align apply to text"));
}
if let Some(deg) = a.opt_int("rotate")? {
if deg.rem_euclid(90) != 0 {
return Err(bad("rotate must be a multiple of 90"));
+47
View File
@@ -3020,6 +3020,53 @@ fn adding_content_through_tools() {
assert!(matches!(a.call("content_delete", &json!({ "doc": doc, "page": 1, "index": 5 })), Err(ToolError::Failed(_))));
}
/// Text styling on an added image is refused (#1331), as image transforms are on text, and the
/// refusal leaves the content, the dirty flag and the undo and redo steps as they were.
#[test]
fn text_styling_on_an_image_is_refused_without_an_edit() {
let dir = workdir("content-style");
let mut png = Vec::new();
{
let mut enc = png::Encoder::new(&mut png, 1, 1);
enc.set_color(png::ColorType::Rgb);
enc.write_header().unwrap().write_image_data(&[80, 160, 200]).unwrap();
}
std::fs::write(dir.join("logo.png"), png).unwrap();
let mut a = auto(&dir);
let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap();
ok(&mut a, "page_add_text", json!({ "doc": doc, "page": 1, "text": "sample", "at": [20, 20] }));
ok(&mut a, "page_add_image", json!({ "doc": doc, "page": 1, "path": "logo.png", "rect": [10, 100, 40, 130] }));
ok(&mut a, "content_update", json!({ "doc": doc, "page": 1, "index": 2, "rotate": 90 }));
ok(&mut a, "edit_undo", json!({ "doc": doc }));
ok(&mut a, "doc_save", json!({ "doc": doc, "path": "saved.pdf" }));
let state = |a: &Automation| {
let d = a.session().docs().iter().find(|d| d.id.0 == doc).unwrap();
(format!("{:?}", d.added), d.dirty, d.edit_generation(), d.can_undo().map(str::to_owned), d.can_redo().map(str::to_owned))
};
let before = state(&a);
assert!(!before.1 && before.4.is_some(), "a clean document with a step to redo: {before:?}");
for (key, value) in [
("font", json!("times")),
("size", json!(24)),
("bold", json!(true)),
("italic", json!(true)),
("color", json!("red")),
("align", json!("center")),
] {
for rect in [None, Some(json!([20, 100, 50, 130]))] {
let mut args = json!({ "doc": doc, "page": 1, "index": 2 });
args[key] = value.clone();
if let Some(r) = rect {
args["rect"] = r;
}
assert!(matches!(a.call("content_update", &args), Err(ToolError::InvalidArgs(_))), "{args}");
assert_eq!(state(&a), before, "{args}");
}
}
ok(&mut a, "content_update", json!({ "doc": doc, "page": 1, "index": 1, "font": "times", "size": 24, "align": "center" }));
assert!(state(&a).1, "text still takes its styling");
}
#[test]
fn form_formats_and_calculations_through_tools() {
let dir = workdir("formcalc");