Responsive SVG Height Issue

Written by Pete Corey on Sep 9, 2014.

You may have noticed, but I’ve been having some issues with the svg logo I’m using for this blog. I’m not specifiying explicit height or width attributes on the svg element, but I am setting a viewBox attribute. This lets me specify the height and/or width of the element through my css. If only the width or height is specified, the other attribute will scale accordingly, preserving the aspect ratio of the svg. After some feedback and testing with browserstack, I found out that this wasn’t behaving as I thought it would on some browsers.

1pxsolidtomato

When the logo is displayed in the navbar, I indirectly set the width to 150px (by setting the wrapping container’s width to 150px). Because the viewBox is set to "0 0 100 25", I would expect the height of the rendered svg to be 38px (0.25 * 150px). This worked as expected in Chrome 37 and Firefox 31 on my windows machine. But strangely, in IE 11 the height of the svg element was set to 150px. Even more strangely, in Chrome 37 and Safari 7 on OSX the height of the svg seemed to be stretching to over 1000px.

The fix was very simple. Setting a max-height of 100% on the svg element will correctly set the height of the svg on all browsers.

svg {
    width: 150px;
    max-height: 100%;
}

Honestly, I’m not entirely sure why this was happening. If I had to guess, I would assume it had something to do with this WebKit bug.

Smart Forms - Automate and Build Your Own Tools!

Written by Pete Corey on Sep 4, 2014.

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:

My Concurrent Jekyll Gruntfile

Written by Pete Corey on Aug 28, 2014.

I wanted to have a single default grunt command kick off my Jekyll server (jekyll serve --watch), and my grunt watch task. After sleuthing around StackOverflow, I found a solution using grunt-jekyll and grunt-concurrent. Concurrency is needed to prevent the jekyll server from blocking.

module.exports = function(grunt) {
    grunt.initConfig({
        jekyll: {
            serve: {
                options: {
                    serve: true,
                    watch: true
                }
            }
        },
        less: {
            development: {
                options: {
                    paths: ['./less'],
                    yuicompress: true
                },
                files: {
                    'css/main.css': 'less/main.less'
                }
            }
        },
        watch: {
            less: {
                files: ['less/**/*.less'],
                tasks: ['less']
            }
        },
        concurrent: {
            all: {
                tasks: ['jekyll:serve', 'watch'],
                options: {
                    logConcurrentOutput: true
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-jekyll');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-concurrent');

    grunt.registerTask('default', ['concurrent:all']);
};