Rendering Text Inside the Canvas Object #

I recently had an idea for a cool hack involving the <canvas> tag/object that is supported by Safari 1.3/2.0, Firefox 1.5 and Opera 9.0. However, I quickly realized that the object does not support text rendering, which made it seemingly useless. The WHATWG spec had this to say:

// drawing text is not supported in this version of the API
// (there is no way to predict what metrics the fonts will have,
// which makes fonts very hard to use for painting)

I'm not entirely sure why predictable font metrics are necessary (for pixel-perfect compliance testing I suppose), but the situation doesn't seem too hopeful. Mozilla's solution to this was the drawWindow, function that could be used with an iframe to render text. However, creating arbitrary windows for text rendering seemed like a lot of overhead, and I wanted something that worked in all browsers.

I remembered from my OpenGL hacking days that a similar shortcoming existed in that environment, and that there were a few workarounds available. One was to render fonts yourself, using the basic line/arc primitives to rasterize TrueType/PostScript fonts. However, this meant finding a font and mapping its drawing operations to canvas ones, which seemed like more work than I was willing to put in for a quick hack. Additionally, having to draw many complex strokes per letter seemed like it would impact performance.

The alternative was to use a font texture. This is usually an image composed of all the necessary letters and symbols for a font. By drawing pieces of it one after another, words can be composed. Since the font has been already rasterized into the texture image, it shouldn't matter how complex each letter is. This also mapped well onto the drawImage call supported by the canvas 2D context. Some quick Googling turned up a ready-made font texture and (more importantly) a table of character coordinates positions within it. If doing this from scratch, Bitmap Font Builder looks handy.

This is the result of putting all of that together. It has decent performance in Safari and Opera 9.0, but Firefox can only manage about ten frames per second. It was even slower when I used drawImage() with an image object. I can only assume that Gecko will decompress the image for each function call instead of keeping the raw pixels around. Thankfully there is an overloaded version of the function that accepts a canvas object. By rendering the desired image into a canvas first and then passing that, performance improved significantly. However, Safari does not seem to support canvas objects as arguments for drawImage(), so a bit of browser detection is necessary.

Update on 2/27/2006: I was curious about the drawImage() performance in Firefox with images vs. canvases that I decided to do a more thorough investigation. Using a simple test bed, I measured the speed of various image rendering calls:

  • drawImage() with an image argument
  • drawImage() with a canvas argument
  • Creating a pattern with an image and then using fillRect() with it
  • Creating a pattern with a canvas and then using fillRect() with it

I then ran 50 iterations of each in Firefox 1.5, Safari 2.0 and Opera 9.0 preview 2, all on a dual 2.3 Ghz G5, with these results:

Method 8-bit opqaue GIF 8-bit transparent GIF* 8-bit opaque PNG* 8-bit transparent PNG* 24-bit opaque PNG 24-bit transparent PNG JPEG
Firefox 1.5
drawImage w/ image 74 138 593 574 242 1959 227
drawImage w/ canvas 446 4378 4433 4444 443 495 454
fillRect w/ pattern w/ image 10 22 75 33 32 118 27
fillRect w/ pattern w/ canvas err err err err err err err
Safari 2.0
drawImage w/ image 15 27 97 34 47 123 62
drawImage w/ canvas NV NV NV NV NV NV NV
fillRect w/ pattern w/ image NV NV NV NV NV NV NV
fillRect w/ pattern w/ canvas err err err err err err err
Opera 9.0 preview 2
drawImage w/ image 521 273 3313 880 1651 4007 err
drawImage w/ canvas 3817 37612 37186 38024 3753 3862 err
fillRect w/ pattern w/ image 3773 36019 36735 37303 3709 3571 err
fillRect w/ pattern w/ canvas NV NV NV NV NV NV err

* 500 iterations
NV: No visible output (but no exceptions thrown either)

As it can be seen, the Firefox performance boost that I saw with drawImage() and a canvas argument only occurs with 24-bit PNGs with an alpha channel. In general, using a pattern is the fastest way to draw an image in Firefox. The one trade-off is that you don't get to use scaling (by playing with the source/destination rectangles), but that can be accomplished with the global matrix transform anyway. Since paterns are always drawn beginning at the top/left corner of the target rectangle, some use of clipping is necesary if only a portion of the image is necessary. However, even with clipping, the use of patterns brings Firefox speed in the text rendering test to ~36 fps instead of ~10 fps.

The Opera numbers are much lower than the others because Opera seems to do some event handling and extra screen refreshes during the benchmark. In general, the fastest approach in Opera is to use drawImage() with a canvas object. Safari seems to have the most trouble supporting alternate approaches, presumably because it had the earliest implementation of canvas and the spec didn't actually exist at that point.

Google Search History as RSS #

Update on 4/24/2006: The feeds have now been officially announced (i.e. they are exposed via auto-discovery on the page). HTTP Basic Authentication (over SSL) is now also supported (in addition to the cookie).

Google recently released some Dashboard Widgets, among them one for accessing your search history. Until now, it had only been accessible at its homepage, so I wondered how the widget got that data out. Thankfully the widget code was not obfuscated, and I was able to see snippets like the following:

  Widget.feed = "http://www.google.com/searchhistory/find?zx=" + 
              randomString() + "&num=50&output=rss&client=google-mcsmhwidget";
  ...
  var url = Widget.feed;
  url += "&start=" + Widget.resultsStart;
  url += "&q=" + encodeURIComponent(query);

Sure enough, URLs such as this one bring up a search through my search history as an RSS feed. The query part of the URL can be left blank to show all items. I'm guessing that by judicious use of the start and num parameters, one could even get at ones entire search history. Presumably the attention fanboys will like this.

The key in the above URL seems to be the output=rss parameter. Since bookmarks are in the same UI as search history, perhaps they can be viewed as RSS too? Yes, they can (though with some XML errors that the team is aware of as of 3/31/2006 it's well-formed XML ). The Trends page however doesn't work as RSS.

Note that these feeds are not really useful for most aggregators, since they require you to logged in to your Google Account and be authenticated by a cookie. The one exception might be Firefox Live Bookmarks. By creating one pointed to your searches or bookmarks feeds and putting it your toolbar, you have one-click access to your search history or bookmarks. However, the real use of the feeds is as a pseudo-API, as they are used in the Dashboard Widget.

This might seem like a convenient "leak," but it's something I decided to blog about for myself, without any prompting from the search history team (though they did get a heads-up about this post). The feed URLs and format may change at any time, though they probably won't deviate too much unless there's a good reason.