review of canvas API additions

Here are my comments on the canvas API additions as input for next 
week's discussion.

Ian Hickson:
> I just added a bunch of things to the <canvas> 2D API:
>
>   - Path primitives:
>       var p = new Path();
>       p.rect(0,0,100,100);
>       context.fill(p);

I think having standalone path objects is a good idea.  Paths are one of 
the data types that we have at the moment where you can't create a 
standalone one.  These are the relevant things we've already resolved on:

  14 Improve the SVG path DOM APIs
  80 Have a DOM method to convert a <text> element to outline path data

Whether we'd want to use an SVGPathSegList here is unclear, given that 
it's a sucky interface with an awkward name.  Here's the definition of 
the Path interface:

[Constructor,
  Constructor(Path path),
  Constructor(DOMString d)]
interface Path {
   void addPath(Path path, SVGMatrix? transformation);
   void addPathByStrokingPath(Path path, CanvasDrawingStyles styles,
                              SVGMatrix? transformation);
   void addText(DOMString text, CanvasDrawingStyles styles,
                SVGMatrix? transformation, double x, double y,
                optional double maxWidth);
   void addPathByStrokingText(DOMString text,
                              CanvasDrawingStyles styles,
                              SVGMatrix? transformation,
                              double x, double y,
                              optional double maxWidth);
   void addText(DOMString text, CanvasDrawingStyles styles,
                SVGMatrix? transformation, Path path,
                optional double maxWidth);
   void addPathByStrokingText(DOMString text,
                              CanvasDrawingStyles styles,
                              SVGMatrix? transformation, Path path,
                              optional double maxWidth);
};
Path implements CanvasPathMethods;

and CanvasPathMethods is:

interface CanvasPathMethods {
   // shared path API methods
   void closePath();
   void moveTo(double x, double y);
   void lineTo(double x, double y);
   void quadraticCurveTo(double cpx, double cpy, double x, double y);
   void bezierCurveTo(double cp1x, double cp1y, double cp2x,
                      double cp2y, double x, double y);
   void arcTo(double x1, double y1, double x2, double y2,
              double radius);
   void arcTo(double x1, double y1, double x2, double y2,
              double radiusX, double radiusY, double rotation);
   void rect(double x, double y, double w, double h);
   void arc(double x, double y, double radius, double startAngle,
            double endAngle, optional boolean anticlockwise = false);
   void ellipse(double x, double y, double radiusX, double radiusY,
                double rotation, double startAngle, double endAngle,
                boolean anticlockwise);
};

My concern is how we are going to integrate SVGPathSegList (or maybe 
SVGAnimatedPathData) with Path.  Might we want to allow these path 
manipulation methods to work on a <path d="">?  For example:

   <path d="M100,100"/>
   <script>
     var elt = /* the path element */;
     elt.d.baseVal.lineTo(200, 300);  // or
     elt.d.path.lineTo(200, 300);
   </script>

The former would require the SVGPathSegList object to have all the same 
methods as Path.  It could be done by

   SVGPathSegList implements Path;

or even having SVGPathSegList inherit from Path.

On the other hand, keeping it as a separate ".path" property keeps 
things simpler.  It also mirrors one of the suggested ways we were going 
to improve SVGAnimatedLength etc., by having additional properties 
hanging off there like:

   myRect.x.px = 100;

as "px" is the easier way to manipulate the SVGAnimatedLength, "path" 
could be the easier way to manipulate the SVGAnimatedPathData.

SVGPathSegList provides access to individual path segments, which Path 
does not.  Is that something we want to retain, if "path" is a separate 
property for elt.d as above?  The whole SVGPathSegItem thing is what was 
awkward about SVGPathSegList, but it's possibly useful to allow this 
kind of individual access to segments somehow.

In his examples in the spec, Ian assumes that SVGMatrix will have a 
constructor (I told him it probably will).  I am unsure at the moment 
what the relationship between all the different Matrix interfaces is -- 
maybe Dirk knows?  I know that if I were writing code I'd rather write 
"new Matrix(...)" than "new SVGMatrix(...)", especially if this matrix 
object could be used in non-SVG contexts like on these canvas methods.

The "addPathByStrokingPath" is a general path outsetting function, which 
we've discussed before (in the context of the (harder) path warping). 
If implementors go for it, that's fine, but we had some concerns about 
graphics libraries not exposing this kind of functionality.

The "addText" and "addPathByStrokingText" functions are for 
text-along-a-path.  These seem to do the same kind of thing that we do 
(i.e., place each glyph aligned with the tangent of the path), but I did 
not study it long enough to see if all the edges cases are the same.  We 
should do that.  In fact, we should provide an easy referenceable 
definition in SVG that HTML can link to here, to avoid it being defined 
in two places.

The methods that add a text outline to the current path take a 
DrawingStyle object which is an object that can be initialised with an 
element (optionally) but which also has the canvas properties for 
setting current stroke width, font, etc. on it.  This is to handle the 
problem of what properties will be used for the text.  I think we had 
settled on using a <text> object in the DOM which could then be 
converted into a path, and getting its properties from the document 
tree.  Ian's proposed way is a bit more flexible, and you don't have to 
manipulate the document tree to get the right styles and text.  I don't 
have a strong opinion here, but I guess I'd lean towards something that 
didn't require doing things in the document.

>   - Ellipses:
>       // arcs from center point, similar to arc()
>       context.ellipse(x, y, width/2, height/2, angle, 0, Math.PI*2);
>       context.stroke();
>       // arc corners
>       context.arcTo(x1, y1, x2, y2, width/2, height/2, angle);

Improving arc commands is path data is on our list too, although I don't 
think we have a concrete proposal yet.  We should bear these functions 
in mind when coming up with the proposal so that if they are similar, we 
can align on argument order etc. so that we don't have confusing 
differences like we do currently.

