What Size? 10 Sep 2012
My main “nights and weekends” project lately has been a mobile application I’m calling “Unravled.” It’s a knitting counter, written entirely in client-side Javascript, and packaged using PhoneGap as an Android (and, maybe later, iOS) app.
One of the interesting side-effects of using HTML in a WebView to render your application is that, by default, Android will automatically scale all the HTML elements one higher (or lower) resolution screens. I didn’t think much of this until I started actually testing the app on my Galaxy Nexus, which has a 1.5x scaling factor.
All the text is beautiful and crisp, but the icons are horrible! The
trick to making everything look good is to disable the automatic
scaling, via a meta tag, and scale it all yourself. For me, the
first step was to try to scale the application icon, which I had drawn
in Inkscape.
![]()
The easiest thing to do is just to export the icon in the appropriate sizes for 1x, 1.5x and 2x scaling (there is also a “low-resolution” 0.75x scaling, but I decided not to support it). One of the keys to keeping the lines clean and sharp is to align any vertical or horizontal lines to an even pixel boundary at the exported size, so that you can keep anti-aliasing on for the curves, but not end up with any fuzzy gray areas near the sharp edges. That’s (relatively) simple to do when you are only targeting 1x and 2x scaling (but you have half the pixel boundary positions to work with), but it gets much harder when you also want to target 1.5x scaling. There are very few pixel boundaries that are shared between a 32, 48 and 64 pixel wide icon.
I played around with the idea of “hinting” my icon (by automatically moving the vertexes when exporting it at different sizes), but then I found FontForge, which is a full-featured, open source font editor. The nice thing about fonts is that the built-in font renderer will already handle rendering the font with “hints,” and FontForge can embedded (and even auto-generate) the hints for you. I’ll need to save the details of creating the font for another post, but I was able to convert my custom icon, plus the icons I was using from an existing SVG icon set, to a font in an afternoon. As an added bonus, using fonts for icons lets you change the color and size of the icons easily in your CSS, instead of modifying and re-exporting your SVGs.
Once I had the icons out of the way, the next problem was the CSS. When auto-scaling is off, WebKit will automatically multiply any absolute sizes defined in your CSS by whatever the scaling factor is. Once you turn it off, you have to manage the different absolute sizes yourself. Additionally, with scaling on, any CSS media queries will “lie” and say that the viewport is smaller than it actually is.
The biggest problem here was that I am using Bootstrap, which, while
very cool, specifies a lot of things in absolute pixels (especially
for the grid system), and all of those sizes needed to be
scaled. While playing around with the images, I tried scaling the
navbar using less mixins, which worked reasonably well, but required
a lot of typing: every element I wanted to scale had to be
re-calculated in my master CSS, assuming it was defined as a mixin in
the first place, which many elements aren’t.
I knew I wanted to implement the scaling at “build time” so that I
could easily add my own custom CSS, or upgrade Bootstrap, without
going through an involved upgrade process. My first thought was to
take advantage of less and the fact that I was already recompiling
all the less code at each build. When you invoke the less compiler
via Javascript, you actually get hooks into the various stages of the
compilation pipeline, so you can get the AST that the parser generates
and operate on it, before compiling it down to CSS. The problem here
is that the AST is entirely undocumented, and probably subject to
change. Additionally, there are different ways that the AST represents
numbers: sometimes as a string value (i.e. 16px) but sometimes as
the result of a computation. In that case it’s a sub-tree representing
the expression, with the leaves being either variable references or
numeric literals, which are JS objects containing an integer value and
a string unit.
In the end, that was all very complicated. Once you get down to the compiled CSS, you just need to multiply all the values that are specified in “px” by whatever the scaling factor is. Since Node isn’t very handy for transforming files (the standard library doesn’t even offer a means to read a file line-by-line), I fell back on this simple Python script:
import sys
import re
pixels = re.compile(r'(\d+)(px|\.png)')
scale = float(sys.argv[3])
with open(sys.argv[1], 'r') as input, open(sys.argv[2], 'w') as output:
    for line in input:
        line = pixels.sub(
            lambda m: '{0}{1}'.format(
                int(int(m.group(1)) * scale), 
                m.group(2)), 
            line)
        
        output.write(line)Sometimes the simplest solution is the best! The lambda is a little messy, and should probably get converted to a named function. It wouldn’t be too bad, except for the need to convert to an int so many times. Checking for “.png” files was a holdover from using SVGs rendered at different scales. It lets you specify file names like: foo32.png in the source (1x scaled) CSS, and have it converted to foo48.png in the 1.5x CSS file.
Once I had the images and the CSS scaled, I was pretty much done. This would definitely be a lot trickier if you were specifying any sizes in your HTML (especially in your HTML templates), but you shouldn’t be doing that, now, should you?
comments powered by Disqus