CanvasRenderingContext2D.drawString()

What is the Problem?

Currently, the Canvas API does not include the ability to draw text strings. That makes several potentional Canvas applications, such as dynamic graphs or maps, more complex than neccessary. Instead of using the text facilities of the platform, Canvas applications need to bundle their own vector fonts or use inaccessible pre-rendered images for text display.

How can this Situation be Improved?

Unfortunately, adding a drawString() method is not as simple as it may seem; there are several more or less complex requirements that need to be taken into account:

Accessability
The drawString method should not reduce accessability.
Consistency
The drawString method should be consistent with the remainder of the Canvas API.
Performancy
Unneccessary temporary objects should be avoided
I18N
drawString should work for all languages that are supported by HTML (introduce no additional constraints)
Query Capabilities
The author should be able to query the actual size of the text bounding box.

Requirement Discussion

Accessability

Authors may reduce accessability by replacing regular HTML content with a Canvas and calls to drawString(). However, without drawString(), game designers may be tempted to use images with rendered text to display in-game messages (as in several Canvs text hacks). From an accessability point of view, drawString() has two advantages over the image approach:

Applying the browser text magnification mechanism to drawString() requires that the size of the text bounding box can be queried, so the content can adopt to enlarged labels dynamically.

Consistency

Other (complex) Canvas rendering properties such as the fill style are set via properties of the class CanvasRenderingContext2D. For fillStyle, supported values are CSS color values ("rgb(r,g,b)", "#fff" etc.) or special style objects (LinearGradient, Pattern, RadialGradient), created via the context object (createLinearGradient()...).

I18N

Fortunately, most internationalization issues are solved implicitly by using unicode characters. For displaying Ruby annotations properly, it is necessary to be able to query text boundaries.

Query Capabilities

Without query capabilities for the text bounding box, it is impossible to avoid overlapping content, in particular if the user is able to change the text size.

Also, the accessability and I18N requirements depend on the availability of this information.

The size of the bounding box depends on the actual font, which may differ from the requested font. The string width of several characters may differ from the sum of all character widths when kerning is supported.

Font Properties

CSS1 Defines a set of font properties that could be relevant for a Canvas drawString() method:

Besides the font properties, there are additional properties that could be taken into account:

The properties text-align and vertical-align make much sense to properly align a label with a given point of reference.

CSS2 defines even more properties. While support for the font and alignement attributes seems to be a good starting point, the API should take into account that future versions may want to add support for more CSS properties.

Existing Proposals

In addition to solutions based on a separate Font or TextStyle object, there were two alternative proposals, drawElement(e) and a more minimalistic approach by Andrew Fedoniouk.

drawElement(e)

This powerful approach was originally suggested by Gervase Markham. The basic idea is to use the built-in HTML renderer to render a given subtree to an image object.

While drawElement(e) would also be a nice addition for the Canvas API, it does not replace a simple basic API for drawing labels with query capabilities:

Since drawElement(e) does not replace drawString(), its inclusion (specification, problems) should be discussed separately.

Minimalist Approach

Andrew Fedoniouk suggested a nice minimalist approach:

Graphics.setFont(FontOrFamilyName, size, weight, ...);
Graphics.setTextAlignment(horizontal, vertical);
Graphics.getFontAscent();
Graphics.getTextWidth(string);
Graphics.drawText(x,y,string);

The advantage of this approach is that it does not require a separate Font or TextStyle object. However, it seems difficult to remember the correct order of parameters in setFont(). Also, when more font or text properties are added, the GraphicsContext class may get cluttered too much.

API Proposal

Because of the issues described above, rhino-canvas implements a simple, string-based solution with a separate TextStyle object:

interface CanvasRenderingContext2D {

  // colours and styles
  attribute CanvasTextStyle textStyle; // (default black)
  
  CanvasTextStyle createTextStyle();
  CanvasTextStyle createTextStyle(  // shortcut
     in DOMString fontStyleVariantWeight, 
     in DOMString fontSize, 
     in DOMString fontFamily); 
     
  void drawString(in float x, in float y, in DOMString text);
  
  [ ...Remainder of CanvasRenderingContext2D omitted... ]
}


interface CanvasTextStyle {
  // relative values and inherit are not supported!

  attribute DOMString fontFamily; /
  attribute DOMString fontStyle;
  attribute DOMString fontVariant;
  attribute DOMString fontWeight;
  attribute DOMString fontSize;

  attribute DOMString textDecoration;
  attribute DOMString verticalAlign;
  attribute DOMString textTransform;
  attribute DOMString textAlign; // justify not supported 

  float stringWidth(in DOMString s); 
  float getHeight(); // ascent + descent + leading
  float getBaselinePosition(); // ascent + leading
  float getAscent();
  float getDescent();
  float getLeading();
  
  
// perhaps add those later...
//  attribute DOMString wordSpacing;  
//  attribute DOMString letterSpacing;
};

Processing Model and Error Handling

If the requested font is not available, the broser choses the best fit, determined by the CSS font selection algorithm. All query methods return properties of the actually selected font, not the requested font.

Why would Browsers Implement this Feature?

There is much user demand for this feature on the WHATWG mailing list, and the feature completes the 2D canvas API. Moreover, it is simple and straight-forward to implement; it can be mapped more or less directly to the underlying graphics API of the platform.

Why would Authors Use this Feature?

Using this feature would simplify several graph- and map related applications. In the long term, authors will prefer this feature over alternatives (inaccessible images, vector fonts, proprietary plugins) because it provides a better user experience (loading time, accessability), combined with a simpler programming model.

What Evidence is there that this Feature is Desparately Needed?

There have been a lot of requests for a drawString() method in the Canvas API on the WHATWG mailing list.