>    - SVG path description syntax
>       context.stroke(new Path('M 100,100 h 50 v 50 h 50'));

Discussed above.

>    - Dashed lines
>       context.setLineDash([3,1,0,1]); // --- . --- . --- .
>       context.moveTo(100,100);
>       context.lineTo(200,300);
>       context.stroke();

We should definitely have the same dashing.  I haven't analysed the 
dashing algorithm in HTML to see how it compares to our less precise 
definition. :)  (Defining dashing more precisely is on our list, too.) 
Modulo the specifics and probable corner case differences, I don't think 
there's anything controversial here.

>   - Text on a path:
>       var p1 = new Path('M 100 350 q 150 -300 300 0');
>       var p2 = new Path();
>       var styles = new DrawingStyle();
>       styles.font = '20px sans-serif';
>       p2.addText('Hello World', styles, null, p1);
>       context.fill(p2);

Discussed above.

>    - Hit testing:
>       context.beginPath();
>       context.rect(10,10,100,100);
>       context.fill();
>       context.addHitRegion({ id: 'The First Button' });
>       context.beginPath();
>       context.rect(120,10,100,100);
>       context.fill();
>       context.addHitRegion({ id: 'The Second Button' });
>       canvas.onclick = function (event) {
>         if (event.region)
>           alert('You clicked ' + event.region);
>       });

I find the API a bit odd, and I feel like it's been added to satisfy 
some accessibility use cases that I don't understand just now.  Not 
being able to remove a hit region seems strange too -- I'm not sure how 
useful it would be without that.

>    - Region discovery for AT users
>       context.rect(10,100,100,50);
>       context.fill();
>       context.addHitRegion({
>         id: 'button',
>         control: canvas.getElementsByTagName('button')[0],
>       });
>       context.beginPath();
>       context.rect(0,0,100,50);
>       context.textAlign = 'center';
>       context.textBaseline = 'top';
>       context.fillText('My Game', 50, 0, 100);
>       context.addHitRegion({
>         id: 'header',
>         label: 'My Game',
>         role: 'heading',
>       });
>       // now user can discover (via touch on a touch device, or via the
>       // virtual cursor in a traditional desktop AT) that there's a heading
>       // at the top of the canvas that says "My Game" and a button lower
>       // down that looks (to the AT) exactly like the canvas element's
>       // first child <button>.

Again I am not sure how well in practice this is going to solve the 
whole "inaccessible canvas applications that probably should have been 
written in SVG in the first place" problem, but I won't pretend to be an 
expert in that area.

>    - Automatic cursor control
>       context.addHitRegion({
>         path: new Path('M 10 10 h 20 v 20 h -20 z'),
>         cursor: 'url(fight.png)',
>       });
>       context.addHitRegion({
>         path: new Path('M 50 30 h 20 v 20 h -20 z'),
>         cursor: 'url(quaff.png)',
>       });

No comment; if hit regions are added supporting cursors on them makes 
sense.  It's actually probably a pretty reasonable thing -- it's 
difficult to get cursor changes working properly if you are writing a 
canvas-based game for example.

>    - APIs that take SVGMatrix objects for transforms
>       // transform a path when you add it to another path
>       var duck = new Path('M 0 0 c 40 48 120 -32 160 -6 c 0 0 5 4 10 '+
>                           '-3 c 10 -103 50 -83 90 -42 c 0 0 20 12 30 7 c '+
>                           '-2 12 -18 17 -40 17 c -55 -2 -40 25 -20 35 c '+
>                           '30 20 35 65 -30 71 c -50 4 -170 4 -200 -79z');
>       var identity = new SVGMatrix(); // constructor will be in SVG2 DOM
>       var threeDucks = new Path();
>       threeDucks.addPath(duck, identity.translate(0,0));
>       threeDucks.addPath(duck, identity.translate(100,0));
>       threeDucks.addPath(duck, identity.translate(200,0));
>
>       // set the transform using an SVGMatrix
>       cantext.currentTransform = context.currentTransform.flipX();

Seems fine, apart from my wondering whether and how SVGMatrix and 
CSSMatrix might ever be merged and what's happening with that general 
Matrix proposal dino had.

>    - many more metrics from measureText()
>       var metrics = context.measureText('Hello World');
>       var bL = metrics.actualBoundingBoxLeft;
>       var bR = metrics.actualBoundingBoxRight;
>       var bU = metrics.actualBoundingBoxAscent;
>       var bD = metrics.actualBoundingBoxDescent;
>       context.fillStyle = 'black';
>       context.fillRect(x-bL, y-bU, bL+bR, bU+bD);
>       context.fillStyle = 'white';
>       context.fillText(x, y, 'Hello World');
>       // there are also values for all the baselines

We've talked about exposing font metrics before but never had any 
proposals.  Seems reasonable at first glance, though some of those names 
are a bit wordy.  We could definitely look at exposing the same metrics 
objects from our <text> elements.

>    - ability to transform a pattern
>       var identity = new SVGMatrix();
>       context.fillStyle = context.createPattern(img, 'repeat');
>       context.fillStyle.setTransform(identity.rotate(angle));
>       context.fillRect(0, 0, canvas.width, canvas.height);

A thought: it would be good if our SVGMatrix constructor could take a 
transform item list:

   var rotation = new SVGMatrix("rotate(45)");

>    - various other additions
>       // reset the clip region
>       context.resetClip();
>       // reset the transform
>       context.resetTransform();

Not relevant for us.


I think the biggest issue for us to think about is how Path and 
SVGPathSegList relate.

Received on Thursday, 29 March 2012 23:17:16 UTC