[whatwg] Text APIs on <canvas>

I have introduced the following APIs:

   context.font
   context.textAlign
   context.textBaseline
   context.fillText()
   context.strokeText()
   context.measureText()

They are defined here:

   http://www.whatwg.org/specs/web-apps/current-work/#text

I haven't provided a way to render text to or along a path, nor a way to 
do vertical text, nor a way to measure anything but the nominal layout 
width of text (e.g. there's no way to measure bounding boxes or get 
baseline metrics). I also haven't provided a way to render document 
fragments straight to a canvas.


Here is some of the feedback on canvas related to text. I have snipped the 
feedback that wasn't constructive or didn't provide anything beyond 
requests for features without use cases and rationale, or that just 
bickered back and forth without making progress. If I snipped something 
that you think I should have responded to, please let me know. (I did take 
everything into account, even the bits that I haven't included below.)

I haven't made many comments below, because I didn't really have much to 
say. There were a lot of proposals and requests, but at the end of the day 
there didn't seem to be strong arguments in favour of some things or other 
things, so I haven't tried to defend the proposed API I put in the spec 
relative to the other APIs that were proposed. (I did take the various 
proposals that were made into account, and tried to adopt the best parts 
of each one.)

If you have further feedback, please send comments to the list, as usual.


On Thu, 18 May 2006, Lachlan Hunt wrote:
> 
> [...] text can often be an important part of an image. Consider a graph 
> or chart generated from a table of data elsewhere in the page, it's 
> important to correctly label the axis and for such labels to remain in 
> the image when it's exported to PNG (or any other image format).

Done.


On Tue, 6 Jun 2006, Martin Atkins wrote:
> 
> You can't really implement drawString as a single call, since there's no 
> guarantee that the font you requested will be available so you need to 
> be ready to deal with that eventuality.

The API as designed can handle this if you pass it a maxWidth argument. 
The 'font' attribute can take multiple fonts, as in CSS.


> A process which might work is:
> 
> * Make a request to prepare some text with an ordered list of preferred 
> fonts, CSS-style.
>
> * Get back some kind of descriptor which allows you to discover various 
> characteristics of the dimensions of the text as it will be rendered.
>
> * At some later point, ask the canvas to "draw" the descriptor at a 
> given location.
> 
> This allows for the browser to substitute other fonts while allowing the 
> canvas-based app to adapt the display to the characteristics of the 
> selected font.

The API vaguely does something like this.


On Thu, 3 Nov 2005, James Graham wrote:
> 
> Allowing text in <canvas> has serious accessibility problems. Presumably 
> such text would not be resizable and encouraging authors to use 
> non-resizable text in their web-apps is not a good idea. I guess there 
> would also be (separate) issues with fonts; one assumes font 
> substitution on a bitmap drawing would be more problematic than font 
> substitution where the text is the main content.

On Thu, 3 Nov 2005, Anne van Kesteren wrote:
> 
> Another problem is that, since html:canvas does not have a DOM, the 
> actual content is not really accessible. However, I have seen people 
> having to use images with the alphabet inside html:canvas to get simple 
> text effects. Making that easier might be nice. (But addressing fonts, 
> line-height, etc. might be difficult.)

On Tue, 8 Nov 2005, Vladimir Vukicevic wrote:
> 
> There are accessibility problems for sure; however, accessibility is not 
> something that can be forced onto content authors.  They have to design 
> for accessibility, it won't happen for them.  If they don't, being able 
> to draw text into canvas is a relatively minor issue.
> 
> If a DOM or if arbitrary high-resolution scaling of already drawn 
> content is desired, SVG's the best option.  However, there are a lot of 
> use cases where text in <canvas> is highly desirable.  <canvas> is 
> already going down a pseudo-CSS path for specifying colors and other 
> styles; I think it makes sense to extend text in a similar way, perhaps 
> by using a textStyle that refers to a CSS property (by ID? by class? 
> somehow), and then letting you render text strings into the canvas.

On Tue, 17 Oct 2006, Charles Iliya Krempeaux wrote:
> 
> I believe that some people's reason for not wanting to add it was 
> because of Accessibility concerns.
> 
> Although normal text in a webpage... or even a text image (with the 
> "alt" attribute filled in properly) could be "read" by a person with 
> disabilities, text embedded in the canvas element could not.
> 
> Perhaps people need to think about how to add Accessibility to the 
> canvas while allowing a "drawString" procedure.

On Tue, 17 Oct 2006, Mathieu Henri wrote:
> 
> But if a user agent do no support Canvas, it MUST fallback to the 
> descendant tags of the Canvas. There you have the accessibility mean.
> 
> It would easy to use DOM methods to grab the texts to display in the 
> fallback content of the Canvas tag. Provided that the fallback markup is 
> exposed to JavaScript in the user agents that support Canvas.

On Tue, 17 Oct 2006, James Graham wrote:
> 
> So how would I, using my canvas-supporting browser, make the text larger 
> when the page author decided to make it illegibly (for me) small.
> 
> The idea that accessibility features are for people who need special 
> browsers is a myth.
> 
> (actually the whole text-on-a-drawing thing is a huge can of worms. What 
> if the browser has different fonts to the author? Font substitution can 
> lead to a font being used with the wrong width, leading to text 
> overlapping the drawing. (Browser implementations of) SVG have this 
> problem. The same issues can occur if the text can be rescaled but not 
> he graphic elements. The obvious solution to this - rescaling text and 
> graphic in lockstep - is an improvement but can lead to huge amounts of 
> unnecessary scrolling if the unscaled figure has a width close to the 
> screen width since there's no sane way to reflow a figure.)

On Tue, 17 Oct 2006, Alfonso Baqueiro wrote:
> 
> Well a drawString method in canvas can be used for drawing the axis 
> labels or values on a dinamic javascript graphic, but is non sense an 
> aural reader read it, theres no way (yet) to read an image to a blinded 
> people, there are cases where is imposible the accessibility, is 
> imposible for a blind to play video games, and accessibility dont stop 
> the video games creation. In the case of images or the canvas the alt 
> attribute could do the job.

Since people are doing text already using bitmap fonts, not including this 
API isn't helping accessibility. So this seems like a moot point. The 
authors who are responsible will provide accessible versions of the 
content regardless of whether the canvas contains text or not.


On Wed, 17 May 2006, Gervase Markham wrote:
> 
> This came up in Vlad's presentation at XTech today. My suggestion was to 
> implement drawDiv() or drawElement(), in addition to drawWindow(). That 
> way, you could fix the potentially hairy text rendering problem with one 
> simple API call, which could be made using the backup accessible content 
> which you had placed inside the <canvas> tag anyway. Everyone wins.
> 
> <canvas id="foo">
>   <table>
>     <tr>
>       <th id="ds1title">Data Set 1</th>
>       <th id="ds2title">Data Set 2</th>
>      ...
> 
> foo.drawElement(document.getElementById("ds1title"), 45, 60);

On Sat, 20 May 2006, Peter Hall wrote:
>
> I really like that idea. But I think you'd put the method on 
> CanvasRenderingContext2D instead of canvas itself?
> 
> foo.getContext("2d").drawElement(document.getElementById("ds1title"), 45,
> 60);
> 
> In the latest Flash player, you can do something similar with 
> BitmapData.draw(), but there are additional arguments for matrix 
> transform, blending mode, clip rectangle and smoothing options. I'd 
> certainly want to at least be able to scale and clip what I'm drawing.

On Wed, 18 Oct 2006, Gervase Markham wrote:
> 
> I've suggested this in the past as a solution to this problem: why not 
> have a drawElement(elem) parameter?
> 
> That way, you could build an accessible, readable version of the content 
> inside the <canvas> tag, as alternative content, and copy labels or 
> anything else into the <canvas> itself with drawElement(label). So the 
> same content serves both as the accessible version and the used version.
> 
> This would give us great flexibility, because the text you do have is 
> controlled with all the power of the existing CSS and browser font 
> model, obviating the need for font controls or font objects on the 
> <canvas> API - which would inevitably be not as good as the CSS ones. 
> And if browsers acquire downloadable font support, so does canvas.
> 
> I would speculate wildly that it might even be easy to implement too. 
> After all, I'm sure browsers have the ability to render the contents of 
> a <div> tag to a drawing buffer...

On Wed, 18 Oct 2006, Mathieu Henri wrote:
> 
> Indeed, adding a something like the toDataURL( [MIMEType] ) method on 
> the HTMLelement object would make our life so much easier and open a 
> whole new range of possibilities.

On Wed, 18 Oct 2006, Stefan Haustein wrote:
> 
> I think drawElement(elem) opens up a whole new can of worms:
> 
> - how would an application determine the size of the text box?
> 
> - where is the baseline position, needed for exact axis label 
> positioning?
> 
> - there are probably issues with dynamically calculated text values
> 
> - code with lots of cross references to elements will be difficult to 
> read
> 
> - it needs to be specified whether css properties are inherited from the 
> parent element of "elem".
> 
> - how much horizontal/vertical space is drawElement permitted to use for 
> rendering?
> 
> - the implementation in actual browsers may be more complex than it 
> seems because of problems with internal data structures for rendering 
> hints and implicitly introducing the ability to render the same element 
> twice.
> 
> - what happens with contained plugins, canvas elements, 
> self-references... all this stuff needs to be well-defined
> 
> Moreover, drawElement() would not solve the drawText problem for 
> non-browser environments such as Rhino.

On Wed, 18 Oct 2006, Gervase Markham wrote:
> 
> The answer to all of these things is that the browser renders all the 
> elements in the page as it would if the <canvas> were not supported and 
> the alternate content were being used. It then basically screenshots the 
> area corresponding to the element (yes, I know this needs careful 
> definition) and draws that into the canvas.
> 
> Like I said, we want to leverage the browser's deep and complex 
> knowledge of text rendering as much as possible, and just take the 
> resulting pixel output as it would be shown to the user.
> 
> I know it's easy to state and there are edge cases. But we could put 
> limits on it like e.g. no plugins, no <object>, and still have something 
> very useful for rendering text.
> 
> I mean, all you really need to support is stuff like:
> 
> <style>
>   .box {
>     width: 300px;
>     font-style: cursive;
>     font-weight: bold;
>     text-transform: uppercase;
>    }
> </style>
> 
> <div class="box">Some text to play with</div>

I haven't provided this because it is significantly more complex than just 
drawing text, but it is something we should probably look at in a future 
version of the canvas API.


On Mon, 23 Oct 2006, David Hyatt wrote:
> 
> Rather than specifying stylistic information explicitly (via a font 
> object), I'd use a special parenthetical pseudo-element. thus allowing 
> the author to specify the style as for any other element on a page.... 
> something like this...
> 
> canvas::canvas-text(barchart)
> {
> 	font-size: 8px;
> 	font-family: Monaco, monospace;
> }
> 
> and then the API would be something like:
> 
> drawText(y-coord of baseline, "barchart", myText)
> 
> and letter-spacing/word-spacing would work, small-caps would work, text-shadow
> would work, etc. etc.

It seems that at least the font-size would be something you'd want to 
control in the code rendering the text, though I could see an argument for 
making the other aspects of fonts accessible from CSS... this seems a bit 
over-engineered though. I haven't provided this for now.


> fitTextToPath might be an interesting API too.

Indeed. I have noted something along these lines as a possible area to 
extend the API into in a future version.


On Tue, 24 Oct 2006, Gervase Markham wrote:
> 
> At the risk of overcomplicating, vertical text is a common requirement 
> for graphs and charts. If this is simpler to implement than the 
> "arbitrary text rotation" case, is it worth having a way of saying the 
> baseline is vertical rather than horizontal? Or would this be a job for 
> fitTextToPath()?

I haven't done vertical text because CSS doesn't yet define how to render 
it. However, there is a drawVerticalText() method commented out in the 
spec that is intended to satisfy this which can be uncommented out when 
CSS defines how to do this.


On Wed, 25 Oct 2006, Charles Iliya Krempeaux wrote:
> 
> One thing that comes to mind is "Ruby" in the Japanese language.

On Tue, 14 Nov 2006, Martin Atkins wrote:
> 
> However, it'd also be useful to be able to render formatted blocks of 
> text, like this:
> 
> void drawString(in float x1, in float y1, in float x2, in float y2,
>                 in DOMString text)
> 
> ...which wrap text and would make a textAlign of "justify" meaningful. 
> Suddenly a bunch more CSS properties become attractive (margins and 
> letter spacing, for example) but I think line-height alone would do for 
> now.
> 
> Rendering formatted blocks of text seems to me to be such a common thing 
> that the canvas API should support it natively.
> 
> This does, of course, require more functions on TextStyle to measure the 
> extent of a block of text. This could probably be confined to just the 
> following:
> 
> float getBlockHeight(in DOMString s, float width)
> 
> I think that a good use-case for this would be replacing regular 
> document elements with canvases, either using bare script or using XBL. 
> You point out in your document that drawElement(e) is tricky, but for 
> limited cases it would be nice to be able to obtain the computed style 
> of the canvas element to use for rendering, so that the text can match 
> the style of the surrounding document.
> 
> This can be as simple as one method on the canvas context to fetch the 
> computed style, which would presumably return null in a non-browser 
> implementation of the API.
> 
> CanvasTextStyle getInheritedStyle();
> 
> For example, this could be used to create some kind of visual effect 
> (fancy headings?) or to render a graphical element from some XML 
> namespace that isn't supported in browsers yet.

On Tue, 14 Nov 2006, Mathieu HENRI wrote:
> 
> I still think by introducing the drawString() method into Canvas we are 
> opening the same can of worms that was open in SVG.
> 
> If we go that way we will need a drawParagraph() method to draw multi 
> line strings or blocks of text with wrapping and a bounding width. We 
> also need to be able to stylize the text, i.e. changing the font-weight 
> / color / font-style ... of any word.
> 
> The list goes on and on ... and HTML and CSS already cover it all.
> 
> The HTMLElement.drawElement() method should be no problem to implement 
> since userAgents already do render HTMLElements.
> 
> Having it return an ImageData object will make it insanely simple to 
> manipulate in Canvas. The text elements/contents can easily be in the 
> fall back content of the Canvas tag thus keeping it accessible. Getting 
> the bounding box of an HTMLElement is no problem either in JavaScript. 
> And applying gradients and patterns can be done using a fillRect() with 
> the appropriate globalCompositeOperation.
> 
> Everything (almost) is there. Let's not re-invent a square wheel.

Ruby, multiline layout, and other such complexities aren't handled by this 
API. Full text layout like this would be better handled using an API 
similar to the drawElement() proposal. I've added a note to this effect in 
the spec.


On Wed, 18 Oct 2006, Stefan Haustein wrote:
>
> However, I think getAscent() is not sufficient, we should also add 
> getLeading() and getDescent():
> 
> This would allow us to determine the total line height 
> (leading+ascent+descent) and the baseline position (leading+ascent):
> 
> http://docs.rinet.ru/UJ11/art/17/17fig08n.jpg
> 
> The total line height is important since it constitutes the minimum 
> vertical distance from baseline to baseline (Accents on uppercase 
> letters will be placed in the leading area, and they must not overlap 
> with the descent from the previous line).
> 
> The baseline position is important for text alignment when using 
> different fonts/styles in a single line.
> 
> Of course we, could add getBaselinePosition() and getHeight() instead of 
> some of the other methods, but including the three most basic values 
> seems to be a consistent approach (simple to remember) and all other 
> values can be calculated by summing them up somehow (no differences 
> needed).

On Tue, 24 Oct 2006, David Hyatt wrote:
>
> I'm very reluctant to expose font metrics and information (yet).  I 
> think once you start getting into specifying fonts, you open up a can of 
> worms that would make this sort of API addition a lot harder.

On Wed, 25 Oct 2006, Stefan Haustein wrote:
> 
> I think it is very important to be able to determine the rendered size 
> of the text. Otherwise, there is no reliable way to make sure things do 
> not overlap. Also, some kinds of applications (flash-like effects, 
> labels expressing weight or distance, WYSIWYG text editors) may require 
> variable font sizes or styles.
> 
> What do you think about
> 
> context.textStyle = "barchart"; // style by reference
> 
> context.textStyle = {  // set style directly
>  "font-size": "8px",
>  "font-family": "Monaco, monospace"
> }
> 
> context.drawText(x,y,string); context.getFontAscent();
> context.getFontDescent();
> context.getFontLeading();
> context.getTextWidth(string);

On Wed, 25 Oct 2006, Stefan Haustein wrote:
> 
> However, I would still prefer an API design that keeps it simple to add 
> the metric query methods later. Perhaps they could be included and 
> simply return null if the requested information is not available (e.g. 
> for a dumb EPS render target?). Also, if we do not have access to font 
> metrics, I think we need an additional parameter for drawText() that 
> specifies the alignment of the text relative to the reference point, as 
> illustrated in the image below:
> 
> http://www.developer.com/img/articles/2002/12/26/03fig12.jpg

On Wed, 25 Oct 2006, Stefan Haustein wrote:
> 
> for bulk text, I think Canvas is the wrong place. However, if we get the 
> metrics query methods, it would theoretically be possible to implement a 
> renderer for any kind of specific purpose.
> 
> To display ruby annotations for labels (maps, some kind of educational 
> flash-like application, you name it...), one could draw the annotation 
> in a small font, aligned to the bottom and draw the base text aligned to 
> the top (given the propsed alignment parameter is added):
> 
> canvas.textStyle = {"font-size": "5.5pt"};
> canvas.drawString(100, 100, "ko ba ya shi", BOTTOM|HCENTER);
> canvas.textStyle = {"font-size": "12pt"};
> canvas.drawString(100, 100, "KO HAYASHI", TOP|HCENTER);

On Wed, 25 Oct 2006, James Graham wrote:
> 
> I still believe that any api that does not allow the author to query the 
> size of the text bounding-box[1], i.e. the overall width and height of 
> the box enclosing the text fragment, is useless for many of the use 
> cases presented so far e.g. figure labelling as, without this 
> information, it is impossible to ensure that adjacent text fragments do 
> not overlap.
> 
> [1] I mean that in a non-technical sense

On Wed, 25 Oct 2006, David Hyatt wrote:
>
> Yeah I see what you mean.  In addition to a drawText you probably want 
> something like a metricsForText API that would tell you the extent of 
> the string and the font metrics (line height, ascent, descent, 
> baseline).

On Thu, 26 Oct 2006, Gervase Markham wrote:
> 
> It's the responsibility of the page designer to make sure the text they 
> are measuring is styled the same as the text they want the metrics of.
> 
> According to the current API suggestion, the styling of the text in a 
> canvas will not be affected by the style of parent elements, but will be 
> defined by the style of the pseudo element name passed to the drawText 
> function.

The API has a measureText() method, which right now just returns the 
layout width of the text, but which could in future be extended to support 
any other metrics we want to expose (e.g. the bounding box, the distance 
from the top of the em square to the top of the bounding box, the relative 
positions of the baselines, etc).


On Mon, 13 Nov 2006, Stefan Haustein wrote:
> 
> I have tried to sum up the requirements for 
> CanvasRenderingContext2D.drawString() at 
> http://rhino-canvas.sf.net/www/drawstring.html
> 
> The page contains an API proposal based on the Font/TextStyle object 
> approach that meets all those requirements, and also some motivation why 
> I have implemented this approach and not one of the alternatives 
> discussed here.

Thanks, this was quite useful.

Cheers,
-- 
Ian Hickson               U+1047E                )\._.,--....,'``.    fL
http://ln.hixie.ch/       U+263A                /,   _.. \   _\  ;`._ ,.
Things that are impossible just take longer.   `._.-(,_..'--(,_..'`-.;.'

Received on Monday, 5 May 2008 20:10:41 UTC