mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-30 21:17:55 -06:00
Refactor xfa for strict Clippy checks
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use super::values::{FieldDatum, Want, wanted, with_template};
|
||||
use super::*;
|
||||
use super::{Document, HashMap, MAX_DEPTH, XFA_DATA_NS};
|
||||
|
||||
pub(super) fn xml_name(s: &str) -> String {
|
||||
let mut out: String = s.chars().map(|c| if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' { c } else { '_' }).collect();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::path::template_field;
|
||||
use super::*;
|
||||
use super::{Field, Template};
|
||||
|
||||
/// The date letters (`yyyy-mm-dd`, `yyyymmdd`) of an XFA date picture clause such as
|
||||
/// `date{YYYY-MM-DD}`, or `None` when it has none this reads (empty, or with symbols beyond
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::build::{escape, fresh_datasets, xml_name};
|
||||
use super::merge::{Op, WRAP_CLOSE, WRAP_OPEN, qname, start_tag_end};
|
||||
use super::values::Want;
|
||||
use super::write::{DatasetsWrite, rewrite};
|
||||
use super::*;
|
||||
use super::{DataPath, Document, MAX_DEPTH, MAX_NODES, ObjRef, Object, XFA_DATA_NS, XfaError};
|
||||
|
||||
/// Data read from a file, held as a tree so it can be written into any packet with the
|
||||
/// prefix that packet uses, never as the file's own markup.
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::build::{Build, CONFLICT, CONTROL, NewNodes, STRUCTURED, TOO_MANY, bui
|
||||
use super::import::{ImportNode, serialize_nodes};
|
||||
use super::values::Want;
|
||||
use super::write::MAX_PACKET_WRITE;
|
||||
use super::*;
|
||||
use super::{DataPath, HashMap, MAX_NODES, XFA_DATA_NS};
|
||||
|
||||
/// What a rewrite of the datasets packet does.
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::merge::Op;
|
||||
use super::path::{data_path, parse_datasets};
|
||||
use super::values::{Want, path_of, text_of, with_template};
|
||||
use super::write::{datasets_stream_ref, rewrite};
|
||||
use super::*;
|
||||
use super::{DataNode, DataPath, Document, MAX_DEPTH, Node, ObjRef, Object, SomIndices, Template, Ui, XfaError};
|
||||
|
||||
/// The SOM path and default value of every AcroForm text field with the Password flag (bit 14,
|
||||
/// `1 << 13`) in `doc`, for static XFA forms whose password fields set the flag in the AcroForm
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{DataNode, DataPath, Field, MAX_DEPTH, MAX_NODES, Node, SomIndices, Template};
|
||||
|
||||
fn to_node(n: roxmltree::Node, depth: usize) -> DataNode {
|
||||
let name = n.tag_name().name().to_string();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::dates::{data_date_pattern, data_to_iso, iso_to_data, iso_to_pattern, pattern_to_iso};
|
||||
use super::path::{data_path, parse_datasets, som_to_path};
|
||||
use super::*;
|
||||
use super::{DataPath, Dict, Document, ObjRef, Object, Template};
|
||||
|
||||
/// What a field holds, for writing the datasets.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::build::{MAX_INDEX, build_children, fresh_datasets};
|
||||
use super::merge::{Op, Within, merge_all, readable};
|
||||
use super::path::path_som;
|
||||
use super::values::{FieldDatum, Want, wanted, with_template};
|
||||
use super::*;
|
||||
use super::{DataOp, DataPath, Dict, Document, Encoding, HashMap, ObjRef, Object, PdfString, Stream, Template, XfaError, encode};
|
||||
|
||||
/// What [`write_datasets`] did.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::items::{Action, Item, Rounded};
|
||||
use super::*;
|
||||
use super::{HashMap, Layout, MAX_DEPTH, Node, Rect, Script, date_letters, iso_to_pattern};
|
||||
|
||||
pub(super) fn collect_values(nodes: &[Node], out: &mut HashMap<String, String>, depth: usize) {
|
||||
if depth > MAX_DEPTH {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::common::clean_name;
|
||||
use super::items::{Item, Rounded};
|
||||
use super::*;
|
||||
use super::{
|
||||
Arc, Border, Ctx, Draw, ExclGroup, Face, Font, Layout, Layouter, MAX_DEPTH, MAX_IMAGE_BYTES, Node, Para, Presence, Rect, SomIndices, Stroke,
|
||||
Subform, Value, XfaError, data_path, text,
|
||||
};
|
||||
|
||||
impl Layouter<'_> {
|
||||
// ───────────────────────────────────────────────────────────────────────── drawing
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::common::{button_action, date_pattern};
|
||||
use super::items::{BorderShape, Item, Widget, WidgetBorder, WidgetKind};
|
||||
use super::*;
|
||||
use super::{
|
||||
Access, Border, Ctx, Engine, Face, Field, HAlign, Layouter, MAX_DATA_IMAGE, Margin, Para, Placement, Presence, Rect, Ui, VAlign, Value, XfaError,
|
||||
data_date_letters, data_path, data_to_iso, is_on, iso_to_pattern, text,
|
||||
};
|
||||
|
||||
impl Layouter<'_> {
|
||||
/// Draw field `f` at `rect`: its border (a button's is its widget's box), its caption, then its
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::common::{clamp_opt, divide, is_row, moved, solid_extent, splittable};
|
||||
use super::items::Item;
|
||||
use super::*;
|
||||
use super::{Ctx, Frame, Layout, Layouter, MAX_DEPTH, Node, Rect, SomIndices, Subform, XfaError, data_path, instance_count};
|
||||
|
||||
impl Layouter<'_> {
|
||||
/// Place `node` as one block of size (`w`, `h`) at the cursor: in this content area when it fits,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Arc, Face, HAlign, Rect, Span};
|
||||
|
||||
/// How far below its baseline an underline is drawn, in the text's size: an underline is the
|
||||
/// level line pushed just before its span, this far below it (the PDF writer tells it apart from
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::common::clamp_opt;
|
||||
use super::field::auto_check_reserve;
|
||||
use super::*;
|
||||
use super::{
|
||||
Access, Area, Ctx, ExclGroup, Face, Field, Font, Layout, Layouter, MAX_DEPTH, MAX_MEASURES, Measured, Node, Para, Placement, Rect, Subform, Ui,
|
||||
Value, data_path, text,
|
||||
};
|
||||
|
||||
impl Layouter<'_> {
|
||||
/// The width and height `node` takes in `avail_w`: its own size, else what its content needs
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#[cfg(test)]
|
||||
use super::EMBED_COPIED;
|
||||
use super::items::{Item, Page, UNDERLINE_DROP};
|
||||
use super::*;
|
||||
use super::{
|
||||
Block, Border, Ctx, Cursor, FlowMark, Font, Frame, Layouter, MAX_ITEMS, MAX_MEASURES, MAX_PAGES, PAGE_COUNT_MARK, PageRole, Para, Rc, Rect,
|
||||
SomIndices, XfaError, text,
|
||||
};
|
||||
|
||||
impl Layouter<'_> {
|
||||
pub(super) fn warn_once(&mut self, msg: impl Into<String>) {
|
||||
|
||||
+71
-57
@@ -163,38 +163,8 @@ impl Emitter<'_> {
|
||||
format!("/{} {} Tf {} rg", face.resource_name(), n(face.size), rgb(face.color)).into_bytes()
|
||||
}
|
||||
|
||||
/// Write laid-out widget `w` on `page` as an AcroForm widget annotation (and its field), with
|
||||
/// its border, background and kind, and return the annotation's object.
|
||||
fn widget(&mut self, w: &Widget, page: &Page, page_ref: ObjRef) -> Result<Option<ObjRef>, XfaError> {
|
||||
fn populate_widget_kind(&mut self, w: &Widget, d: &mut Dict, mk: &mut Dict) -> (i64, bool, Option<String>) {
|
||||
let r = w.rect;
|
||||
let (x0, y0, x1, y1) = (r.x, page.height - r.bottom(), r.right(), page.height - r.y);
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Type".to_vec(), Object::name("Annot"));
|
||||
d.set(b"Subtype".to_vec(), Object::name("Widget"));
|
||||
d.set(b"F".to_vec(), Object::Int(4));
|
||||
d.set(b"P".to_vec(), Object::Ref(page_ref));
|
||||
d.set(b"Rect".to_vec(), Object::Array([x0, y0, x1, y1].iter().map(|v| Object::Real(*v)).collect()));
|
||||
d.set(b"DA".to_vec(), Object::String(PdfString::literal(Self::da(&w.face))));
|
||||
if let Some(t) = &w.tooltip {
|
||||
d.set(b"TU".to_vec(), Object::String(PdfString::text(t)));
|
||||
}
|
||||
d.set(SOM_KEY.to_vec(), Object::String(PdfString::text(&w.som)));
|
||||
let mut mk = Dict::new();
|
||||
if let Some(c) = w.border.color
|
||||
&& w.border.shape != BorderShape::None
|
||||
{
|
||||
mk.set(b"BC".to_vec(), rgb_array(c));
|
||||
}
|
||||
if let Some(c) = w.border.fill {
|
||||
mk.set(b"BG".to_vec(), rgb_array(c));
|
||||
}
|
||||
let mut bs = Dict::new();
|
||||
bs.set(b"W".to_vec(), Object::Real(if w.border.shape == BorderShape::None { 0.0 } else { w.border.width.clamp(0.1, 12.0) }));
|
||||
bs.set(b"S".to_vec(), Object::name(if w.border.shape == BorderShape::Underline { "U" } else { "S" }));
|
||||
d.set(b"BS".to_vec(), Object::Dict(bs));
|
||||
if matches!(w.kind, WidgetKind::CheckBox { round: true, .. } | WidgetKind::Radio { round: true, .. }) {
|
||||
d.set(ROUND_KEY.to_vec(), Object::Bool(true));
|
||||
}
|
||||
let mut ff: i64 = 0;
|
||||
if w.read_only {
|
||||
ff |= 1;
|
||||
@@ -383,34 +353,45 @@ impl Emitter<'_> {
|
||||
d.set(b"FT".to_vec(), Object::name("Btn"));
|
||||
ff |= 1 << 16;
|
||||
mk.set(b"CA".to_vec(), Object::String(PdfString::text(caption)));
|
||||
if let Some(Action::Script(_)) = &w.action {
|
||||
// An XFA click script is no Acrobat JavaScript: no PDF action, which other
|
||||
// viewers would run; PdfCraft finds the script by the field's SOM path.
|
||||
d.set(CLICK_KEY.to_vec(), Object::Bool(true));
|
||||
} else if let Some(a) = &w.action {
|
||||
let mut ad = Dict::new();
|
||||
match a {
|
||||
Action::Reset => {
|
||||
ad.set(b"S".to_vec(), Object::name("ResetForm"));
|
||||
}
|
||||
Action::Print => {
|
||||
ad.set(b"S".to_vec(), Object::name("Named"));
|
||||
ad.set(b"N".to_vec(), Object::name("Print"));
|
||||
}
|
||||
Action::SaveAs => {
|
||||
ad.set(b"S".to_vec(), Object::name("Named"));
|
||||
ad.set(b"N".to_vec(), Object::name("SaveAs"));
|
||||
}
|
||||
Action::Url(u) => {
|
||||
ad.set(b"S".to_vec(), Object::name("URI"));
|
||||
ad.set(b"URI".to_vec(), Object::String(PdfString::literal(u.as_bytes().to_vec())));
|
||||
}
|
||||
Action::Script(_) => {}
|
||||
}
|
||||
d.set(b"A".to_vec(), Object::Dict(ad));
|
||||
}
|
||||
write_button_action(w.action.as_ref(), d);
|
||||
}
|
||||
}
|
||||
(ff, top_level, under)
|
||||
}
|
||||
|
||||
/// Write laid-out widget `w` on `page` as an AcroForm widget annotation (and its field), with
|
||||
/// its border, background and kind, and return the annotation's object.
|
||||
fn widget(&mut self, w: &Widget, page: &Page, page_ref: ObjRef) -> Result<Option<ObjRef>, XfaError> {
|
||||
let r = w.rect;
|
||||
let (x0, y0, x1, y1) = (r.x, page.height - r.bottom(), r.right(), page.height - r.y);
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Type".to_vec(), Object::name("Annot"));
|
||||
d.set(b"Subtype".to_vec(), Object::name("Widget"));
|
||||
d.set(b"F".to_vec(), Object::Int(4));
|
||||
d.set(b"P".to_vec(), Object::Ref(page_ref));
|
||||
d.set(b"Rect".to_vec(), Object::Array([x0, y0, x1, y1].iter().map(|v| Object::Real(*v)).collect()));
|
||||
d.set(b"DA".to_vec(), Object::String(PdfString::literal(Self::da(&w.face))));
|
||||
if let Some(t) = &w.tooltip {
|
||||
d.set(b"TU".to_vec(), Object::String(PdfString::text(t)));
|
||||
}
|
||||
d.set(SOM_KEY.to_vec(), Object::String(PdfString::text(&w.som)));
|
||||
let mut mk = Dict::new();
|
||||
if let Some(c) = w.border.color
|
||||
&& w.border.shape != BorderShape::None
|
||||
{
|
||||
mk.set(b"BC".to_vec(), rgb_array(c));
|
||||
}
|
||||
if let Some(c) = w.border.fill {
|
||||
mk.set(b"BG".to_vec(), rgb_array(c));
|
||||
}
|
||||
let mut bs = Dict::new();
|
||||
bs.set(b"W".to_vec(), Object::Real(if w.border.shape == BorderShape::None { 0.0 } else { w.border.width.clamp(0.1, 12.0) }));
|
||||
bs.set(b"S".to_vec(), Object::name(if w.border.shape == BorderShape::Underline { "U" } else { "S" }));
|
||||
d.set(b"BS".to_vec(), Object::Dict(bs));
|
||||
if matches!(w.kind, WidgetKind::CheckBox { round: true, .. } | WidgetKind::Radio { round: true, .. }) {
|
||||
d.set(ROUND_KEY.to_vec(), Object::Bool(true));
|
||||
}
|
||||
let (ff, top_level, under) = self.populate_widget_kind(w, &mut d, &mut mk);
|
||||
if !mk.is_empty() {
|
||||
d.set(b"MK".to_vec(), Object::Dict(mk));
|
||||
}
|
||||
@@ -751,6 +732,39 @@ impl Emitter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_button_action(action: Option<&Action>, d: &mut Dict) {
|
||||
match action {
|
||||
Some(Action::Script(_)) => {
|
||||
// An XFA click script is no Acrobat JavaScript: no PDF action, which other
|
||||
// viewers would run; PdfCraft finds the script by the field's SOM path.
|
||||
d.set(CLICK_KEY.to_vec(), Object::Bool(true));
|
||||
}
|
||||
Some(action) => {
|
||||
let mut ad = Dict::new();
|
||||
match action {
|
||||
Action::Reset => {
|
||||
ad.set(b"S".to_vec(), Object::name("ResetForm"));
|
||||
}
|
||||
Action::Print => {
|
||||
ad.set(b"S".to_vec(), Object::name("Named"));
|
||||
ad.set(b"N".to_vec(), Object::name("Print"));
|
||||
}
|
||||
Action::SaveAs => {
|
||||
ad.set(b"S".to_vec(), Object::name("Named"));
|
||||
ad.set(b"N".to_vec(), Object::name("SaveAs"));
|
||||
}
|
||||
Action::Url(url) => {
|
||||
ad.set(b"S".to_vec(), Object::name("URI"));
|
||||
ad.set(b"URI".to_vec(), Object::String(PdfString::literal(url.as_bytes().to_vec())));
|
||||
}
|
||||
Action::Script(_) => {}
|
||||
}
|
||||
d.set(b"A".to_vec(), Object::Dict(ad));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The link annotation a page's text last ended in: the next piece of the same link on the same
|
||||
/// line widens it.
|
||||
struct LinkPiece {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dict, Document, Family, Object, PdfString, Rect, Rounded, Stream};
|
||||
|
||||
/// Width and height of a JPEG from its first frame header (SOF0–SOF15, not the DNL/JPG/DAC/DHT
|
||||
/// markers). `None` for anything else.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{CLICK_KEY, Dict, Document, IMAGE_FIELD_KEY, LAYOUT_KEY, NODE_KEY, ObjRef, Object, SOM_KEY, XfaError};
|
||||
|
||||
/// What laying a form out says when it took the usage rights off (see [`drop_usage_rights`]).
|
||||
pub(crate) const USAGE_RIGHTS_REMOVED: &str =
|
||||
|
||||
@@ -1512,6 +1512,11 @@ fn top_fields(doc: &Document) -> Vec<(String, pdfcraft_cos::Dict)> {
|
||||
/// widget, and their AcroForm fields and image XObjects once written.
|
||||
#[test]
|
||||
fn choice_lists_signatures_passwords_and_pictures_become_fields_and_images() {
|
||||
assert_choice_widget_layout();
|
||||
assert_choice_fields_and_picture_streams();
|
||||
}
|
||||
|
||||
fn assert_choice_widget_layout() {
|
||||
let form = layout_xml(&fields_template("")).unwrap();
|
||||
let p1 = &form.pages[0];
|
||||
let w = widgets(p1);
|
||||
@@ -1546,7 +1551,9 @@ fn choice_lists_signatures_passwords_and_pictures_become_fields_and_images() {
|
||||
assert!(t.iter().any(|s| s == "12345"), "the barcode's value as text: {t:?}");
|
||||
assert!(form.warnings.iter().any(|w| w.contains("barcode")), "{:?}", form.warnings);
|
||||
assert!(!form.warnings.iter().any(|w| w.contains("can't be chosen")), "pictures can be chosen now: {:?}", form.warnings);
|
||||
}
|
||||
|
||||
fn assert_choice_fields_and_picture_streams() {
|
||||
// Written into the PDF: /Ch fields with their options and flags, a /Sig field, a password
|
||||
// field, and Flate images (the PNG with a soft mask).
|
||||
let mut doc = Document::open(Arc::new(shell(&fields_template("")))).unwrap();
|
||||
@@ -3556,6 +3563,11 @@ fn laying_a_form_out_takes_off_the_usage_rights_it_voids() {
|
||||
|
||||
#[test]
|
||||
fn stripping_takes_the_xfa_out_and_leaves_an_ordinary_document() {
|
||||
assert_stripping_leaves_ordinary_document();
|
||||
assert_stripping_handles_unbound_data_and_shared_field_tree();
|
||||
}
|
||||
|
||||
fn assert_stripping_leaves_ordinary_document() {
|
||||
use crate::pdf::strip;
|
||||
let mut doc = Document::open(Arc::new(shell(&fields_template("<form><page1><country>JP</country></page1></form>")))).unwrap();
|
||||
render_into(&mut doc).unwrap();
|
||||
@@ -3606,6 +3618,10 @@ fn stripping_takes_the_xfa_out_and_leaves_an_ordinary_document() {
|
||||
let r = strip(&mut doc).unwrap();
|
||||
assert_eq!(r, crate::Stripped::default());
|
||||
assert!(!doc.is_modified());
|
||||
}
|
||||
|
||||
fn assert_stripping_handles_unbound_data_and_shared_field_tree() {
|
||||
use crate::pdf::strip;
|
||||
// Data no field holds is counted; a form whose click buttons ran scripts too; the shape
|
||||
// check needs no packet decoded and tells a never-laid-out dynamic form from one to strip.
|
||||
let mut doc = Document::open(Arc::new(shell(&scripted_template().replace("</template>", "</template><xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data><form><page1><qty>3</qty><extra>kept nowhere</extra><more2>x</more2></page1></form></xfa:data></xfa:datasets>")))).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user