Files
pdfcraft/crates/xfa/src/fixtures.rs
T
aiongg da7f592cd0 Merge PR #1648: XFA: decode BMP and TIFF pictures
crates/xfa/src/tests.rs: union of fixture imports (tiny_bmp, tiny_tiff).
parity/acrobat-features.toml: form.xfa and the XFA image entry: evidence lists unioned, notes take both sides' insertions.
2026-10-22 01:49:05 +00:00

661 lines
38 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Synthetic fixtures for tests in this and other crates (never real forms).
#![doc(hidden)]
/// A small dynamic form: a letter page master with a page-number draw, a title, a text field
/// with a caption on top, a check box, a radio group beside a question, a date field, a table
/// with a repeated header row and `rows` data rows, and a last section that starts a new page.
pub fn template(rows: usize) -> String {
let mut t = String::from(
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<config xmlns="http://www.xfa.org/schema/xci/3.0/"><present><pdf><version>1.7</version></pdf></present><template><base/></template></config>
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb" locale="en_US">
<pageSet>
<pageArea name="front" id="front"><occur min="1" max="1"/>
<contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
<medium stock="letter" short="8.5in" long="11in"/>
<draw name="pn" x="7in" y="10.6in" w="1in" h="0.2in"><value><exData contentType="text/html"><body xmlns="http://www.w3.org/1999/xhtml"><p>Page <span xfa:embed="#pageNo" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"/> of <span xfa:embed="#pageCount" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"/></p></body></exData></value><font typeface="Arial" size="8pt"/></draw>
<field name="pageNo" id="pageNo" presence="hidden" w="1in" h="0.2in"><ui><textEdit/></ui><event activity="ready" ref="$layout"><script contentType="application/x-javascript">this.rawValue = xfa.layout.page(this);</script></event></field>
<field name="pageCount" id="pageCount" presence="hidden" w="1in" h="0.2in"><ui><textEdit/></ui><event activity="ready" ref="$layout"><script contentType="application/x-javascript">this.rawValue = xfa.layout.pageCount();</script></event></field>
</pageArea>
<pageArea name="rest"><occur max="-1"/>
<contentArea x="0.5in" y="1in" w="7.5in" h="9.5in"/>
<medium stock="letter" short="8.5in" long="11in"/>
</pageArea>
</pageSet>
<subform name="head" layout="lr-tb">
<draw name="title" w="7.5in" h="0.4in"><value><exData contentType="text/html"><body xmlns="http://www.w3.org/1999/xhtml"><p style="font-weight:bold">Sample Form</p><p>Second line</p></body></exData></value><font typeface="Arial" size="12pt"/><para hAlign="center"/></draw>
<draw name="hidden" presence="hidden" w="7.5in" h="3in"><value><text>never shown</text></value></draw>
<field name="familyName" w="3in" h="0.5in"><ui><textEdit><border><edge presence="hidden"/><edge presence="hidden"/><edge/><edge presence="hidden"/></border></textEdit></ui><font typeface="Courier New" size="9pt"/><caption placement="top" reserve="0.2in"><value><text>Family name</text></value><font typeface="Arial" size="7pt"/></caption><assist><toolTip>Your family name</toolTip></assist><value><text maxChars="30"/></value></field>
<field name="agree" w="2in" h="0.25in"><ui><checkButton><border><edge/><fill/></border></checkButton></ui><caption placement="right" reserve="1.5in"><value><text>I agree</text></value></caption><items><integer>1</integer><integer>0</integer></items></field>
<field name="born" w="2in" h="0.5in"><ui><dateTimeEdit/><picture>date{YYYY-MM-DD}</picture></ui><caption placement="top" reserve="0.2in"><value><text>Date of birth</text></value></caption><value><date/></value></field>
<draw name="q" w="6.5in" h="0.25in"><value><text>Have you ever?</text></value></draw>
<exclGroup name="answer" layout="lr-tb">
<field name="yes" w="0.5in" h="0.25in"><ui><checkButton/></ui><items><text>Y</text></items></field>
<field name="no" w="0.5in" h="0.25in"><ui><checkButton/></ui><items><text>N</text></items></field>
</exclGroup>
<field name="go" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Reset</text></value></caption><border><edge stroke="raised"/><fill><color value="212,208,200"/></fill></border><event activity="click"><script contentType="application/x-javascript">xfa.host.resetData();</script></event></field>
</subform>
<subform name="table" layout="table" columnWidths="2in 3in 2.5in">
<overflow leader="header"/>
<subform name="header" layout="row"><occur max="-1"/>
<draw w="2in" h="0.3in"><value><text>From</text></value><border><edge/><fill><color value="200,200,200"/></fill></border></draw>
<draw h="0.3in"><value><text>Activity</text></value><border><edge/></border></draw>
<draw h="0.3in"><value><text>Place</text></value><border><edge/></border></draw>
</subform>
"##,
);
for i in 0..rows {
t.push_str(&format!(
r##" <subform name="row" layout="row"><occur max="-1"/><field name="from{i}" h="0.4in"><ui><textEdit/></ui></field><field name="what{i}" h="0.4in"><ui><textEdit/></ui></field><field name="where{i}" h="0.4in"><ui><textEdit/></ui></field></subform>
"##
));
}
t.push_str(
r##" </subform>
<subform name="last" layout="tb"><breakBefore targetType="pageArea"/>
<draw name="sig" w="7.5in" h="0.3in"><value><text>Signature</text></value></draw>
</subform>
</subform>
</template>
</xdp:xdp>
"##,
);
t
}
/// A growable rich-text field followed by a section that starts on a new page.
pub fn growable_text_template(scripted: bool) -> String {
let event = if scripted { r#"<event activity="initialize"><script contentType="application/x-javascript">0;</script></event>"# } else { "" };
format!(
r#"<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/"><subform name="form" layout="tb">{event}
<pageSet><pageArea name="master"><occur max="-1"/>
<contentArea x="36pt" y="36pt" w="540pt" h="720pt"/><medium short="612pt" long="792pt"/>
</pageArea></pageSet>
<subform name="Page3" layout="tb">
<field name="ACTIVIDADES" w="540pt" minH="207.017mm"><ui><textEdit multiLine="1" allowRichText="1"/></ui>
<font typeface="Courier" size="8pt"/><value><exData contentType="text/html"/></value>
</field>
</subform>
<subform name="following" layout="tb"><breakBefore targetType="pageArea"/>
<draw name="end" w="500pt" h="20pt"><value><text>Following section</text></value></draw>
</subform>
</subform></template>
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><xfa:data><form><Page3><ACTIVIDADES>Short</ACTIVIDADES></Page3></form></xfa:data></xfa:datasets>
</xdp:xdp>"#
)
}
/// Three body pages sharing a page-master print button and its XFA click script.
pub fn repeated_print_button_template() -> String {
let mut xml = String::from(
r#"<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="master"><occur max="-1"/>
<contentArea x="36pt" y="72pt" w="540pt" h="648pt"/><medium short="612pt" long="792pt"/>
<field name="PrintButton1" x="36pt" y="24pt" w="100pt" h="25pt"><ui><button/></ui>
<caption><value><text>Print form</text></value></caption>
<event activity="click"><script contentType="application/x-javascript">xfa.host.print(1, "0", (xfa.host.numPages -1).toString(), 0, 0, 0, 0, 0);</script></event>
</field>
</pageArea></pageSet>
"#,
);
for page in 1..=3 {
xml.push_str(&format!(
r#"<subform name="page{page}" layout="tb"><breakBefore targetType="pageArea"/>
<field name="note{page}" w="200pt" h="25pt"><ui><textEdit/></ui><value><text>Page {page}</text></value></field>
</subform>"#
));
}
xml.push_str("</subform></template>");
xml
}
/// [`template`] with a datasets packet holding `data` (the children of `xfa:data`).
pub fn template_with_data(rows: usize, data: &str) -> String {
let t = template(rows);
let packet =
format!("<xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data>{data}</xfa:data></xfa:datasets>\n</xdp:xdp>");
t.replace("</xdp:xdp>", &packet)
}
/// A static XFA form: an AcroForm with Designer-style field names (`form1[0].page1[0].name[0]`,
/// a text field, and `…agree[0]`, a check box with on state `1`), plus XFA packets whose
/// datasets hold `data` (the children of `xfa:data`). No `/NeedsRendering`.
pub fn static_shell(data: &str) -> Vec<u8> {
let xdp = format!(
"<xdp:xdp xmlns:xdp=\"http://ns.adobe.com/xdp/\"><template xmlns=\"http://www.xfa.org/schema/xfa-template/3.3/\"><subform name=\"form1\"/></template><xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data>{data}</xfa:data></xfa:datasets></xdp:xdp>"
);
let objs: Vec<Vec<u8>> = vec![
b"<< /Type /Catalog /Pages 2 0 R /AcroForm << /XFA 5 0 R /Fields [6 0 R] /DA (/Helv 0 Tf 0 g) /DR << /Font << /Helv << /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >> >> >> >> >>".to_vec(),
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Annots [8 0 R 9 0 R] >>".to_vec(),
b"<< /Length 0 >>\nstream\n\nendstream".to_vec(),
format!("<< /Length {} >>\nstream\n{xdp}\nendstream", xdp.len()).into_bytes(),
b"<< /T (form1[0]) /Kids [7 0 R] >>".to_vec(),
b"<< /T (page1[0]) /Parent 6 0 R /Kids [8 0 R 9 0 R] >>".to_vec(),
b"<< /Type /Annot /Subtype /Widget /FT /Tx /T (name[0]) /Parent 7 0 R /Rect [72 700 300 720] /P 3 0 R /DA (/Helv 10 Tf 0 g) /MK << /BC [0 0 0] >> >>".to_vec(),
b"<< /Type /Annot /Subtype /Widget /FT /Btn /T (agree[0]) /Parent 7 0 R /Rect [72 660 84 672] /P 3 0 R /V /Off /AS /Off /MK << /BC [0 0 0] >> /AP << /N << /1 10 0 R /Off 10 0 R >> >> >>".to_vec(),
b"<< /Type /XObject /Subtype /Form /BBox [0 0 12 12] /Length 0 >>\nstream\n\nendstream".to_vec(),
];
assemble(objs)
}
/// A PDF file holding `objs` as objects 1, 2, … in order, with a cross-reference table and a
/// trailer whose root is object 1.
fn assemble(objs: Vec<Vec<u8>>) -> Vec<u8> {
let mut out = b"%PDF-1.7\n".to_vec();
let mut offsets = Vec::new();
for (i, o) in objs.iter().enumerate() {
offsets.push(out.len());
out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
out.extend_from_slice(o);
out.extend_from_slice(b"\nendobj\n");
}
let xref = out.len();
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
for o in offsets {
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
}
out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
out
}
/// A hidden subform `details` a script may show, holding a named field and three unnamed
/// objects: a caption, an old caption the form keeps hidden, and a hidden subform. Unnamed
/// objects have no SOM name of their own, so showing `details` must leave the hidden ones hidden.
pub fn unnamed_hidden_template() -> String {
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page1" layout="tb">
<subform name="details" layout="tb" presence="hidden">
<draw w="4in" h="0.3in"><value><text>Current caption</text></value></draw>
<draw w="4in" h="0.3in" presence="hidden"><value><text>Old caption</text></value></draw>
<subform layout="tb" presence="hidden"><draw w="4in" h="0.3in"><value><text>Old section</text></value></draw></subform>
<field name="note" w="4in" h="0.3in"><ui><textEdit/></ui></field>
</subform>
</subform>
</subform>
</template>
</xdp:xdp>
"##
.to_string()
}
/// A scripted form (JavaScript events): `qty` (initialised to 2, validated ≤ 100) × `price`
/// (5) = `total` (calculated); a check box `more` that shows the hidden `details` subform;
/// a table whose `row` (item, amount) repeats, with `addRow`, `removeRow` and `hello` buttons;
/// `grand`, the calculated sum of the amounts.
pub fn scripted_template() -> String {
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page1" layout="tb">
<event activity="initialize"><script contentType="application/x-javascript">if (qty.rawValue === null) qty.rawValue = 2;</script></event>
<field name="qty" w="2in" h="0.3in"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Qty</text></value></caption>
<validate><script contentType="application/x-javascript">this.rawValue === null || this.rawValue &lt;= 100</script><message><text name="scriptTest">Quantity must be 100 or less</text></message></validate></field>
<field name="price" w="2in" h="0.3in"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Price</text></value></caption><value><integer>5</integer></value></field>
<field name="total" w="2in" h="0.3in" access="readOnly"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Total</text></value></caption>
<calculate><script contentType="application/x-javascript">qty.rawValue * price.rawValue</script></calculate></field>
<field name="more" w="2in" h="0.3in"><ui><checkButton/></ui><caption placement="right" reserve="1.5in"><value><text>More details</text></value></caption><items><integer>1</integer><integer>0</integer></items>
<event activity="change"><script contentType="application/x-javascript">details.presence = (this.rawValue == 1) ? "visible" : "hidden";</script></event></field>
<subform name="details" layout="tb" presence="hidden">
<field name="note" w="4in" h="0.3in"><ui><textEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Note</text></value></caption></field>
</subform>
<subform name="table" layout="table" columnWidths="3in 2in">
<subform name="row" layout="row"><occur min="1" max="-1"/>
<field name="item" h="0.3in"><ui><textEdit/></ui></field>
<field name="amount" h="0.3in"><ui><numericEdit/></ui></field>
</subform>
</subform>
<field name="grand" w="2in" h="0.3in" access="readOnly"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Sum</text></value></caption>
<calculate><script contentType="application/x-javascript">var t = 0; var rows = xfa.resolveNodes("table.row[*]"); for (var i = 0; i &lt; rows.length; i++) { t += (rows.item(i).amount.rawValue || 0); } t</script></calculate></field>
<field name="addRow" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Add row</text></value></caption>
<event activity="click"><script contentType="application/x-javascript">table._row.addInstance(1);</script></event></field>
<field name="removeRow" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Remove row</text></value></caption>
<event activity="click"><script contentType="application/x-javascript">if (table._row.count > 1) { table._row.removeInstance(table._row.count - 1); }</script></event></field>
<field name="hello" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Hello</text></value></caption>
<event activity="click"><script contentType="application/x-javascript">xfa.host.messageBox("Hello " + qty.rawValue);</script></event></field>
<field name="legacy" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Legacy</text></value></caption>
<event activity="click"><script>$host.messageBox("FormCalc")</script></event></field>
</subform>
</subform>
</template>
</xdp:xdp>
"##
.to_string()
}
/// [`scripted_template`] with every script in FormCalc (no `contentType`, as Designer writes
/// the default language): the same fields, plus `words`, the total in English words.
pub fn formcalc_template() -> String {
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page1" layout="tb">
<event activity="initialize"><script>if (HasValue(qty) == 0) then qty = 2 endif</script></event>
<field name="qty" w="2in" h="0.3in"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Qty</text></value></caption>
<validate><script>HasValue($) == 0 or $ &lt;= 100</script><message><text name="scriptTest">Quantity must be 100 or less</text></message></validate></field>
<field name="price" w="2in" h="0.3in"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Price</text></value></caption><value><integer>5</integer></value></field>
<field name="total" w="2in" h="0.3in" access="readOnly"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Total</text></value></caption>
<calculate><script>qty * price</script></calculate></field>
<field name="words" w="4in" h="0.3in" access="readOnly"><ui><textEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Words</text></value></caption>
<calculate><script>WordNum(total)</script></calculate></field>
<field name="more" w="2in" h="0.3in"><ui><checkButton/></ui><caption placement="right" reserve="1.5in"><value><text>More details</text></value></caption><items><integer>1</integer><integer>0</integer></items>
<event activity="change"><script>if ($ == 1) then
details.presence = "visible"
else
details.presence = "hidden"
endif</script></event></field>
<subform name="details" layout="tb" presence="hidden">
<field name="note" w="4in" h="0.3in"><ui><textEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Note</text></value></caption></field>
</subform>
<subform name="table" layout="table" columnWidths="3in 2in">
<subform name="row" layout="row"><occur min="1" max="-1"/>
<field name="item" h="0.3in"><ui><textEdit/></ui></field>
<field name="amount" h="0.3in"><ui><numericEdit/></ui></field>
</subform>
</subform>
<field name="grand" w="2in" h="0.3in" access="readOnly"><ui><numericEdit/></ui><caption placement="left" reserve="0.8in"><value><text>Sum</text></value></caption>
<calculate><script>Sum(table.row[*].amount)</script></calculate></field>
<field name="addRow" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Add row</text></value></caption>
<event activity="click"><script>table._row.addInstance(1)</script></event></field>
<field name="removeRow" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Remove row</text></value></caption>
<event activity="click"><script>if (table._row.count > 1) then table._row.removeInstance(table._row.count - 1) endif</script></event></field>
<field name="hello" w="1in" h="0.3in"><ui><button/></ui><caption><value><text>Hello</text></value></caption>
<event activity="click"><script>$host.messageBox(Concat("Hello ", qty))</script></event></field>
</subform>
</subform>
</template>
</xdp:xdp>
"##
.to_string()
}
/// Shaped like a real Designer leave-request form: a positioned page whose unnamed subform holds
/// an area (`box`) with a top-to-bottom table of positioned rows (`<occur min="3"/>`) and a
/// read-only total that a FormCalc calculate works out over a fixed range of rows, as such forms
/// do. `data` is `xfa:data` content as Adobe's viewers write it: areas are not data scopes, so
/// there is no `box` element.
pub fn leave_template(data: &str) -> String {
let datasets = if data.is_empty() {
String::new()
} else {
format!("<xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data>{data}</xfa:data></xfa:datasets>")
};
format!(
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page" w="7.5in" h="9in">
<subform w="7.5in" h="8in">
<area name="box" x="0.25in" y="0.5in">
<subform name="table" layout="tb" w="5in">
<subform name="row" w="5in" h="0.3in"><occur min="3" max="-1"/>
<field name="reason" x="0in" w="3in" h="0.3in"><ui><textEdit/></ui></field>
<field name="days" x="3in" w="1in" h="0.3in"><ui><numericEdit/></ui></field>
</subform>
</subform>
<field name="carried" y="1.5in" w="2in" h="0.3in"><ui><numericEdit/></ui></field>
<field name="allowance" y="2in" w="2in" h="0.3in"><ui><numericEdit/></ui><value><float>30</float></value></field>
<field name="rest" y="2.5in" w="2in" h="0.3in" access="readOnly"><ui><numericEdit/></ui>
<calculate><script>
var left = carried + allowance
for i=0 upto 2 step 1 do
if (table.row[i].reason == "Regular") then
left = left - table.row[i].days
endif
endfor
this = left
</script></calculate></field>
</area>
</subform>
</subform>
</subform>
</template>
{datasets}
</xdp:xdp>
"##
)
}
/// Two date fields shown as `MM/DD/YYYY`: `due` binds its value to the data as `YYYYMMDD`
/// (`<bind><picture>`), `when` has no data picture, so its data holds ISO dates.
pub fn bound_date_template(data: &str) -> String {
let datasets = if data.is_empty() {
String::new()
} else {
format!("<xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data>{data}</xfa:data></xfa:datasets>")
};
format!(
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page" layout="tb">
<field name="due" w="2in" h="0.3in"><ui><dateTimeEdit/><picture>date{{MM/DD/YYYY}}</picture></ui><value><date/></value><bind match="once"><picture>date{{YYYYMMDD}}</picture></bind></field>
<field name="when" w="2in" h="0.3in"><ui><dateTimeEdit/><picture>date{{MM/DD/YYYY}}</picture></ui><value><date/></value></field>
</subform>
</subform>
</template>
{datasets}
</xdp:xdp>
"##
)
}
/// Date fields shown as `MM/DD/YYYY` whose data pictures differ: two siblings both named
/// `due`, the first bound `YYYYMMDD` and the second `DDMMYYYY`, and `month`, bound `YYYYMM`
/// (a year and month, no day).
pub fn sibling_date_template(data: &str) -> String {
let datasets = if data.is_empty() {
String::new()
} else {
format!("<xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data>{data}</xfa:data></xfa:datasets>")
};
let field = |name: &str, bind: &str| {
format!(
r#"<field name="{name}" w="2in" h="0.3in"><ui><dateTimeEdit/><picture>date{{MM/DD/YYYY}}</picture></ui><value><date/></value><bind match="once"><picture>date{{{bind}}}</picture></bind></field>"#
)
};
format!(
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page" layout="tb">
{}
{}
{}
</subform>
</subform>
</template>
{datasets}
</xdp:xdp>
"##,
field("due", "YYYYMMDD"),
field("due", "DDMMYYYY"),
field("month", "YYYYMM")
)
}
/// A 2 × 2 RGBA PNG: red, green / blue, transparent.
pub fn tiny_png() -> Vec<u8> {
TINY_PNG.to_vec()
}
const TINY_PNG: &[u8] = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x02\x00\x00\x00\x02\x08\x06\x00\x00\x00\x72\xb6\x0d\x24\x00\x00\x00\x13\x49\x44\x41\x54\x78\xda\x63\xf8\xcf\xc0\xf0\x1f\x0c\x81\x34\x88\x60\x00\x00\x3f\xd2\x05\xfb\x7f\xe6\x6a\x2b\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
/// A 1 × 1 white GIF.
pub const TINY_GIF: &[u8] = b"GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
/// A 2 × 1 BMP, red then blue: 24-bit, or 8-bit with a two-colour palette.
pub fn tiny_bmp(palette: bool) -> Vec<u8> {
let (bpp, colours, pixels): (u16, &[u8], &[u8]) =
if palette { (8, &[0, 0, 255, 0, 255, 0, 0, 0], &[0, 1, 0, 0]) } else { (24, &[], &[0, 0, 255, 255, 0, 0, 0, 0]) };
let offset = 54 + colours.len() as u32;
let mut b = b"BM".to_vec();
b.extend_from_slice(&(offset + pixels.len() as u32).to_le_bytes());
b.extend_from_slice(&[0; 4]);
b.extend_from_slice(&offset.to_le_bytes());
for v in [40u32, 2, 1] {
b.extend_from_slice(&v.to_le_bytes());
}
b.extend_from_slice(&1u16.to_le_bytes());
b.extend_from_slice(&bpp.to_le_bytes());
for v in [0u32, pixels.len() as u32, 0, 0, (colours.len() / 4) as u32, 0] {
b.extend_from_slice(&v.to_le_bytes());
}
b.extend_from_slice(colours);
b.extend_from_slice(pixels);
b
}
/// A 1 × 1 little-endian RGB TIFF of colour `#123456`, uncompressed or LZW.
pub fn tiny_tiff(lzw: bool) -> Vec<u8> {
// Clear, 0x12, 0x34, 0x56, end of information, as 9-bit codes, most significant bit first.
let strip: &[u8] = if lzw { &[0x80, 0x04, 0x86, 0x85, 0x68, 0x08] } else { &[0x12, 0x34, 0x56] };
// Header (8), the IFD (2 + 9 entries of 12 + 4), then BitsPerSample's three values (6).
let (bps_at, strip_at) = (122u32, 128u32);
let (short, long) = (3u16, 4u16);
let mut b = b"II*\0".to_vec();
b.extend_from_slice(&8u32.to_le_bytes());
b.extend_from_slice(&9u16.to_le_bytes());
for (tag, ty, count, value) in [
(256u16, short, 1u32, 1u32),
(257, short, 1, 1),
(258, short, 3, bps_at),
(259, short, 1, if lzw { 5 } else { 1 }),
(262, short, 1, 2),
(273, long, 1, strip_at),
(277, short, 1, 3),
(278, short, 1, 1),
(279, long, 1, strip.len() as u32),
] {
b.extend_from_slice(&tag.to_le_bytes());
b.extend_from_slice(&ty.to_le_bytes());
b.extend_from_slice(&count.to_le_bytes());
b.extend_from_slice(&value.to_le_bytes());
}
b.extend_from_slice(&0u32.to_le_bytes());
for _ in 0..3 {
b.extend_from_slice(&8u16.to_le_bytes());
}
b.extend_from_slice(strip);
b
}
/// A form with the other field kinds: `country`, a drop-down whose shown names save as codes
/// (`Canada`/`CA`, `France`/`FR`, `Japan`/`JP`; default `FR`); `langs`, a multi-select list
/// box (`English`, `French`, `Spanish`); `other`, an editable drop-down (`A`, `B`); `pin`, a
/// password field; `sign`, a signature field; `photo`, an image field whose template picture
/// is [`tiny_png`]; `code`, a barcode field with value `12345`; `logo`, a draw showing
/// [`TINY_GIF`]; `summary`, calculated (JavaScript) from `country`. `data` is the children of
/// `xfa:data` (empty for none).
pub fn fields_template(data: &str) -> String {
use base64::Engine;
let png = base64::engine::general_purpose::STANDARD.encode(tiny_png());
let gif = base64::engine::general_purpose::STANDARD.encode(TINY_GIF);
let datasets = if data.is_empty() {
String::new()
} else {
format!("<xfa:datasets xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><xfa:data>{data}</xfa:data></xfa:datasets>")
};
format!(
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page1" layout="tb">
<field name="country" w="3in" h="0.3in"><ui><choiceList/></ui><caption placement="left" reserve="1in"><value><text>Country</text></value></caption>
<items><text>Canada</text><text>France</text><text>Japan</text></items>
<items save="1" presence="hidden"><text>CA</text><text>FR</text><text>JP</text></items>
<value><text>FR</text></value></field>
<field name="langs" w="3in" h="0.8in"><ui><choiceList open="multiSelect"/></ui><caption placement="left" reserve="1in"><value><text>Languages</text></value></caption>
<items><text>English</text><text>French</text><text>Spanish</text></items></field>
<field name="other" w="3in" h="0.3in"><ui><choiceList open="onEntry" textEntry="1"/></ui><items><text>A</text><text>B</text></items></field>
<field name="pin" w="2in" h="0.3in"><ui><passwordEdit/></ui><caption placement="left" reserve="0.5in"><value><text>PIN</text></value></caption></field>
<field name="sign" w="3in" h="0.6in"><ui><signature/></ui><caption placement="top" reserve="0.2in"><value><text>Sign here</text></value></caption><border><edge/></border></field>
<field name="photo" w="1in" h="1in"><ui><imageEdit/></ui><value><image contentType="image/png">{png}</image></value></field>
<field name="code" w="2in" h="0.5in"><ui><barcode type="code128A"/></ui><value><text>12345</text></value><border><edge/></border></field>
<draw name="logo" w="0.5in" h="0.5in"><value><image contentType="image/gif">{gif}</image></value></draw>
<field name="summary" w="3in" h="0.3in" access="readOnly"><ui><textEdit/></ui>
<calculate><script contentType="application/x-javascript">"Country: " + (country.rawValue || "none")</script></calculate></field>
</subform>
</subform>
</template>
{datasets}
</xdp:xdp>
"##
)
}
/// A form of `sections` sections of `rows` rows, each row with a calculated total (half in
/// JavaScript, half in FormCalc) and a section sum over its rows: 2 × sections × rows + sections
/// calculate scripts, every one reading values the earlier ones settled.
pub fn many_calculations_template(sections: usize, rows: usize) -> String {
let mut body = String::new();
for sct in 0..sections {
body.push_str(&format!("<subform name=\"s{sct}\" layout=\"tb\"><subform name=\"table\" layout=\"table\" columnWidths=\"1in 1in 1in\">"));
for r in 0..rows {
let calc = if (sct + r) % 2 == 0 {
"<script contentType=\"application/x-javascript\">qty.rawValue * price.rawValue</script>"
} else {
"<script>qty * price</script>"
};
body.push_str(&format!(
"<subform name=\"r{r}\" layout=\"row\"><field name=\"qty\" h=\"0.2in\"><ui><numericEdit/></ui><value><integer>{}</integer></value></field><field name=\"price\" h=\"0.2in\"><ui><numericEdit/></ui><value><integer>2</integer></value></field><field name=\"total\" h=\"0.2in\"><ui><numericEdit/></ui><calculate>{calc}</calculate></field></subform>",
r + 1
));
}
body.push_str("</subform>");
let terms: Vec<String> = (0..rows).map(|r| format!("table.r{r}.total")).collect();
body.push_str(&format!(
"<field name=\"sum\" w=\"2in\" h=\"0.2in\"><ui><numericEdit/></ui><calculate><script>Sum({})</script></calculate></field></subform>",
terms.join(", ")
));
}
format!(
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><occur max="-1"/><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
{body}
</subform>
</template>
</xdp:xdp>
"##
)
}
/// Shaped like a leave form's row of dates: `from`, a date field typed and shown as `YYYYMMDD`
/// (its data keeps ISO dates), `days`, an invisible `minus1` one less than `days`, and `to`,
/// which a calculation works out as `from` plus `minus1` days. As such forms do, it checks that
/// `from`'s raw value is a whole ISO date, then does its date arithmetic on `formattedValue`.
pub fn date_row_template() -> String {
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form1" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page" layout="tb">
<field name="from" w="1.5in" h="0.3in"><ui><dateTimeEdit/><picture>date{YYYYMMDD}</picture></ui><value><date/></value><format><picture>date{YYYYMMDD}</picture></format></field>
<field name="days" w="1in" h="0.3in"><ui><numericEdit/></ui></field>
<field name="minus1" w="1in" h="0.3in" presence="invisible" access="readOnly"><ui><numericEdit/></ui><calculate><script contentType="application/x-javascript">this.rawValue = days.rawValue - 1;</script></calculate></field>
<field name="to" w="1.5in" h="0.3in" access="readOnly"><ui><textEdit/></ui><calculate><script contentType="application/x-javascript">
if (form1.page.from.access == "open") {
if (form1.page.from.rawValue != null &amp;&amp; form1.page.from.rawValue != "" &amp;&amp; String(form1.page.from.rawValue).length == 10) {
form1.page.to.rawValue = AddDays(form1.page.from, form1.page.minus1.rawValue);
} else {
form1.page.to.rawValue = "";
}
}
function AddDays(dateObj, daysOffset) {
var shown = dateObj.formattedValue;
var date = new Date(shown.substr(0, 4), shown.substr(4, 2) - 1, shown.substr(6, 2), 0, 0, 0, 0);
date.setMilliseconds(daysOffset * 86400000);
var month = date.getMonth() + 1;
if (month &lt; 10) { month = "0" + month; }
var day = date.getDate();
if (day &lt; 10) { day = "0" + day; }
return String(date.getFullYear()) + String(month) + String(day);
}
</script></calculate></field>
</subform>
</subform>
</template>
</xdp:xdp>
"##
.to_string()
}
/// A PDF shell around `xdp`: one placeholder page, `/NeedsRendering true`, no fields.
pub fn shell(xdp: &str) -> Vec<u8> {
let objs: Vec<Vec<u8>> = vec![
b"<< /Type /Catalog /Pages 2 0 R /NeedsRendering true /AcroForm << /XFA 5 0 R /Fields [] >> >>".to_vec(),
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>".to_vec(),
b"<< /Length 44 >>\nstream\nBT /F1 12 Tf 72 700 Td (Please wait...) Tj ET\nendstream".to_vec(),
format!("<< /Length {} >>\nstream\n{xdp}\nendstream", xdp.len()).into_bytes(),
];
assemble(objs)
}
/// A dynamic form with two signature boxes of which `sign` has been signed: the file holds the
/// field a signing application leaves (named by its SOM path, its value a signature dictionary)
/// and the picture drawn in the box, a solid blue rectangle.
pub fn signed_shell() -> Vec<u8> {
let xdp = r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="page"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="body" layout="tb" w="7.5in">
<field name="sign" w="3in" h="1in"><ui><signature/></ui></field>
<field name="other" w="3in" h="1in"><ui><signature/></ui></field>
</subform>
</subform>
</template>
</xdp:xdp>
"##;
let picture = "0 0 1 rg 0 0 100 40 re f";
let objs: Vec<Vec<u8>> = vec![
b"<< /Type /Catalog /Pages 2 0 R /NeedsRendering true /AcroForm << /XFA 5 0 R /Fields [6 0 R] /SigFlags 3 >> >>".to_vec(),
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>".to_vec(),
b"<< /Length 44 >>\nstream\nBT /F1 12 Tf 72 700 Td (Please wait...) Tj ET\nendstream".to_vec(),
format!("<< /Length {} >>\nstream\n{xdp}\nendstream", xdp.len()).into_bytes(),
b"<< /T (form[0]) /Kids [7 0 R] >>".to_vec(),
b"<< /T (body[0]) /Parent 6 0 R /Kids [8 0 R] >>".to_vec(),
b"<< /T (sign[0]) /Parent 7 0 R /FT /Sig /V 9 0 R /Type /Annot /Subtype /Widget /Rect [0 0 0 0] /AP << /N 10 0 R >> >>".to_vec(),
b"<< /Type /Sig /Name (A Signer) >>".to_vec(),
format!("<< /Type /XObject /Subtype /Form /BBox [0 0 100 40] /Length {} >>\nstream\n{picture}\nendstream", picture.len()).into_bytes(),
];
assemble(objs)
}
/// A check box `kind` whose change script, once it is checked, opens the protected check box
/// `leave` and shows the invisible field `details`, then fails on an object the form doesn't
/// have (as scripts written for an earlier version of a form do).
pub fn partly_failing_script_template() -> String {
r##"<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form" layout="tb">
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
<subform name="page" layout="tb">
<field name="kind" w="0.3in" h="0.3in"><ui><checkButton/></ui><items><integer>1</integer><integer>0</integer></items><value><integer>0</integer></value>
<event activity="change"><script contentType="application/x-javascript">
if (this.rawValue == "1") {
this.resolveNode("leave").access = "open";
this.resolveNode("details").presence = "visible";
this.resolveNode("noSuchField").rawValue = "0";
}
</script></event>
</field>
<field name="leave" w="0.3in" h="0.3in" access="protected"><ui><checkButton/></ui><items><integer>1</integer><integer>0</integer></items><value><integer>0</integer></value></field>
<field name="details" w="2in" h="0.3in" presence="invisible"><ui><textEdit/></ui></field>
</subform>
</subform>
</template>
</xdp:xdp>
"##
.to_string()
}