I was recently tasked with building out hundreds of pages of pdf based forms into online accessible “smart-forms” complete with text-replacement, expanding content and usable inputs (text, datepickers, pre-populated fields, selects, etc…).
At first, I started to manually build out each of the forms as html/css partials with AngularJS interpolation to do the text replacement. I used Angular directives to accomplish any advanced functionality my inputs needed. Building out the forms manually turned out to be a huge undertaking. Simple pages would take ~45 minutes to build, and more extreme forms would take over 3 hours per page. There had to be some other way.
While talking with another developer about a similar system he’d build, he mentioned absolutely positioning the inputs over a screenshot of the form pdf. Perfect! Well, almost. Using a static image wasn’t an option since I needed to do things like text-replacement and text color changes. However, using svgs would be the perfect solution. An svg would allow me to use Angular interpolation for text replacement, change text color using ng-class
and build expanding sections by injecting html in-between the svg objects.
The first thing I needed to do was convert all of the pdf pages into svgs. I also needed to remove any explicit width
and height
attributes on the svg elements and replace them with a viewBox
attribute. Thanks to inkscape, I was able to whip up a quick shell script to convert every page file into a corresponding svg:
#!/usr/bin/zsh
for file in *.pdf; do
inkscape $file --export-plain-svg=$file.svg
sed -i 's/width=".*"//g' $file.svg
sed -i 's/height=".*"/viewBox="0 0 765.75 990.75"/g' $file.svg
done
rename -v 's/pdf\.svg/svg/' *.pdf.svg
While this isn’t the prettiest script in the world, it saved me a huge chunk of time.
After creating all of my svgs, I needed to go through and find where my text replacement and logic-based coloring needed to happen. I probably could have done this with sed
, but I ended up doing it by hand fairly quickly.
Below is an example of how I modified the svg generated by inkscape, and the corresponding css example:
<text transform="matrix(1,0,0,-1,195.09,397.14)" id="text278">
<tspan
x="0"
y="0"
id="tspan280"
ng-class="{'invalid': form.name.$invalid}"
style="font-size:11.00016022px;font-variant:normal;font-weight:normal; ... ">
Your name is {{data.name}}, and this is an example!
</tspan>
</text>
.submitted .invalid {
fill: #C00000;
}
The one downside to this technique is that there is no word wrapping support. If your interpolated string is too long, the line will simple run off the document. I played with using the foreignObject element to inject html into my svg, but it was not supported in IE.
For each form I wrote an html partial to pull in each page and to hold all of the inputs. Sublime Text snippets helped speed up the process of creating all of these partials:
<div class="page">
<div class="svg-wrapper">
<!-- page 1 inputs go here -->
<div ng-include="'forms/svg/formPage1.svg'" class="svg-include"></div>
</div>
</div>
<div class="page">
<div class="svg-wrapper">
<!-- page 2 inputs go here -->
<div ng-include="'forms/svg/formPage2.svg'" class="svg-include"></div>
</div>
</div>
The svg-wrapper
class has a display: relative
, while each of the inputs have display: absolute
.
The next step in the process was to manually position each of the inputs. It didn’t take long to realize that absolutely positioning each element by hand would take a considerable amount of time (although it was still faster than the original solution). I decided to build a tool to help me out.
I hacked together a very simple draggable Angular directive that let me click and drag to define input boxes. After a few iterations I had a tool that reduced the time to lay out a complex page from hours to just minutes. Honestly, the tool’s code is some of the worst I’ve written in years, but I think that’s what makes it amazing. Haphazardly built, poorly functioning code saved me from billing hundreds of hours of work to my client and produced a better result in the end. I’d call that a success!
Here’s a quick screencast of the tool working on an example W-9 form: