mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 16:36:13 -06:00
Group annotation, form and document operation parameters
This commit is contained in:
@@ -30,7 +30,7 @@ let index = add_annotation(&mut doc, &NewAnnotation { page, shape, style, conten
|
||||
add_reply(&mut doc, page, index, "Agreed", "Ada", &meta)?;
|
||||
set_review_state(&mut doc, page, index, ReviewState::Accepted, "Ada", &meta)?;
|
||||
move_annotation(&mut doc, page, index, dx, dy, &meta)?;
|
||||
set_style(&mut doc, page, index, Some(rgb), Some(0.5), Some(2.0), &meta)?;
|
||||
set_style(&mut doc, page, index, StyleChange::new(Some(rgb), Some(0.5), Some(2.0), None), &meta)?;
|
||||
delete_annotation(&mut doc, page, index)?;
|
||||
signature_image(&doc, page, index)?; // embedded image XObject of Fill & Sign signature/initials
|
||||
```
|
||||
|
||||
@@ -41,7 +41,7 @@ pub use modify::{
|
||||
};
|
||||
pub use types::{
|
||||
AttachIcon, FillMark, LineEnding, Markup, Meta, NOTE_SIZE, NewAnnotation, NoteIcon, OverlayFont, OverlayLook, ReviewState, Rgb, Shape,
|
||||
StampGroup, StampKind, Style, rect_quad,
|
||||
StampGroup, StampKind, Style, StyleChange, TextReplacement, rect_quad,
|
||||
};
|
||||
|
||||
pub mod appearance;
|
||||
|
||||
@@ -6,6 +6,7 @@ use super::info::{line_endings_of, text_value};
|
||||
use super::lookup::{annot_dict, annot_ref, annotation_flags, annots, page_ref, set_annots};
|
||||
use super::types::{
|
||||
FLAG_HIDDEN, FLAG_LOCKED, FLAG_NO_ROTATE, FLAG_NO_ZOOM, FLAG_PRINT, LineEnding, Markup, Meta, NewAnnotation, ReviewState, Rgb, Shape, Style,
|
||||
StyleChange, TextReplacement,
|
||||
};
|
||||
use super::{AnnotError, Dict, Document, HashMap, HashSet, ObjRef, Object, PdfString, appearance};
|
||||
|
||||
@@ -499,17 +500,8 @@ fn border_width_of(d: &Dict) -> f64 {
|
||||
}
|
||||
|
||||
/// Change a comment's colour, opacity, line width and/or line endings, and redraw it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn set_style(
|
||||
doc: &mut Document,
|
||||
page: usize,
|
||||
index: usize,
|
||||
color: Option<Rgb>,
|
||||
opacity: Option<f64>,
|
||||
width: Option<f64>,
|
||||
endings: Option<&[LineEnding]>,
|
||||
meta: &Meta,
|
||||
) -> Result<(), AnnotError> {
|
||||
pub fn set_style(doc: &mut Document, page: usize, index: usize, change: StyleChange, meta: &Meta) -> Result<(), AnnotError> {
|
||||
let StyleChange { color, opacity, width, endings } = change;
|
||||
let (_, r) = annot_ref(doc, page, index)?;
|
||||
unlocked(doc, r)?;
|
||||
let d = annot_dict(doc, r);
|
||||
@@ -521,7 +513,7 @@ pub fn set_style(
|
||||
if color.is_some_and(|c| !finite(&c)) || opacity.is_some_and(|o| !o.is_finite()) || width.is_some_and(|w| !w.is_finite()) {
|
||||
return Err(AnnotError::Invalid("invalid style".into()));
|
||||
}
|
||||
if let Some(ends) = endings {
|
||||
if let Some(ends) = &endings {
|
||||
let callout = subtype == "FreeText" && d.contains(b"CL");
|
||||
let pair = matches!(subtype.as_str(), "Line" | "PolyLine");
|
||||
if (callout && ends.len() != 1) || (pair && ends.len() != 2) || (!callout && !pair) {
|
||||
@@ -691,18 +683,8 @@ fn pair_points(d: &Dict, key: &[u8]) -> Option<Vec<[f64; 2]>> {
|
||||
/// to, so one id for both writes `inreplyto` equal to the caret's own `name`, and a reader that
|
||||
/// resolves names (Acrobat, and our own importer) loses the pair. `caret_meta` is a second id
|
||||
/// from the caller, so the edit stays deterministic.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn add_text_replacement(
|
||||
doc: &mut Document,
|
||||
page: usize,
|
||||
quads: &[[f64; 8]],
|
||||
replacement: &str,
|
||||
author: &str,
|
||||
strike: &Style,
|
||||
caret: &Style,
|
||||
meta: &Meta,
|
||||
caret_meta: &Meta,
|
||||
) -> Result<usize, AnnotError> {
|
||||
pub fn add_text_replacement(doc: &mut Document, replacement: TextReplacement<'_>) -> Result<usize, AnnotError> {
|
||||
let TextReplacement { page, quads, replacement, author, strike, caret, strike_meta: meta, caret_meta } = replacement;
|
||||
let last = *quads.last().ok_or_else(|| AnnotError::Invalid("select the text to replace first".into()))?;
|
||||
let s = add_annotation(
|
||||
doc,
|
||||
|
||||
+45
-14
@@ -303,7 +303,7 @@ fn move_shifts_all_geometry_and_the_popup() {
|
||||
fn restyle_and_resize_redraw_the_appearance() {
|
||||
let mut doc = fixture();
|
||||
let r = add_annotation(&mut doc, &new(0, Shape::Rectangle { rect: [10.0, 10.0, 110.0, 60.0] }), &meta("r")).unwrap();
|
||||
set_style(&mut doc, 0, r, Some([1.0, 0.0, 0.0]), Some(0.5), Some(4.0), None, &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, r, StyleChange::new(Some([1.0, 0.0, 0.0]), Some(0.5), Some(4.0), None), &meta("")).unwrap();
|
||||
set_rect(&mut doc, 0, r, [0.0, 0.0, 200.0, 100.0], &meta("")).unwrap();
|
||||
let doc2 = reopen(&doc);
|
||||
let d = &list(&doc2, 0)[r];
|
||||
@@ -313,7 +313,10 @@ fn restyle_and_resize_redraw_the_appearance() {
|
||||
assert_eq!(d.get(b"CA").and_then(|o| o.as_f64()), Some(0.5));
|
||||
// A stamp's appearance can't be regenerated, so restyling it changes nothing.
|
||||
let before = list(&doc, 1)[0].clone();
|
||||
assert_eq!(set_style(&mut doc, 1, 0, Some([0.0; 3]), None, None, None, &meta("")), Err(AnnotError::Unsupported("Stamp".into())));
|
||||
assert_eq!(
|
||||
set_style(&mut doc, 1, 0, StyleChange::new(Some([0.0; 3]), None, None, None), &meta("")),
|
||||
Err(AnnotError::Unsupported("Stamp".into()))
|
||||
);
|
||||
assert_eq!(list(&doc, 1)[0], before);
|
||||
// Lines can't be resized as rectangles.
|
||||
let l = add_annotation(
|
||||
@@ -374,7 +377,7 @@ fn text_box_text_and_colour_changes_are_drawn() {
|
||||
let mut doc = fixture();
|
||||
let t = add_annotation(&mut doc, &new(0, Shape::TextBox { rect: [0.0, 0.0, 300.0, 100.0], font_size: 12.0 }), &meta("t")).unwrap();
|
||||
set_contents(&mut doc, 0, t, "Caf\u{e9} (draft) \u{2014} 100%", &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, t, Some([1.0, 0.0, 0.0]), None, None, None, &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, t, StyleChange::new(Some([1.0, 0.0, 0.0]), None, None, None), &meta("")).unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let d = &list(&doc, 0)[t];
|
||||
assert_eq!(text(d, b"Contents"), "Caf\u{e9} (draft) \u{2014} 100%");
|
||||
@@ -556,8 +559,8 @@ fn fill_and_sign_items_are_drawn() {
|
||||
assert_eq!(text(&all[s], b"Subj"), "Signature");
|
||||
// Other stamps still can't be restyled; ours can.
|
||||
let mut doc = doc;
|
||||
set_style(&mut doc, 1, marks[0], Some([0.0, 0.0, 1.0]), None, None, None, &meta("")).unwrap();
|
||||
assert!(set_style(&mut doc, 1, 0, Some([0.0; 3]), None, None, None, &meta("")).is_err());
|
||||
set_style(&mut doc, 1, marks[0], StyleChange::new(Some([0.0, 0.0, 1.0]), None, None, None), &meta("")).unwrap();
|
||||
assert!(set_style(&mut doc, 1, 0, StyleChange::new(Some([0.0; 3]), None, None, None), &meta("")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -844,7 +847,20 @@ fn replace_text_groups_a_strikeout_and_a_caret() {
|
||||
let quads = [[100.0, 720.0, 200.0, 720.0, 100.0, 706.0, 200.0, 706.0]];
|
||||
let red = Style { color: [0.89, 0.13, 0.13], opacity: 1.0, width: 1.0, fill: None };
|
||||
let blue = Style { color: [0.0, 0.47, 0.84], opacity: 1.0, width: 1.0, fill: None };
|
||||
let i = add_text_replacement(&mut doc, 0, &quads, "new words", "Ada", &red, &blue, &meta("r"), &meta("r-caret")).unwrap();
|
||||
let i = add_text_replacement(
|
||||
&mut doc,
|
||||
TextReplacement {
|
||||
page: 0,
|
||||
quads: &quads,
|
||||
replacement: "new words",
|
||||
author: "Ada",
|
||||
strike: &red,
|
||||
caret: &blue,
|
||||
strike_meta: &meta("r"),
|
||||
caret_meta: &meta("r-caret"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let a = list(&doc, 0);
|
||||
let strike = &a[i];
|
||||
@@ -866,7 +882,22 @@ fn replace_text_groups_a_strikeout_and_a_caret() {
|
||||
let mut doc = doc;
|
||||
delete_annotation(&mut doc, 0, i).unwrap();
|
||||
assert!(!list(&doc, 0).iter().any(|d| d.name(b"Subtype") == Some(b"Caret")));
|
||||
assert!(add_text_replacement(&mut doc, 0, &[], "x", "Ada", &red, &blue, &meta("r"), &meta("r-caret")).is_err());
|
||||
assert!(
|
||||
add_text_replacement(
|
||||
&mut doc,
|
||||
TextReplacement {
|
||||
page: 0,
|
||||
quads: &[],
|
||||
replacement: "x",
|
||||
author: "Ada",
|
||||
strike: &red,
|
||||
caret: &blue,
|
||||
strike_meta: &meta("r"),
|
||||
caret_meta: &meta("r-caret")
|
||||
}
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1177,7 +1208,7 @@ fn every_line_ending_is_drawn() {
|
||||
)
|
||||
.unwrap();
|
||||
let before = rect(&list(&doc, 0).into_iter().find(|d| d.name(b"Subtype") == Some(b"Line")).expect("line"));
|
||||
set_style(&mut doc, 0, index, None, None, None, Some(&[LineEnding::None, LineEnding::Square]), &meta("style")).unwrap();
|
||||
set_style(&mut doc, 0, index, StyleChange::new(None, None, None, Some(vec![LineEnding::None, LineEnding::Square])), &meta("style")).unwrap();
|
||||
let mut doc = reopen(&doc);
|
||||
let line = list(&doc, 0).into_iter().find(|d| d.name(b"Subtype") == Some(b"Line")).expect("line");
|
||||
let after = rect(&line);
|
||||
@@ -1245,7 +1276,7 @@ fn redraw_keeps_shared_annotation_appearances_and_drops_stale_alternates() {
|
||||
let other_dict = list(&doc, 0)[other].clone();
|
||||
let source = other_dict.get(b"AP").unwrap();
|
||||
let before = doc.resolve(source);
|
||||
set_style(&mut doc, 0, first, Some([1.0, 0.0, 0.0]), None, None, None, &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, first, StyleChange::new(Some([1.0, 0.0, 0.0]), None, None, None), &meta("")).unwrap();
|
||||
assert_eq!(doc.resolve(source), before, "a shared AP must not change");
|
||||
for full in [false, true] {
|
||||
let bytes = if full {
|
||||
@@ -1367,7 +1398,7 @@ fn comment_text_the_standard_fonts_cannot_draw_is_kept_and_marked() {
|
||||
.unwrap();
|
||||
assert_eq!(mark(&doc, s), Some('张'));
|
||||
// Our own `?` appearance is redrawn on a restyle; Western European text clears the mark.
|
||||
set_style(&mut doc, 0, t, Some([1.0, 0.0, 0.0]), None, None, None, &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, t, StyleChange::new(Some([1.0, 0.0, 0.0]), None, None, None), &meta("")).unwrap();
|
||||
assert_eq!(mark(&doc, t), Some('签'));
|
||||
set_contents(&mut doc, 0, t, "Zürich —\nGenève", &meta("")).unwrap();
|
||||
assert_eq!(mark(&doc, t), None);
|
||||
@@ -1395,7 +1426,7 @@ fn comment_text_the_standard_fonts_cannot_draw_is_kept_and_marked() {
|
||||
set_annots(&mut doc, page, on_page).unwrap();
|
||||
let k = list(&doc, 0).len() - 1;
|
||||
let before = list(&doc, 0)[k].clone();
|
||||
let err = set_style(&mut doc, 0, k, Some([1.0, 0.0, 0.0]), Some(0.4), Some(6.0), None, &meta("")).unwrap_err();
|
||||
let err = set_style(&mut doc, 0, k, StyleChange::new(Some([1.0, 0.0, 0.0]), Some(0.4), Some(6.0), None), &meta("")).unwrap_err();
|
||||
assert!(matches!(&err, AnnotError::Undrawable(m) if m.contains("can't redraw") && m.contains("\"欢\" (U+6B22)")), "{err:?}");
|
||||
assert_eq!(list(&doc, 0)[k], before, "a refused restyle keeps the colour, opacity, width and appearance");
|
||||
let err = set_rect(&mut doc, 0, k, [40.0, 40.0, 180.0, 90.0], &meta("")).unwrap_err();
|
||||
@@ -1476,7 +1507,7 @@ fn a_comment_listed_many_times_is_drawn_once_for_display() {
|
||||
fn text_box_font_size_changes_the_da_and_grows_the_box() {
|
||||
let mut doc = fixture();
|
||||
let t = add_annotation(&mut doc, &new(0, Shape::TextBox { rect: [100.0, 680.0, 220.0, 692.0], font_size: 12.0 }), &meta("t")).unwrap();
|
||||
set_style(&mut doc, 0, t, Some([1.0, 0.0, 0.0]), None, None, None, &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, t, StyleChange::new(Some([1.0, 0.0, 0.0]), None, None, None), &meta("")).unwrap();
|
||||
set_contents(&mut doc, 0, t, "one two three four five six seven eight nine ten eleven twelve", &meta("")).unwrap();
|
||||
let height = |doc: &Document| {
|
||||
let rect: Vec<f64> = list(doc, 0)[t].get(b"Rect").unwrap().as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect();
|
||||
@@ -1522,7 +1553,7 @@ fn text_boxes_and_callouts_can_change_fill_and_border() {
|
||||
let index = add_annotation(&mut doc, &new(0, shape), &meta("test")).unwrap();
|
||||
assert!(props(&doc, 0, index).unwrap().fillable);
|
||||
set_fill(&mut doc, 0, index, Some([1.0, 1.0, 1.0]), &meta("test")).unwrap();
|
||||
set_style(&mut doc, 0, index, None, None, Some(3.0), None, &meta("test")).unwrap();
|
||||
set_style(&mut doc, 0, index, StyleChange::new(None, None, Some(3.0), None), &meta("test")).unwrap();
|
||||
let saved = reopen(&doc);
|
||||
let properties = props(&saved, 0, index).unwrap();
|
||||
assert_eq!(properties.fill, Some([1.0; 3]));
|
||||
@@ -1791,7 +1822,7 @@ fn a_star_note_keeps_its_star_when_recoloured() {
|
||||
b"<< /Type /Annot /Subtype /Text /Rect [100 100 120 120] /Name /Star /C [1 0.82 0] /F 4 >>",
|
||||
]);
|
||||
assert_eq!(props(&doc, 0, 0).and_then(|p| p.icon), Some(NoteIcon::Star));
|
||||
set_style(&mut doc, 0, 0, Some([0.0, 0.0, 1.0]), None, None, None, &meta("")).unwrap();
|
||||
set_style(&mut doc, 0, 0, StyleChange::new(Some([0.0, 0.0, 1.0]), None, None, None), &meta("")).unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let d = &list(&doc, 0)[0];
|
||||
assert_eq!(d.name(b"Name"), Some(&b"Star"[..]));
|
||||
|
||||
@@ -563,6 +563,33 @@ pub struct Style {
|
||||
pub fill: Option<Rgb>,
|
||||
}
|
||||
|
||||
/// The style properties changed by one annotation edit. `None` leaves a property unchanged.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct StyleChange {
|
||||
pub color: Option<Rgb>,
|
||||
pub opacity: Option<f64>,
|
||||
pub width: Option<f64>,
|
||||
pub endings: Option<Vec<LineEnding>>,
|
||||
}
|
||||
|
||||
impl StyleChange {
|
||||
pub fn new(color: Option<Rgb>, opacity: Option<f64>, width: Option<f64>, endings: Option<Vec<LineEnding>>) -> Self {
|
||||
Self { color, opacity, width, endings }
|
||||
}
|
||||
}
|
||||
|
||||
/// Inputs for replacing selected text with a grouped strikeout and caret annotation.
|
||||
pub struct TextReplacement<'a> {
|
||||
pub page: usize,
|
||||
pub quads: &'a [[f64; 8]],
|
||||
pub replacement: &'a str,
|
||||
pub author: &'a str,
|
||||
pub strike: &'a Style,
|
||||
pub caret: &'a Style,
|
||||
pub strike_meta: &'a Meta,
|
||||
pub caret_meta: &'a Meta,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self { color: [1.0, 0.82, 0.0], opacity: 1.0, width: 1.0, fill: None }
|
||||
|
||||
@@ -331,7 +331,19 @@ pub(crate) fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut E
|
||||
// One `/NM` each: the caret replies to the strikeout, and XFDF names that reply by
|
||||
// the strikeout's `/NM` (#2666).
|
||||
let (strike_meta, caret_meta) = (cx.meta(), cx.meta());
|
||||
pdfcraft_annot::add_text_replacement(doc, *page, quads, text, author, strike, caret, &strike_meta, &caret_meta)?;
|
||||
pdfcraft_annot::add_text_replacement(
|
||||
doc,
|
||||
pdfcraft_annot::TextReplacement {
|
||||
page: *page,
|
||||
quads,
|
||||
replacement: text,
|
||||
author,
|
||||
strike,
|
||||
caret,
|
||||
strike_meta: &strike_meta,
|
||||
caret_meta: &caret_meta,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Edit::MarkAnnotation { page, index, marked, author } => {
|
||||
pdfcraft_annot::set_marked(doc, *page, *index, *marked, author, &cx.meta())?;
|
||||
@@ -341,7 +353,13 @@ pub(crate) fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut E
|
||||
Edit::ReshapeAnnotation { page, index, points } => pdfcraft_annot::set_points(doc, *page, *index, points, &cx.meta())?,
|
||||
Edit::ResizeAnnotation { page, index, rect } => pdfcraft_annot::set_rect(doc, *page, *index, *rect, &cx.meta())?,
|
||||
Edit::StyleAnnotation { page, index, color, opacity, width, endings } => {
|
||||
pdfcraft_annot::set_style(doc, *page, *index, *color, *opacity, *width, endings.as_deref(), &cx.meta())?;
|
||||
pdfcraft_annot::set_style(
|
||||
doc,
|
||||
*page,
|
||||
*index,
|
||||
pdfcraft_annot::StyleChange { color: *color, opacity: *opacity, width: *width, endings: endings.clone() },
|
||||
&cx.meta(),
|
||||
)?;
|
||||
}
|
||||
Edit::FillAnnotation { page, index, fill } => pdfcraft_annot::set_fill(doc, *page, *index, *fill, &cx.meta())?,
|
||||
Edit::FontSizeAnnotation { page, index, size } => pdfcraft_annot::set_font_size(doc, *page, *index, *size, &cx.meta())?,
|
||||
|
||||
+15
-13
@@ -25,6 +25,17 @@ pub struct PreparedDocument {
|
||||
document: Document,
|
||||
}
|
||||
|
||||
struct DocumentOpenData<'a> {
|
||||
name: String,
|
||||
path: Option<String>,
|
||||
bytes: Arc<Vec<u8>>,
|
||||
info: DocInfo,
|
||||
structure: Result<Result<pdfcraft_cos::Document, pdfcraft_cos::CosError>, Box<dyn std::any::Any + Send>>,
|
||||
render_password: Option<String>,
|
||||
password: Option<&'a str>,
|
||||
xfa: Option<XfaLayout>,
|
||||
}
|
||||
|
||||
impl OpenContext {
|
||||
/// Prepare a document without mutating the session that supplied this context.
|
||||
pub fn prepare_open(
|
||||
@@ -241,7 +252,8 @@ impl Session {
|
||||
};
|
||||
// XFA notes survive the document being re-read after edits.
|
||||
let xfa_warnings: Vec<String> = if info.xfa.is_some() { info.warnings.clone() } else { Vec::new() };
|
||||
let mut document = Self::build_document(&context.trust, name, path, bytes, info, cos, render_password, password, xfa);
|
||||
let mut document =
|
||||
Self::build_document(&context.trust, DocumentOpenData { name, path, bytes, info, structure: cos, render_password, password, xfa });
|
||||
// A script that ran away on open turns the form's scripts off for this document.
|
||||
document.xfa_template = if ran_away { None } else { xfa_template };
|
||||
document.js_output.append(script_output);
|
||||
@@ -254,18 +266,8 @@ impl Session {
|
||||
}
|
||||
|
||||
/// The last step of opening: build the document record and register it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_document(
|
||||
trust: &Arc<TrustStore>,
|
||||
name: String,
|
||||
path: Option<String>,
|
||||
bytes: Arc<Vec<u8>>,
|
||||
mut info: DocInfo,
|
||||
cos: Result<Result<pdfcraft_cos::Document, pdfcraft_cos::CosError>, Box<dyn std::any::Any + Send>>,
|
||||
render_password: Option<String>,
|
||||
password: Option<&str>,
|
||||
xfa: Option<XfaLayout>,
|
||||
) -> Document {
|
||||
fn build_document(trust: &Arc<TrustStore>, data: DocumentOpenData<'_>) -> Document {
|
||||
let DocumentOpenData { name, path, bytes, mut info, structure: cos, render_password, password, xfa } = data;
|
||||
let config = RenderConfig { password: render_password.as_deref().map(Arc::from), ..Default::default() };
|
||||
let (mut editor, read_only_reason) = match cos {
|
||||
Ok(Ok(cos)) => {
|
||||
|
||||
@@ -262,17 +262,21 @@ fn write(doc: &mut Document, a: &FieldAction, pages: &[pdfcraft_cos::ObjRef]) ->
|
||||
|
||||
/// Rewrite one trigger of a widget to `new` (`None` clears it), given the `old` value read from
|
||||
/// the widget. `aa` and `a_entry` are the widget's `/AA` dict and `/A` entry, updated in place.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
struct TriggerUpdate<'a> {
|
||||
trigger: Trigger,
|
||||
replacement: Option<&'a FieldAction>,
|
||||
previous: Option<Object>,
|
||||
}
|
||||
|
||||
fn replace_trigger(
|
||||
doc: &mut Document,
|
||||
pages: &[pdfcraft_cos::ObjRef],
|
||||
scripts: &mut pdfcraft_cos::DecodeBudget,
|
||||
t: Trigger,
|
||||
new: Option<&FieldAction>,
|
||||
old: Option<Object>,
|
||||
update: TriggerUpdate<'_>,
|
||||
aa: &mut Dict,
|
||||
a_entry: &mut Option<Object>,
|
||||
) -> Result<(), FormError> {
|
||||
let TriggerUpdate { trigger: t, replacement: new, previous: old } = update;
|
||||
let old_action = old.as_ref().and_then(|o| read(doc, o, pages, scripts));
|
||||
if new.is_some() && new == old_action.as_ref() {
|
||||
return Ok(());
|
||||
@@ -322,7 +326,7 @@ pub fn set_field_actions(doc: &mut Document, name: &str, actions: &[(Trigger, Fi
|
||||
None => wd.get(b"A").cloned().or_else(|| aa.get(b"U").cloned()),
|
||||
Some(k) => aa.get(k).cloned(),
|
||||
};
|
||||
replace_trigger(doc, &pages, &mut scripts, t, new, old, &mut aa, &mut a_entry)?;
|
||||
replace_trigger(doc, &pages, &mut scripts, TriggerUpdate { trigger: t, replacement: new, previous: old }, &mut aa, &mut a_entry)?;
|
||||
}
|
||||
doc.update_dict(w.obj, |d| {
|
||||
match &a_entry {
|
||||
|
||||
@@ -80,7 +80,7 @@ fn standard_annotations_captions_restyle_move_and_unknown_keys() {
|
||||
let (r, dict) = annot(&d, 0, 0).unwrap();
|
||||
assert_eq!(dict.name(b"IT"), Some(&b"LineDimension"[..]));
|
||||
d.update_dict(r, |d| d.set(b"VendorKey".to_vec(), Object::Int(42))).unwrap();
|
||||
pdfcraft_annot::set_style(&mut d, 0, 0, Some([1.0, 0.0, 0.0]), None, Some(2.0), None, &meta).unwrap();
|
||||
pdfcraft_annot::set_style(&mut d, 0, 0, pdfcraft_annot::StyleChange::new(Some([1.0, 0.0, 0.0]), None, Some(2.0), None), &meta).unwrap();
|
||||
let (_, dict) = annot(&d, 0, 0).unwrap();
|
||||
assert_eq!(dict.int(b"VendorKey"), Some(42));
|
||||
let ap = d.resolve(dict.get(b"AP").unwrap());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pdfcraft_annot::{Markup, Meta, NewAnnotation, Shape, Style, add_annotation, add_reply, add_text_replacement, summaries};
|
||||
use pdfcraft_annot::{Markup, Meta, NewAnnotation, Shape, Style, TextReplacement, add_annotation, add_reply, add_text_replacement, summaries};
|
||||
use pdfcraft_cos::{SaveOptions, write_full, write_incremental};
|
||||
use pdfcraft_forms::{NewField, add_field, fields, set_value};
|
||||
|
||||
@@ -151,7 +151,20 @@ fn a_replace_text_pair_survives_an_xfdf_round_trip() {
|
||||
let quads = [[100.0, 720.0, 200.0, 720.0, 100.0, 706.0, 200.0, 706.0]];
|
||||
let style = Style { color: [0.89, 0.13, 0.13], opacity: 1.0, width: 1.0, fill: None };
|
||||
let meta = |id: &str| Meta { date: Some("D:20261001120000Z".into()), id: id.into() };
|
||||
add_text_replacement(&mut src, 0, &quads, "new words", "Ada", &style, &style, &meta("strike"), &meta("caret")).unwrap();
|
||||
add_text_replacement(
|
||||
&mut src,
|
||||
TextReplacement {
|
||||
page: 0,
|
||||
quads: &quads,
|
||||
replacement: "new words",
|
||||
author: "Ada",
|
||||
strike: &style,
|
||||
caret: &style,
|
||||
strike_meta: &meta("strike"),
|
||||
caret_meta: &meta("caret"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(summaries(&src).len(), 2, "a strikeout and a caret");
|
||||
|
||||
let xfdf = export_xfdf(&src, true, false, "a.pdf");
|
||||
|
||||
Reference in New Issue
Block a user