Fill in a form and save it - #10
Merged
Merged
Conversation
Filling in a form is not like the other verbs. The rest of this package takes pages apart and puts them together again, and what it does not understand it leaves behind. A form cannot be treated that way: it is tied into the document by object number in a dozen places at once — the field tree, the widget annotations on the pages, the resources the appearances name — and a document rebuilt around it would have to rebuild all of that correctly or quietly break it. So a filled form is written as an incremental update: the original file, byte for byte, with the objects that changed appended after it and a new cross-reference section pointing back at the old one. Nothing already there is rewritten, so everything this does not understand survives untouched, and if the update is wrong the original is still the first part of the file. The update has to say where its objects went the same way the file already does. A file whose cross-reference is a stream cannot be pointed back at by a plain table: a reader following /Prev finds an object where it expects the word "xref". That is not a nicety. The first version wrote a plain table either way, and our own reader was lenient enough to read it back perfectly — while macOS's renderer drew nothing at all for the file. That is how it was found, and it is why a filled form is now put in front of another renderer rather than only read back by this one. Two things it will not do, and says so: a document that had to be repaired to be read has no section worth pointing back at, and a document written through a key would need everything appended to it written through that key too. Measured on a real form. fw9.pdf, every field filled, written out, read back with all 23 values in place and all 23 widgets carrying a drawing — and rendered by macOS, which drew what we drew, in the same places, comb cells and ticks included. Its own rendering moved by 0.0028 when we filled the form in, so it saw the values; our distance from it moved 0.1095 to 0.1131, which is the same order, and the 0.11 itself is our text against Quartz's on a dense form and not a thing about forms at all. pdfops gains "fields", which lists what a form asks for and what it holds, and "fill". Running the lister at a real form is what found that field names are written in UTF-16 and were coming back as bytes — unreadable, and worse, unmatchable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ops.OpenFormreads a document's form,Fillsets a field,Byteswrites the result.pdfops fieldslists what a form asks for and what it holds;pdfops fillfills it in.Why an incremental update
Every other verb here takes pages apart and builds a new document round them. A form cannot be treated that way: it is tied into the document by object number in a dozen places at once — the field tree, the widget annotations on the pages, the resources the appearances name.
So a filled form is written as the original file, byte for byte, with the changed objects appended and a new cross-reference section pointing back at the old one. That is how every program that saves a form saves one, and it is the safest thing a program can do to somebody's document.
The defect this found, and how
The first version wrote a plain
xreftable whatever the file already used. Our own reader read it back perfectly. macOS's renderer drew nothing at all — because a file whose cross-reference is a stream cannot be pointed back at by a plain table: a reader following/Prevfinds an object where it expects the wordxref.That is the argument for putting output in front of a second renderer rather than only reading it back with the one that wrote it.
Measured
fw9.pdf, a real IRS form: every field filled, written out, read back with all 23 values in place and all 23 widgets carrying a drawing, and rendered by macOS.The 0.11 baseline is our text against Quartz's on a dense form; it is the same before and after, so it says nothing about forms.
Running
pdfops fieldsat that form is also what found field names coming back as raw UTF-16 bytes — unreadable and unmatchable (fixed informsv0.2.1).Stated plainly
100% statement coverage in both the package and the command,
go vetclean, nine cross-compile targets.