diff options
| author | Jake Sandler | 2020-09-25 10:48:26 -0400 | 
|---|---|---|
| committer | GitHub | 2020-09-25 10:48:26 -0400 | 
| commit | 8003d0724cf86fcf8eb58b8d84f17a9af318a90e (patch) | |
| tree | 0e2f1eca6f6621b7307388b91edb5fdde1f4a78e | |
| parent | 1f43c2da8648de9cd28db903a35289c87a7dec1d (diff) | |
| parent | 631f0d8b2fcc3427d8c8765883a23f6bcd61cfd4 (diff) | |
| download | pdf_form-8003d0724cf86fcf8eb58b8d84f17a9af318a90e.tar.bz2 | |
Merge pull request #5 from Emulator000/required-readonly-improvements
Various improvements and added ability to read "readonly" and "required" fields flags
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | .idea/.gitignore | 4 | ||||
| -rw-r--r-- | .idea/modules.xml | 8 | ||||
| -rw-r--r-- | .idea/pdf_form.iml | 12 | ||||
| -rw-r--r-- | .idea/vcs.xml | 6 | ||||
| -rw-r--r-- | Cargo.lock | 284 | ||||
| -rw-r--r-- | Cargo.toml | 10 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | src/lib.rs | 618 | ||||
| -rw-r--r-- | src/utils.rs | 49 | 
10 files changed, 715 insertions, 284 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a62ab56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/*.rs.bk +/*.data +/*.gz +/target/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..86730ac --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,4 @@ +# Default ignored files +/shelf/ +/workspace.xml +/dbnavigator.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ad5009c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="ProjectModuleManager"> +    <modules> +      <module fileurl="file://$PROJECT_DIR$/.idea/pdf_form.iml" filepath="$PROJECT_DIR$/.idea/pdf_form.iml" /> +    </modules> +  </component> +</project>
\ No newline at end of file diff --git a/.idea/pdf_form.iml b/.idea/pdf_form.iml new file mode 100644 index 0000000..9b4cf84 --- /dev/null +++ b/.idea/pdf_form.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> +  <component name="NewModuleRootManager" inherit-compiler-output="true"> +    <exclude-output /> +    <content url="file://$MODULE_DIR$"> +      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> +      <excludeFolder url="file://$MODULE_DIR$/target" /> +    </content> +    <orderEntry type="inheritedJdk" /> +    <orderEntry type="sourceFolder" forTests="false" /> +  </component> +</module>
\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> +  <component name="VcsDirectoryMappings"> +    <mapping directory="" vcs="Git" /> +  </component> +</project>
\ No newline at end of file @@ -1,6 +1,18 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +  [[package]]  name = "bitflags" -version = "1.0.3" +version = "1.2.1"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] @@ -9,17 +21,69 @@ version = "0.1.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] -name = "cc" -version = "1.0.17" +name = "cfg-if" +version = "0.1.10"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]]  name = "chrono" -version = "0.3.0" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]] @@ -34,7 +98,12 @@ dependencies = [  [[package]]  name = "dtoa" -version = "0.4.2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "either" +version = "1.6.1"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] @@ -96,95 +165,134 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]]  name = "flate2" -version = "0.2.20" +version = "1.0.17"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]]  name = "itoa" -version = "0.3.4" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.4.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]]  name = "libc" -version = "0.2.42" +version = "0.2.77"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]]  name = "linked-hash-map" -version = "0.3.0" +version = "0.4.2"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]]  name = "lopdf" -version = "0.15.2" +version = "0.25.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", + "dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",   "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pom 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pom 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]] -name = "miniz-sys" -version = "0.1.10" +name = "lzw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memoffset" +version = "0.5.6"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]] -name = "num" -version = "0.1.42" +name = "miniz_oxide" +version = "0.4.2"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]]  name = "num-integer" -version = "0.1.38" +version = "0.1.43"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]] -name = "num-iter" -version = "0.1.37" +name = "num-traits" +version = "0.2.12"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]] -name = "num-traits" -version = "0.2.4" +name = "num_cpus" +version = "1.13.0"  source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", +]  [[package]]  name = "pdf_form" -version = "0.1.0" +version = "0.3.0"  dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",   "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lopdf 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lopdf 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]]  name = "pom" -version = "1.1.0" +version = "3.2.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] @@ -193,8 +301,31 @@ version = "0.3.15"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] -name = "redox_syscall" -version = "0.1.40" +name = "rayon" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "1.1.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] @@ -217,12 +348,12 @@ dependencies = [  [[package]]  name = "time" -version = "0.1.40" +version = "0.1.44"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",  ]  [[package]] @@ -231,8 +362,13 @@ version = "0.0.4"  source = "registry+https://github.com/rust-lang/crates.io-index"  [[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]]  name = "winapi" -version = "0.3.5" +version = "0.3.9"  source = "registry+https://github.com/rust-lang/crates.io-index"  dependencies = [   "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -250,12 +386,20 @@ version = "0.4.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  [metadata] -"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" +"checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"  "checksum case 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e88b166b48e29667f5443df64df3c61dc07dc2b1a0b0d231800e07f09a33ecc1" -"checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d" -"checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum chrono 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)" = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"  "checksum derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ec098440b29ea3b1ece3e641bac424c19cf996779b623c9e0f2171495425c2c8" -"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" +"checksum dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" +"checksum either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"  "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"  "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"  "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" @@ -263,23 +407,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"  "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"  "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" -"checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423" -"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" -"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" -"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" -"checksum lopdf 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e4777bc707e21e9f1a3aed4da3b3a711a7eb468963df3c077fe48409993c1ae9" -"checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4" -"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" -"checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" -"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" -"checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" -"checksum pom 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" +"checksum flate2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "766d0e77a2c1502169d4a93ff3b8c15a71fd946cd0126309752104e5f3c46d94" +"checksum hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" +"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +"checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" +"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +"checksum lopdf 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3170bf7b90ea1fc3c4eaa1bed4882dddcfd58b79baa60fd1e10829a482f464f9" +"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +"checksum memoffset 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +"checksum miniz_oxide 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9" +"checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +"checksum pom 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e2192780e9f8e282049ff9bffcaa28171e1cb0844f49ed5374e518ae6024ec"  "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum rayon 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" +"checksum rayon-core 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" +"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"  "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"  "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"  "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" +"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"  "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"  "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" @@ -1,13 +1,13 @@  [package]  name = "pdf_form" -version = "0.2.0" -authors = ["Jake <jsandler18@gmail.com>"] +version = "0.3.0" +authors = ["Jake <jsandler18@gmail.com>", "Malte <voos.malte@gmail.com>", "Emulator000 <emulator@hotmail.it>"]  readme = "README.md"  keywords = ["pdf", "form"]  description = "A library for programatically filling out pdf forms"  license = "MIT"  [dependencies] -lopdf = "0.15.1" -bitflags = "1.0.1" -derive-error = "0.0.4" +bitflags = "^1.2" +derive-error = "^0.0.4" +lopdf = "^0.25" @@ -3,7 +3,7 @@ A library to programatically identify and fill out PDF forms  ## Example Code  * Read a PDF and discover the form fields -``` +```rust  extern crate pdf_form;  use pdf_form::{Form, FieldType}; @@ -18,7 +18,7 @@ for type in field_types {  ```  * Write to the form fields -``` +```rust  extern crate pdf_form;  use pdf_form::{Form, FieldState}; @@ -1,36 +1,21 @@ -//! This crate is for filling out PDFs with forms programatically. -extern crate lopdf;  #[macro_use]  extern crate bitflags;  #[macro_use]  extern crate derive_error; -use lopdf::{Document, ObjectId, Object, StringFormat}; -use std::path::Path; +mod utils; +  use std::collections::VecDeque;  use std::io; +use std::io::Write; +use std::path::Path;  use std::str; -bitflags! { -    struct ButtonFlags: u32 { -        const NO_TOGGLE_TO_OFF  = 0x8000; -        const RADIO             = 0x10000; -        const PUSHBUTTON        = 0x20000; -        const RADIO_IN_UNISON   = 0x4000000; +use bitflags::_core::str::from_utf8; -    } -} +use lopdf::{Document, Object, ObjectId, StringFormat}; -bitflags! { -    struct ChoiceFlags: u32 { -        const COBMO             = 0x40000; -        const EDIT              = 0x80000; -        const SORT              = 0x100000; -        const MULTISELECT       = 0x400000; -        const DO_NOT_SPELLCHECK = 0x800000; -        const COMMIT_ON_CHANGE  = 0x8000000; -    } -} +use crate::utils::*;  /// A PDF Form that contains fillable fields  /// @@ -38,8 +23,8 @@ bitflags! {  /// analyze the PDF and identify the fields. Then you can get and set the content of the fields by  /// index.  pub struct Form { -   doc: Document, -   form_ids: Vec<ObjectId> +    doc: Document, +    form_ids: Vec<ObjectId>,  }  /// The possible types of fillable form fields in a PDF @@ -50,40 +35,20 @@ pub enum FieldType {      CheckBox,      ListBox,      ComboBox, -    Text -} - -/// The current state of a form field -#[derive(Debug)] -pub enum FieldState { -    /// Push buttons have no state -    Button, -    /// `selected` is the sigular option from `options` that is selected -    Radio { selected: String, options: Vec<String> }, -    /// The toggle state of the checkbox -    CheckBox { is_checked: bool }, -    /// `selected` is the list of selected options from `options` -    ListBox { selected: Vec<String>, options: Vec<String>, multiselect: bool }, -    /// `selected` is the list of selected options from `options` -    ComboBox { selected: Vec<String>, options: Vec<String>, multiselect: bool }, -    /// User Text Input -    Text { text: String } +    Text, +    Unknown,  }  #[derive(Debug, Error)]  /// Errors that may occur while loading a PDF  pub enum LoadError { -    /// An IO Error -    IoError(io::Error), -    /// A dictionary key that must be present in order to find forms was not present -    DictionaryKeyNotFound, +    /// An Lopdf Error +    LopdfError(lopdf::Error),      /// The reference `ObjectId` did not point to any values      #[error(non_std, no_from)]      NoSuchReference(ObjectId),      /// An element that was expected to be a reference was not a reference      NotAReference, -    /// A value that must be a certain type was not that type -    UnexpectedType  }  /// Errors That may occur while setting values in a form @@ -94,7 +59,52 @@ pub enum ValueError {      /// One or more selected values are not valid choices      InvalidSelection,      /// Multiple values were selected when only one was allowed -    TooManySelected +    TooManySelected, +    /// Readonly field cannot be edited +    Readonly, +} +/// The current state of a form field +#[derive(Debug)] +pub enum FieldState { +    /// Push buttons have no state +    Button, +    /// `selected` is the singular option from `options` that is selected +    Radio { +        selected: String, +        options: Vec<String>, +        readonly: bool, +        required: bool, +    }, +    /// The toggle state of the checkbox +    CheckBox { +        is_checked: bool, +        readonly: bool, +        required: bool, +    }, +    /// `selected` is the list of selected options from `options` +    ListBox { +        selected: Vec<String>, +        options: Vec<String>, +        multiselect: bool, +        readonly: bool, +        required: bool, +    }, +    /// `selected` is the list of selected options from `options` +    ComboBox { +        selected: Vec<String>, +        options: Vec<String>, +        editable: bool, +        readonly: bool, +        required: bool, +    }, +    /// User Text Input +    Text { +        text: String, +        readonly: bool, +        required: bool, +    }, +    /// Unknown fields have no state +    Unknown,  }  trait PdfObjectDeref { @@ -103,15 +113,14 @@ trait PdfObjectDeref {  impl PdfObjectDeref for Object {      fn deref<'a>(&self, doc: &'a Document) -> Result<&'a Object, LoadError> { -        match self { -            &Object::Reference(oid) => doc.objects.get(&oid).ok_or(LoadError::NoSuchReference(oid)), -            _ => Err(LoadError::NotAReference) +        match *self { +            Object::Reference(oid) => doc.objects.get(&oid).ok_or(LoadError::NoSuchReference(oid)), +            _ => Err(LoadError::NotAReference),          }      }  }  impl Form { -      /// Takes a reader containing a PDF with a fillable form, analyzes the content, and attempts to      /// identify all of the fields the form has.      pub fn load_from<R: io::Read>(reader: R) -> Result<Self, LoadError> { @@ -126,36 +135,31 @@ impl Form {          Self::load_doc(doc)      } -    fn load_doc(doc: Document) -> Result<Self, LoadError>{ +    fn load_doc(doc: Document) -> Result<Self, LoadError> {          let mut form_ids = Vec::new();          let mut queue = VecDeque::new();          // Block so borrow of doc ends before doc is moved into the result          {              // Get the form's top level fields -            let catalog = doc.trailer.get("Root") -                .ok_or(LoadError::DictionaryKeyNotFound)? -                .deref(&doc)? -                .as_dict().ok_or(LoadError::UnexpectedType)?; -            let acroform = catalog.get("AcroForm") -                .ok_or(LoadError::DictionaryKeyNotFound)? -                .deref(&doc)? -                .as_dict().ok_or(LoadError::UnexpectedType)?; -            let fields_list = acroform.get("Fields") -                .ok_or(LoadError::DictionaryKeyNotFound)? -            //    .deref(&doc)? -                .as_array().ok_or(LoadError::UnexpectedType)?; +            let catalog = doc.trailer.get(b"Root")?.deref(&doc)?.as_dict()?; +            let acroform = catalog.get(b"AcroForm")?.deref(&doc)?.as_dict()?; +            let fields_list = acroform +                .get(b"Fields")? +                //    .deref(&doc)? +                .as_array()?;              queue.append(&mut VecDeque::from(fields_list.clone()));              // Iterate over the fields              while let Some(objref) = queue.pop_front() {                  let obj = objref.deref(&doc)?; -                if let &Object::Dictionary(ref dict) = obj { +                if let Object::Dictionary(ref dict) = *obj {                      // If the field has FT, it actually takes input.  Save this -                    if let Some(_) = dict.get("FT") { +                    if dict.get(b"FT").is_ok() {                          form_ids.push(objref.as_reference().unwrap());                      } +                      // If this field has kids, they might have FT, so add them to the queue -                    if let Some(&Object::Array(ref kids)) = dict.get("Kids") { +                    if let Ok(&Object::Array(ref kids)) = dict.get(b"Kids") {                          queue.append(&mut VecDeque::from(kids.clone()));                      }                  } @@ -166,7 +170,12 @@ impl Form {      /// Returns the number of fields the form has      pub fn len(&self) -> usize { -       self.form_ids.len() +        self.form_ids.len() +    } + +    /// Returns true if empty +    pub fn is_empty(&self) -> bool { +        self.len() == 0      }      /// Gets the type of field of the given index @@ -175,12 +184,18 @@ impl Form {      /// This function will panic if the index is greater than the number of fields      pub fn get_type(&self, n: usize) -> FieldType {          // unwraps should be fine because load should have verified everything exists -        let field = self.doc.objects.get(&self.form_ids[n]).unwrap().as_dict().unwrap(); -        let obj_zero = Object::Integer(0); -        let type_str = field.get("FT").unwrap().as_name_str().unwrap(); +        let field = self +            .doc +            .objects +            .get(&self.form_ids[n]) +            .unwrap() +            .as_dict() +            .unwrap(); + +        let type_str = field.get(b"FT").unwrap().as_name_str().unwrap();          if type_str == "Btn" { -            let flags = ButtonFlags::from_bits_truncate(field.get("Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32); -            if flags.intersects(ButtonFlags::RADIO) { +            let flags = ButtonFlags::from_bits_truncate(get_field_flags(field)); +            if flags.intersects(ButtonFlags::RADIO | ButtonFlags::NO_TOGGLE_TO_OFF) {                  FieldType::Radio              } else if flags.intersects(ButtonFlags::PUSHBUTTON) {                  FieldType::Button @@ -188,14 +203,37 @@ impl Form {                  FieldType::CheckBox              }          } else if type_str == "Ch" { -            let flags = ChoiceFlags::from_bits_truncate(field.get("Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32); +            let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field));              if flags.intersects(ChoiceFlags::COBMO) {                  FieldType::ComboBox              } else {                  FieldType::ListBox              } -        } else { +        } else if type_str == "Tx" {              FieldType::Text +        } else { +            FieldType::Unknown +        } +    } + +    /// Gets the name of field of the given index +    /// +    /// # Panics +    /// This function will panic if the index is greater than the number of fields +    pub fn get_name(&self, n: usize) -> Option<String> { +        // unwraps should be fine because load should have verified everything exists +        let field = self +            .doc +            .objects +            .get(&self.form_ids[n]) +            .unwrap() +            .as_dict() +            .unwrap(); + +        // The "T" key refers to the name of the field +        match field.get(b"T") { +            Ok(Object::String(data, _)) => String::from_utf8(data.clone()).ok(), +            _ => None,          }      } @@ -204,7 +242,16 @@ impl Form {          let mut res = Vec::with_capacity(self.len());          for i in 0..self.len() {              res.push(self.get_type(i)) -        }; +        } +        res +    } + +    /// Gets the names of all of the fields in the form +    pub fn get_all_names(&self) -> Vec<Option<String>> { +        let mut res = Vec::with_capacity(self.len()); +        for i in 0..self.len() { +            res.push(self.get_name(i)) +        }          res      } @@ -213,151 +260,176 @@ impl Form {      /// # Panics      /// This function will panic if the index is greater than the number of fields      pub fn get_state(&self, n: usize) -> FieldState { -        let field = self.doc.objects.get(&self.form_ids[n]).unwrap().as_dict().unwrap(); +        let field = self +            .doc +            .objects +            .get(&self.form_ids[n]) +            .unwrap() +            .as_dict() +            .unwrap();          match self.get_type(n) {              FieldType::Button => FieldState::Button,              FieldType::Radio => FieldState::Radio { -                selected: match field.get("V") { -                    Some(name) => name.as_name_str().unwrap().to_owned(), -                    None => match field.get("AS") { -                        Some(name) => name.as_name_str().unwrap().to_owned(), -                        None => "".to_owned() -                    } +                selected: match field.get(b"V") { +                    Ok(name) => name.as_name_str().unwrap().to_owned(), +                    _ => match field.get(b"AS") { +                        Ok(name) => name.as_name_str().unwrap().to_owned(), +                        _ => "".to_owned(), +                    },                  }, -                options: self.get_possibilities(self.form_ids[n]) +                options: self.get_possibilities(self.form_ids[n]), +                readonly: is_read_only(field), +                required: is_required(field),              }, -            FieldType::CheckBox=> FieldState::CheckBox { is_checked: -                match field.get("V") { -                    Some(name) => if name.as_name_str().unwrap() == "Yes" { true } else { false }, -                    None => match field.get("AS") { -                        Some(name) => if name.as_name_str().unwrap() == "Yes" { true } else { false }, -                        None => false -                    } -                } +            FieldType::CheckBox => FieldState::CheckBox { +                is_checked: match field.get(b"V") { +                    Ok(name) => name.as_name_str().unwrap() == "Yes", +                    _ => match field.get(b"AS") { +                        Ok(name) => name.as_name_str().unwrap() == "Yes", +                        _ => false, +                    }, +                }, +                readonly: is_read_only(field), +                required: is_required(field),              },              FieldType::ListBox => FieldState::ListBox {                  // V field in a list box can be either text for one option, an array for many                  // options, or null -                selected: match field.get("V") { -                    Some(selection) => match selection { -                        &Object::String(ref s,StringFormat::Literal) => vec![str::from_utf8(&s).unwrap().to_owned()], -                        &Object::Array(ref chosen) => { +                selected: match field.get(b"V") { +                    Ok(selection) => match *selection { +                        Object::String(ref s, StringFormat::Literal) => { +                            vec![str::from_utf8(&s).unwrap().to_owned()] +                        } +                        Object::Array(ref chosen) => {                              let mut res = Vec::new();                              for obj in chosen { -                                if let &Object::String(ref s,StringFormat::Literal) = obj { +                                if let Object::String(ref s, StringFormat::Literal) = *obj {                                      res.push(str::from_utf8(&s).unwrap().to_owned());                                  }                              }                              res                          } -                        _ => Vec::new() +                        _ => Vec::new(),                      }, -                    None => Vec::new() +                    _ => Vec::new(),                  },                  // The options is an array of either text elements or arrays where the second                  // element is what we want -                options: match field.get("Opt") { -                    Some(&Object::Array(ref options)) => options.iter().map(|x| { -                        match x { -                            &Object::String(ref s,StringFormat::Literal) => str::from_utf8(&s).unwrap().to_owned(), -                            &Object::Array(ref arr) => if let &Object::String(ref s,StringFormat::Literal) = &arr[1] { +                options: match field.get(b"Opt") { +                    Ok(&Object::Array(ref options)) => options +                        .iter() +                        .map(|x| match *x { +                            Object::String(ref s, StringFormat::Literal) => {                                  str::from_utf8(&s).unwrap().to_owned() -                            } else { -                                String::new() -                            }, -                            _ => String::new() -                        } -                    }).filter(|x| x.len() > 0).collect(), -                    _ => Vec::new() +                            } +                            Object::Array(ref arr) => { +                                if let Object::String(ref s, StringFormat::Literal) = &arr[1] { +                                    str::from_utf8(&s).unwrap().to_owned() +                                } else { +                                    String::new() +                                } +                            } +                            _ => String::new(), +                        }) +                        .filter(|x| !x.is_empty()) +                        .collect(), +                    _ => Vec::new(),                  },                  multiselect: { - -                    let flags = ChoiceFlags::from_bits_truncate(field.get("Ff").unwrap().as_i64().unwrap() as u32); +                    let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field));                      flags.intersects(ChoiceFlags::MULTISELECT) -                } +                }, +                readonly: is_read_only(field), +                required: is_required(field),              },              FieldType::ComboBox => FieldState::ComboBox {                  // V field in a list box can be either text for one option, an array for many                  // options, or null -                selected: match field.get("V") { -                    Some(selection) => match selection { -                        &Object::String(ref s,StringFormat::Literal) => vec![str::from_utf8(&s).unwrap().to_owned()], -                        &Object::Array(ref chosen) => { +                selected: match field.get(b"V") { +                    Ok(selection) => match *selection { +                        Object::String(ref s, StringFormat::Literal) => { +                            vec![str::from_utf8(&s).unwrap().to_owned()] +                        } +                        Object::Array(ref chosen) => {                              let mut res = Vec::new();                              for obj in chosen { -                                if let &Object::String(ref s,StringFormat::Literal) = obj { +                                if let Object::String(ref s, StringFormat::Literal) = *obj {                                      res.push(str::from_utf8(&s).unwrap().to_owned());                                  }                              }                              res                          } -                        _ => Vec::new() +                        _ => Vec::new(),                      }, -                    None => Vec::new() +                    _ => Vec::new(),                  },                  // The options is an array of either text elements or arrays where the second                  // element is what we want -                options: match field.get("Opt") { -                    Some(&Object::Array(ref options)) => options.iter().map(|x| { -                        match x { -                            &Object::String(ref s,StringFormat::Literal) => str::from_utf8(&s).unwrap().to_owned(), -                            &Object::Array(ref arr) => if let &Object::String(ref s,StringFormat::Literal) = &arr[1] { +                options: match field.get(b"Opt") { +                    Ok(&Object::Array(ref options)) => options +                        .iter() +                        .map(|x| match *x { +                            Object::String(ref s, StringFormat::Literal) => {                                  str::from_utf8(&s).unwrap().to_owned() -                            } else { -                                String::new() -                            }, -                            _ => String::new() -                        } -                    }).filter(|x| x.len() > 0).collect(), -                    _ => Vec::new() +                            } +                            Object::Array(ref arr) => { +                                if let Object::String(ref s, StringFormat::Literal) = &arr[1] { +                                    str::from_utf8(&s).unwrap().to_owned() +                                } else { +                                    String::new() +                                } +                            } +                            _ => String::new(), +                        }) +                        .filter(|x| !x.is_empty()) +                        .collect(), +                    _ => Vec::new(),                  }, -                multiselect: { +                editable: { +                    let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field)); -                    let flags = ChoiceFlags::from_bits_truncate(field.get("Ff").unwrap().as_i64().unwrap() as u32); -                    flags.intersects(ChoiceFlags::MULTISELECT) -                } +                    flags.intersects(ChoiceFlags::EDIT) +                }, +                readonly: is_read_only(field), +                required: is_required(field),              }, -            FieldType::Text => FieldState::Text{ text: -                match field.get("V") { -                    Some(&Object::String(ref s,StringFormat::Literal)) => -                        str::from_utf8(&s.clone()).unwrap().to_owned(), -                    _ => "".to_owned() -                } - -            } +            FieldType::Text => FieldState::Text { +                text: match field.get(b"V") { +                    Ok(&Object::String(ref s, StringFormat::Literal)) => { +                        str::from_utf8(&s.clone()).unwrap().to_owned() +                    } +                    _ => "".to_owned(), +                }, +                readonly: is_read_only(field), +                required: is_required(field), +            }, +            FieldType::Unknown => FieldState::Unknown,          }      } -      /// If the field at index `n` is a text field, fills in that field with the text `s`.      /// If it is not a text field, returns ValueError      ///      /// # Panics      /// Will panic if n is larger than the number of fields -    pub fn set_text(&mut self, n: usize, s: String) -> Result<(),ValueError> { -        match self.get_type(n) { -            FieldType::Text => { -                let field = self.doc.objects.get_mut(&self.form_ids[n]).unwrap().as_dict_mut().unwrap(); -                field.set("V",Object::String(s.into_bytes(),StringFormat::Literal)); -                field.remove("AP"); -                Ok(()) -            }, -            _ => Err(ValueError::TypeMismatch) - -        } -    } +    pub fn set_text(&mut self, n: usize, s: String) -> Result<(), ValueError> { +        match self.get_state(n) { +            FieldState::Text { .. } => { +                let field = self +                    .doc +                    .objects +                    .get_mut(&self.form_ids[n]) +                    .unwrap() +                    .as_dict_mut() +                    .unwrap(); + +                field.set("V", Object::String(s.into_bytes(), StringFormat::Literal)); +                field.remove(b"AP"); -    fn get_possibilities(&self, oid: ObjectId) -> Vec<String> { -        let mut res = Vec::new(); -        let kids_obj = self.doc.objects.get(&oid).unwrap().as_dict().unwrap().get("Kids"); -        if let Some(&Object::Array(ref kids)) = kids_obj { -            for kid in kids { -                if let Some(&Object::Name(ref s)) = kid.deref(&self.doc).unwrap().as_dict().unwrap().get("AS") { -                    res.push(str::from_utf8(&s).unwrap().to_owned()); -                } +                Ok(())              } +            _ => Err(ValueError::TypeMismatch),          } -        res      }      /// If the field at index `n` is a checkbox field, toggles the check box based on the value @@ -366,17 +438,34 @@ impl Form {      ///      /// # Panics      /// Will panic if n is larger than the number of fields -    pub fn set_check_box(&mut self, n: usize, is_checked: bool) -> Result<(),ValueError> { -        match self.get_type(n) { -            FieldType::CheckBox => { -                let state = Object::Name({if is_checked {"Yes"} else {"Off"}}.to_owned().into_bytes()); -                let field = self.doc.objects.get_mut(&self.form_ids[n]).unwrap().as_dict_mut().unwrap(); -                field.set("V",state.clone()); -                field.set("AS",state); -                Ok(()) -            }, -            _ => Err(ValueError::TypeMismatch) +    pub fn set_check_box(&mut self, n: usize, is_checked: bool) -> Result<(), ValueError> { +        match self.get_state(n) { +            FieldState::CheckBox { .. } => { +                let state = Object::Name( +                    { +                        if is_checked { +                            "Yes" +                        } else { +                            "Off" +                        } +                    } +                    .to_owned() +                    .into_bytes(), +                ); +                let field = self +                    .doc +                    .objects +                    .get_mut(&self.form_ids[n]) +                    .unwrap() +                    .as_dict_mut() +                    .unwrap(); + +                field.set("V", state.clone()); +                field.set("AS", state); +                Ok(()) +            } +            _ => Err(ValueError::TypeMismatch),          }      } @@ -386,53 +475,160 @@ impl Form {      ///      /// # Panics      /// Will panic if n is larger than the number of fields -    pub fn set_radio(&mut self, n: usize, choice: String) -> Result<(),ValueError> { +    pub fn set_radio(&mut self, n: usize, choice: String) -> Result<(), ValueError> {          match self.get_state(n) { -            FieldState::Radio { selected: _,  options } => if options.contains(&choice) { -                let field = self.doc.objects.get_mut(&self.form_ids[n]).unwrap().as_dict_mut().unwrap(); -                field.set("V",Object::Name(choice.into_bytes())); -                Ok(()) -            } else { -                Err(ValueError::InvalidSelection) -            }, -            _ => Err(ValueError::TypeMismatch) - +            FieldState::Radio { options, .. } => { +                if options.contains(&choice) { +                    let field = self +                        .doc +                        .objects +                        .get_mut(&self.form_ids[n]) +                        .unwrap() +                        .as_dict_mut() +                        .unwrap(); +                    field.set("V", Object::Name(choice.into_bytes())); +                    Ok(()) +                } else { +                    Err(ValueError::InvalidSelection) +                } +            } +            _ => Err(ValueError::TypeMismatch),          }      } -    /// If the field at index `n` is a listbox or comboox field, selects the options in `choice` -    /// If it is not a listbox or combobox field or one of the choices is not a valid option, or if too many choices are selected, returns ValueError +    /// If the field at index `n` is a listbox field, selects the options in `choice` +    /// If it is not a listbox field or one of the choices is not a valid option, or if too many choices are selected, returns ValueError      ///      /// # Panics      /// Will panic if n is larger than the number of fields -    pub fn set_choice(&mut self, n: usize, choices: Vec<String>) -> Result<(),ValueError> { +    pub fn set_list_box(&mut self, n: usize, choices: Vec<String>) -> Result<(), ValueError> {          match self.get_state(n) { -            FieldState::ListBox { selected: _, options, multiselect } | FieldState::ComboBox { selected: _, options, multiselect } => if choices.iter().fold(true, |a,h| options.contains(h) && a) { -                if !multiselect && choices.len() > 1 { -                    Err(ValueError::TooManySelected) +            FieldState::ListBox { +                options, +                multiselect, +                .. +            } => { +                if choices.iter().fold(true, |a, h| options.contains(h) && a) { +                    if !multiselect && choices.len() > 1 { +                        Err(ValueError::TooManySelected) +                    } else { +                        let field = self +                            .doc +                            .objects +                            .get_mut(&self.form_ids[n]) +                            .unwrap() +                            .as_dict_mut() +                            .unwrap(); +                        match choices.len() { +                            0 => field.set("V", Object::Null), +                            1 => field.set( +                                "V", +                                Object::String( +                                    choices[0].clone().into_bytes(), +                                    StringFormat::Literal, +                                ), +                            ), +                            _ => field.set( +                                "V", +                                Object::Array( +                                    choices +                                        .iter() +                                        .map(|x| { +                                            Object::String( +                                                x.clone().into_bytes(), +                                                StringFormat::Literal, +                                            ) +                                        }) +                                        .collect(), +                                ), +                            ), +                        }; +                        Ok(()) +                    }                  } else { -                    let field = self.doc.objects.get_mut(&self.form_ids[n]).unwrap().as_dict_mut().unwrap(); -                    match choices.len() { -                        0 => field.set("V", Object::Null), -                        1 => field.set("V", Object::String(choices[0].clone().into_bytes(), -                          StringFormat::Literal)), -                        _ => field.set("V", Object::Array(choices.iter().map(|x| Object::String(x.clone().into_bytes(),StringFormat::Literal)).collect())) - -                    }; -                    Ok(()) +                    Err(ValueError::InvalidSelection)                  } - -            } else { -                Err(ValueError::InvalidSelection) -            }, -            _ => Err(ValueError::TypeMismatch) - +            } +            _ => Err(ValueError::TypeMismatch),          }      } +    /// If the field at index `n` is a combobox field, selects the options in `choice` +    /// If it is not a combobox field or one of the choices is not a valid option, or if too many choices are selected, returns ValueError +    /// +    /// # Panics +    /// Will panic if n is larger than the number of fields +    pub fn set_combo_box(&mut self, n: usize, choice: String) -> Result<(), ValueError> { +        match self.get_state(n) { +            FieldState::ComboBox { +                options, editable, .. +            } => { +                if options.contains(&choice) || editable { +                    let field = self +                        .doc +                        .objects +                        .get_mut(&self.form_ids[n]) +                        .unwrap() +                        .as_dict_mut() +                        .unwrap(); +                    field.set( +                        "V", +                        Object::String(choice.into_bytes(), StringFormat::Literal), +                    ); +                    Ok(()) +                } else { +                    Err(ValueError::InvalidSelection) +                } +            } +            _ => Err(ValueError::TypeMismatch), +        } +    }      /// Saves the form to the specified path -    pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(),io::Error> { +    pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(), io::Error> {          self.doc.save(path).map(|_| ())      } + +    /// Saves the form to the specified path +    pub fn save_to<W: Write>(&mut self, target: &mut W) -> Result<(), io::Error> { +        self.doc.save_to(target) +    } + +    fn get_possibilities(&self, oid: ObjectId) -> Vec<String> { +        let mut res = Vec::new(); +        let kids_obj = self +            .doc +            .objects +            .get(&oid) +            .unwrap() +            .as_dict() +            .unwrap() +            .get(b"Kids"); +        if let Ok(&Object::Array(ref kids)) = kids_obj { +            for (i, kid) in kids.iter().enumerate() { +                let mut found = false; +                if let Ok(&Object::Dictionary(ref appearance_states)) = +                    kid.deref(&self.doc).unwrap().as_dict().unwrap().get(b"AP") +                { +                    if let Ok(&Object::Dictionary(ref normal_appearance)) = +                        appearance_states.get(b"N") +                    { +                        for (key, _) in normal_appearance { +                            if key != b"Off" { +                                res.push(from_utf8(key).unwrap_or("").to_owned()); +                                found = true; +                                break; +                            } +                        } +                    } +                } + +                if !found { +                    res.push(i.to_string()); +                } +            } +        } + +        res +    }  } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..7ff4e41 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,49 @@ +use lopdf::{Dictionary, Object}; + +bitflags! { +    pub struct FieldFlags: u32 { +        const READONLY          = 0x1; +        const REQUIRED          = 0x2; +    } +} + +bitflags! { +    pub struct ButtonFlags: u32 { +        const NO_TOGGLE_TO_OFF  = 0x8000; +        const RADIO             = 0x10000; +        const PUSHBUTTON        = 0x20000; +        const RADIO_IN_UNISON   = 0x4000000; + +    } +} + +bitflags! { +    pub struct ChoiceFlags: u32 { +        const COBMO             = 0x20000; +        const EDIT              = 0x40000; +        const SORT              = 0x80000; +        const MULTISELECT       = 0x200000; +        const DO_NOT_SPELLCHECK = 0x800000; +        const COMMIT_ON_CHANGE  = 0x8000000; +    } +} + +pub fn is_read_only(field: &Dictionary) -> bool { +    let flags = FieldFlags::from_bits_truncate(get_field_flags(field)); + +    flags.intersects(FieldFlags::READONLY) +} + +pub fn is_required(field: &Dictionary) -> bool { +    let flags = FieldFlags::from_bits_truncate(get_field_flags(field)); + +    flags.intersects(FieldFlags::REQUIRED) +} + +pub fn get_field_flags(field: &Dictionary) -> u32 { +    field +        .get(b"Ff") +        .unwrap_or(&Object::Integer(0)) +        .as_i64() +        .unwrap() as u32 +} | 
