View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.canvas;
16  
17  import java.io.IOException;
18  
19  import org.apache.commons.lang3.StringUtils;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.htmlunit.corejs.javascript.Context;
23  import org.htmlunit.corejs.javascript.Function;
24  import org.htmlunit.corejs.javascript.Scriptable;
25  import org.htmlunit.html.HtmlImage;
26  import org.htmlunit.javascript.HtmlUnitScriptable;
27  import org.htmlunit.javascript.JavaScriptEngine;
28  import org.htmlunit.javascript.configuration.JsxClass;
29  import org.htmlunit.javascript.configuration.JsxConstructor;
30  import org.htmlunit.javascript.configuration.JsxFunction;
31  import org.htmlunit.javascript.configuration.JsxGetter;
32  import org.htmlunit.javascript.configuration.JsxSetter;
33  import org.htmlunit.javascript.host.html.HTMLCanvasElement;
34  import org.htmlunit.javascript.host.html.HTMLImageElement;
35  import org.htmlunit.platform.Platform;
36  import org.htmlunit.platform.canvas.rendering.RenderingBackend;
37  import org.htmlunit.platform.canvas.rendering.RenderingBackend.WindingRule;
38  import org.htmlunit.protocol.data.DataURLConnection;
39  import org.htmlunit.util.MimeType;
40  
41  /**
42   * A JavaScript object for {@code CanvasRenderingContext2D}.
43   *
44   * @author Ahmed Ashour
45   * @author Marc Guillemot
46   * @author Frank Danek
47   * @author Ronald Brill
48   */
49  @JsxClass
50  public class CanvasRenderingContext2D extends HtmlUnitScriptable {
51  
52      private static final Log LOG = LogFactory.getLog(CanvasRenderingContext2D.class);
53  
54      private HTMLCanvasElement canvas_;
55      private RenderingBackend renderingBackend_;
56  
57      /**
58       * Default constructor.
59       */
60      public CanvasRenderingContext2D() {
61          super();
62      }
63  
64      /**
65       * JavaScript constructor.
66       */
67      @JsxConstructor
68      public void jsConstructor() {
69          // nothing to do
70      }
71  
72      /**
73       * Constructs in association with {@link HTMLCanvasElement}.
74       * @param canvas the {@link HTMLCanvasElement}
75       */
76      public CanvasRenderingContext2D(final HTMLCanvasElement canvas) {
77          super();
78          canvas_ = canvas;
79          renderingBackend_ = null;
80      }
81  
82      private RenderingBackend getRenderingBackend() {
83          if (renderingBackend_ == null) {
84              final int imageWidth = Math.max(1, canvas_.getWidth());
85              final int imageHeight = Math.max(1, canvas_.getHeight());
86  
87              renderingBackend_ = Platform.getRenderingBackend(imageWidth, imageHeight);
88          }
89          return renderingBackend_;
90      }
91  
92      /**
93       * Specifies the alpha (transparency) value that is applied to shapes and images
94       * before they are drawn onto the canvas..
95       * @return the {@code globalAlpha} property
96       */
97      @JsxGetter
98      public double getGlobalAlpha() {
99          return getRenderingBackend().getGlobalAlpha();
100     }
101 
102     /**
103      * Sets the {@code globalAlpha} property.
104      * @param globalAlpha the {@code globalAlpha} property
105      */
106     @JsxSetter
107     public void setGlobalAlpha(final double globalAlpha) {
108         getRenderingBackend().setGlobalAlpha(globalAlpha);
109     }
110 
111     /**
112      * Returns the {@code fillStyle} property.
113      * @return the {@code fillStyle} property
114      */
115     @JsxGetter
116     public HtmlUnitScriptable getFillStyle() {
117         LOG.info("CanvasRenderingContext2D.getFillStyle() not yet implemented");
118         return null;
119     }
120 
121     /**
122      * Sets the {@code fillStyle} property.
123      * @param fillStyle the {@code fillStyle} property
124      */
125     @JsxSetter
126     public void setFillStyle(final String fillStyle) {
127         getRenderingBackend().setFillStyle(fillStyle);
128     }
129 
130     /**
131      * Returns the {@code strokeStyle} property.
132      * @return the {@code strokeStyle} property
133      */
134     @JsxGetter
135     public HtmlUnitScriptable getStrokeStyle() {
136         LOG.info("CanvasRenderingContext2D.getStrokeStyle() not yet implemented");
137         return null;
138     }
139 
140     /**
141      * Sets the {@code strokeStyle} property.
142      * @param strokeStyle the {@code strokeStyle} property
143      */
144     @JsxSetter
145     public void setStrokeStyle(final String strokeStyle) {
146         getRenderingBackend().setStrokeStyle(strokeStyle);
147     }
148 
149     /**
150      * Returns the {@code lineWidth} property.
151      * @return the {@code lineWidth} property
152      */
153     @JsxGetter
154     public double getLineWidth() {
155         return getRenderingBackend().getLineWidth();
156     }
157 
158     /**
159      * Sets the {@code lineWidth} property.
160      * @param lineWidth the {@code lineWidth} property
161      */
162     @JsxSetter
163     public void setLineWidth(final Object lineWidth) {
164         if (!JavaScriptEngine.isUndefined(lineWidth)) {
165             final double width = JavaScriptEngine.toNumber(lineWidth);
166             if (!Double.isNaN(width)) {
167                 getRenderingBackend().setLineWidth((int) width);
168             }
169         }
170     }
171 
172     /**
173      * Draws an arc.
174      * @param x the x
175      * @param y the y
176      * @param radius the radius
177      * @param startAngle the start angle
178      * @param endAngle the end angle
179      * @param anticlockwise is anti-clockwise
180      */
181     @JsxFunction
182     public void arc(final double x, final double y, final double radius, final double startAngle,
183                 final double endAngle, final boolean anticlockwise) {
184         getRenderingBackend().arc(x, y, radius, startAngle, endAngle, anticlockwise);
185     }
186 
187     /**
188      * Draws an arc.
189      * @param x1 the x1
190      * @param y1 the y1
191      * @param x2 the x2
192      * @param y2 the y2
193      * @param radius the radius
194      */
195     @JsxFunction
196     public void arcTo(final double x1, final double y1, final double x2, final double y2,
197                 final double radius) {
198         LOG.info("CanvasRenderingContext2D.arcTo() not yet implemented");
199     }
200 
201     /**
202      * Begins the subpaths.
203      */
204     @JsxFunction
205     public void beginPath() {
206         getRenderingBackend().beginPath();
207     }
208 
209     /**
210      * Draws a cubic Bézier curve.
211      * @param cp1x the cp1x
212      * @param cp1y the cp1y
213      * @param cp2x the cp2x
214      * @param cp2y the cp2y
215      * @param x the x
216      * @param y the y
217      */
218     @JsxFunction
219     public void bezierCurveTo(final double cp1x, final double cp1y, final double cp2x, final double cp2y,
220             final double x, final double y) {
221         getRenderingBackend().bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
222     }
223 
224     /**
225      * Clears the specified rectangular area.
226      * @param x the x
227      * @param y the y
228      * @param w the width
229      * @param h the height
230      */
231     @JsxFunction
232     public void clearRect(final double x, final double y, final double w, final double h) {
233         getRenderingBackend().clearRect(x, y, w, h);
234     }
235 
236     /**
237      * Creates a new clipping region.
238      * @param context the JavaScript context
239      * @param scope the scope
240      * @param thisObj the scriptable
241      * @param args the arguments passed into the method
242      * @param function the function
243      */
244     @JsxFunction
245     public static void clip(final Context context, final Scriptable scope,
246             final Scriptable thisObj, final Object[] args, final Function function) {
247         if (!(thisObj instanceof CanvasRenderingContext2D)) {
248             throw JavaScriptEngine.reportRuntimeError(
249                     "CanvasRenderingContext2D.getImageData() failed - this is not a CanvasRenderingContext2D");
250         }
251         final CanvasRenderingContext2D canvas = (CanvasRenderingContext2D) thisObj;
252 
253         RenderingBackend.WindingRule windingRule = WindingRule.NON_ZERO;
254         if (args.length == 1) {
255             final String windingRuleParam = JavaScriptEngine.toString(args[0]);
256             if ("evenodd".contentEquals(windingRuleParam)) {
257                 windingRule = WindingRule.EVEN_ODD;
258             }
259             canvas.getRenderingBackend().clip(windingRule, null);
260         }
261 
262         if (args.length > 1) {
263             if (!(args[0] instanceof Path2D)) {
264                 throw JavaScriptEngine.reportRuntimeError(
265                         "CanvasRenderingContext2D.clip() failed - the first parameter has to be a Path2D");
266             }
267 
268             final String windingRuleParam = JavaScriptEngine.toString(args[1]);
269             if ("evenodd".contentEquals(windingRuleParam)) {
270                 windingRule = WindingRule.EVEN_ODD;
271             }
272 
273             LOG.info("CanvasRenderingContext2D.clip(path, fillRule) not yet implemented");
274             // canvas.getRenderingBackend().clip(windingRule, (Path2D) args[0]);
275         }
276 
277         canvas.getRenderingBackend().clip(WindingRule.NON_ZERO, null);
278     }
279 
280     /**
281      * Closes the current subpath.
282      */
283     @JsxFunction
284     public void closePath() {
285         getRenderingBackend().closePath();
286     }
287 
288     /**
289      * Returns the {@code ImageData} object.
290      * this may accept a variable number of arguments.
291      * @param context the JavaScript context
292      * @param scope the scope
293      * @param thisObj the scriptable
294      * @param args the arguments passed into the method
295      * @param function the function
296      * @return the {@code ImageData} object
297      */
298     @JsxFunction
299     public static ImageData createImageData(final Context context, final Scriptable scope,
300             final Scriptable thisObj, final Object[] args, final Function function) {
301         if (!(thisObj instanceof CanvasRenderingContext2D)) {
302             throw JavaScriptEngine.reportRuntimeError(
303                     "CanvasRenderingContext2D.getImageData() failed - this is not a CanvasRenderingContext2D");
304         }
305         final CanvasRenderingContext2D canvas = (CanvasRenderingContext2D) thisObj;
306 
307         if (args.length > 0 && args[0] instanceof ImageData) {
308             final ImageData imageDataParameter = (ImageData) args[0];
309             final ImageData imageData = new ImageData(null,
310                     0, 0, imageDataParameter.getWidth(), imageDataParameter.getHeight());
311             imageData.setParentScope(canvas.getParentScope());
312             imageData.setPrototype(canvas.getPrototype(imageData.getClass()));
313             return imageData;
314         }
315 
316         if (args.length > 1) {
317             final int width = Math.abs((int) JavaScriptEngine.toInteger(args, 0));
318             final int height = Math.abs((int) JavaScriptEngine.toInteger(args, 1));
319             final ImageData imageData = new ImageData(null, 0, 0, width, height);
320             imageData.setParentScope(canvas.getParentScope());
321             imageData.setPrototype(canvas.getPrototype(imageData.getClass()));
322             return imageData;
323         }
324 
325         throw JavaScriptEngine.reportRuntimeError(
326                 "CanvasRenderingContext2D.getImageData() failed - "
327                 + "wrong parameters given (" + StringUtils.join(args, ", ") + ")");
328     }
329 
330     /**
331      * Creates linear gradient.
332      * @param x0 the x0
333      * @param y0 the y0
334      * @param r0 the r0
335      * @param x1 the x1
336      * @param y1 the y1
337      * @param r1 the r1
338      * @return the new CanvasGradient
339      */
340     @JsxFunction
341     public CanvasGradient createLinearGradient(final double x0, final double y0, final double r0, final double x1,
342             final Object y1, final Object r1) {
343         final CanvasGradient canvasGradient = new CanvasGradient();
344         canvasGradient.setParentScope(getParentScope());
345         canvasGradient.setPrototype(getPrototype(canvasGradient.getClass()));
346         return canvasGradient;
347     }
348 
349     /**
350      * Creates a pattern.
351      */
352     @JsxFunction
353     public void createPattern() {
354         LOG.info("CanvasRenderingContext2D.createPattern() not yet implemented");
355     }
356 
357     /**
358      * Creates a gradient.
359      * @param x0 the x axis of the coordinate of the start circle
360      * @param y0 the y axis of the coordinate of the start circle
361      * @param r0 the radius of the start circle
362      * @param x1 the x axis of the coordinate of the end circle
363      * @param y1 the y axis of the coordinate of the end circle
364      * @param r1 the radius of the end circle
365      * @return the new CanvasGradient
366      */
367     @JsxFunction
368     public CanvasGradient createRadialGradient(final double x0, final double y0,
369                             final double r0, final double x1, final double y1, final double r1) {
370         final CanvasGradient canvasGradient = new CanvasGradient();
371         canvasGradient.setParentScope(getParentScope());
372         canvasGradient.setPrototype(getPrototype(canvasGradient.getClass()));
373         return canvasGradient;
374     }
375 
376     /**
377      * Draws images onto the canvas.
378      *
379      * @param image an element to draw into the context
380      * @param sx the X coordinate of the top left corner of the sub-rectangle of the source image
381      *        to draw into the destination context
382      * @param sy the Y coordinate of the top left corner of the sub-rectangle of the source image
383      *        to draw into the destination context
384      * @param sWidth the width of the sub-rectangle of the source image to draw into the destination context
385      * @param sHeight the height of the sub-rectangle of the source image to draw into the destination context
386      * @param dx the X coordinate in the destination canvas at which to place the top-left corner of the source image
387      * @param dy the Y coordinate in the destination canvas at which to place the top-left corner of the source image
388      * @param dWidth the width to draw the image in the destination canvas. This allows scaling of the drawn image
389      * @param dHeight the height to draw the image in the destination canvas. This allows scaling of the drawn image
390      */
391     @JsxFunction
392     @SuppressWarnings("unused")
393     public void drawImage(final Object image, final int sx, final int sy, final Object sWidth, final Object sHeight,
394             final Object dx, final Object dy, final Object dWidth, final Object dHeight) {
395 
396         if (image instanceof HTMLImageElement) {
397             final HTMLImageElement imageElem = (HTMLImageElement) image;
398             try {
399                 final org.htmlunit.platform.image.ImageData imageData
400                             = ((HtmlImage) imageElem.getDomNodeOrDie()).getImageData();
401 
402                 // 3 arguments
403                 //   void ctx.drawImage(image, dx, dy);
404                 if (JavaScriptEngine.isUndefined(sWidth)) {
405                     getRenderingBackend().drawImage(imageData, 0, 0, null, null, sx, sy, null, null);
406                 }
407 
408                 // 5 arguments
409                 //   void ctx.drawImage(image, dx, dy, dWidth, dHeight);
410                 else if (JavaScriptEngine.isUndefined(dx)) {
411                     final int dWidthI = JavaScriptEngine.toInt32(sWidth);
412                     final int dHeightI = JavaScriptEngine.toInt32(sHeight);
413 
414                     getRenderingBackend().drawImage(imageData, 0, 0, null, null, sx, sy, dWidthI, dHeightI);
415                 }
416 
417                 // all 9 arguments
418                 //   void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
419                 else {
420                     final int sWidthI = JavaScriptEngine.toInt32(sWidth);
421                     final int sHeightI = JavaScriptEngine.toInt32(sHeight);
422 
423                     final int dxI = JavaScriptEngine.toInt32(dx);
424                     final int dyI = JavaScriptEngine.toInt32(dy);
425                     final int dWidthI = JavaScriptEngine.toInt32(dWidth);
426                     final int dHeightI = JavaScriptEngine.toInt32(dHeight);
427 
428                     getRenderingBackend().drawImage(imageData,
429                             sx, sy, sWidthI, sHeightI, dxI, dyI, dWidthI, dHeightI);
430                 }
431             }
432             catch (final IOException ex) {
433                 LOG.info("There is no ImageReader available for you imgage with src '" + imageElem.getSrc() + "'"
434                         + "Please have a look at https://www.htmlunit.org/images-howto.html "
435                         + "for a possible solution.");
436             }
437         }
438     }
439 
440     /**
441      * Returns the Data URL.
442      *
443      * @param type an optional type
444      * @return the dataURL
445      */
446     public String toDataURL(String type) {
447         try {
448             if (type == null) {
449                 type = MimeType.IMAGE_PNG;
450             }
451             return DataURLConnection.DATA_PREFIX + type + ";base64," + getRenderingBackend().encodeToString(type);
452         }
453         catch (final IOException ex) {
454             throw JavaScriptEngine.throwAsScriptRuntimeEx(ex);
455         }
456     }
457 
458     /**
459      * Paints the specified ellipse.
460      * @param x the x
461      * @param y the y
462      * @param radiusX the radiusX
463      * @param radiusY the radiusY
464      * @param rotation the rotation
465      * @param startAngle the startAngle
466      * @param endAngle the endAngle
467      * @param anticlockwise the anticlockwise
468      */
469     @JsxFunction
470     public void ellipse(final double x, final double y,
471                     final double radiusX, final double radiusY,
472                     final double rotation, final double startAngle, final double endAngle,
473                     final boolean anticlockwise) {
474         getRenderingBackend().ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
475     }
476 
477     /**
478      * Fills the shape.
479      */
480     @JsxFunction
481     public void fill() {
482         getRenderingBackend().fill();
483     }
484 
485     /**
486      * Paints the specified rectangular area.
487      * @param x the x
488      * @param y the y
489      * @param w the width
490      * @param h the height
491      */
492     @JsxFunction
493     public void fillRect(final int x, final int y, final int w, final int h) {
494         getRenderingBackend().fillRect(x, y, w, h);
495     }
496 
497     /**
498      * Fills a given text at the given (x, y) position.
499      * @param text the text
500      * @param x the x
501      * @param y the y
502      */
503     @JsxFunction
504     public void fillText(final String text, final double x, final double y) {
505         getRenderingBackend().fillText(text, x, y);
506     }
507 
508     /**
509      * Returns the {@code ImageData} object.
510      * @param sx x
511      * @param sy y
512      * @param sw width
513      * @param sh height
514      * @return the {@code ImageData} object
515      */
516     @JsxFunction
517     public ImageData getImageData(final int sx, final int sy, final int sw, final int sh) {
518         final ImageData imageData = new ImageData(getRenderingBackend(), sx, sy, sw, sh);
519         imageData.setParentScope(getParentScope());
520         imageData.setPrototype(getPrototype(imageData.getClass()));
521         return imageData;
522     }
523 
524     /**
525      * Dummy placeholder.
526      */
527     @JsxFunction(functionName = "getLineDash")
528     public void lineDash() {
529         LOG.info("CanvasRenderingContext2D.getLineDash() not yet implemented");
530     }
531 
532     /**
533      * Dummy placeholder.
534      */
535     @JsxFunction(functionName = "getLineData")
536     public void lineData() {
537         LOG.info("CanvasRenderingContext2D.getLineData() not yet implemented");
538     }
539 
540     /**
541      * Dummy placeholder.
542      */
543     @JsxFunction
544     public void isPointInPath() {
545         LOG.info("CanvasRenderingContext2D.isPointInPath() not yet implemented");
546     }
547 
548     /**
549      * Connect the last point to the given point.
550      * @param x the x
551      * @param y the y
552      */
553     @JsxFunction
554     public void lineTo(final double x, final double y) {
555         getRenderingBackend().lineTo(x, y);
556     }
557 
558     /**
559      * Calculate TextMetrics for the given text.
560      * @param text the text to measure
561      * @return the text metrics
562      */
563     @JsxFunction
564     public TextMetrics measureText(final Object text) {
565         if (text == null || JavaScriptEngine.isUndefined(text)) {
566             throw JavaScriptEngine.typeError("Missing argument for CanvasRenderingContext2D.measureText().");
567         }
568 
569         final String textValue = JavaScriptEngine.toString(text);
570 
571         // TODO take font into account
572         final int width = textValue.length() * getBrowserVersion().getPixesPerChar();
573 
574         final TextMetrics metrics = new TextMetrics(width);
575         metrics.setParentScope(getParentScope());
576         metrics.setPrototype(getPrototype(metrics.getClass()));
577         return metrics;
578     }
579 
580     /**
581      * Creates a new subpath.
582      * @param x the x
583      * @param y the y
584      */
585     @JsxFunction
586     public void moveTo(final double x, final double y) {
587         getRenderingBackend().moveTo(x, y);
588     }
589 
590     /**
591      * Paints data from the given ImageData object onto the canvas.
592      * @param imageData an ImageData object containing the array of pixel values
593      * @param dx horizontal position (x coordinate) at which to place the image data in the destination canvas
594      * @param dy vertical position (y coordinate) at which to place the image data in the destination canvas
595      * @param dirtyX horizontal position (x coordinate) of the top-left corner
596      *        from which the image data will be extracted. Defaults to 0.
597      * @param dirtyY vertical position (y coordinate) of the top-left corner
598      *        from which the image data will be extracted. Defaults to 0.
599      * @param dirtyWidth width of the rectangle to be painted.
600      *        Defaults to the width of the image data.
601      * @param dirtyHeight height of the rectangle to be painted.
602      *        Defaults to the height of the image data.
603      */
604     @JsxFunction
605     public void putImageData(final ImageData imageData,
606                 final int dx, final int dy, final Object dirtyX, final Object dirtyY,
607                 final Object dirtyWidth, final Object dirtyHeight) {
608         int dirtyXArg = 0;
609         int dirtyYArg = 0;
610         int dirtyWidthArg = imageData.getWidth();
611         int dirtyHeightArg = imageData.getHeight();
612 
613         if (!JavaScriptEngine.isUndefined(dirtyX)) {
614             dirtyXArg = (int) JavaScriptEngine.toInteger(dirtyX);
615 
616             if (JavaScriptEngine.isUndefined(dirtyY)
617                     || JavaScriptEngine.isUndefined(dirtyWidth)
618                     || JavaScriptEngine.isUndefined(dirtyHeight)) {
619                 throw JavaScriptEngine.reportRuntimeError(
620                         "CanvasRenderingContext2D.putImageData() failed - seven parameters expected");
621             }
622             dirtyYArg = (int) JavaScriptEngine.toInteger(dirtyY);
623             dirtyWidthArg = (int) JavaScriptEngine.toInteger(dirtyWidth);
624             dirtyHeightArg = (int) JavaScriptEngine.toInteger(dirtyHeight);
625         }
626 
627         getRenderingBackend().putImageData(
628                 imageData.getData().getBuffer().getBuffer(), imageData.getHeight(), imageData.getWidth(),
629                 dx, dy, dirtyXArg, dirtyYArg, dirtyWidthArg, dirtyHeightArg);
630     }
631 
632     /**
633      * Draws a quadratic Bézier curve.
634      * @param controlPointX the x-coordinate of the control point
635      * @param controlPointY the y-coordinate of the control point
636      * @param endPointX the x-coordinate of the end point
637      * @param endPointY the y-coordinate of the end point
638      */
639     @JsxFunction
640     public void quadraticCurveTo(final double controlPointX, final double controlPointY,
641             final double endPointX, final double endPointY) {
642         getRenderingBackend().quadraticCurveTo(controlPointX, controlPointY, endPointX, endPointY);
643     }
644 
645     /**
646      * Renders a rectangle.
647      * @param x the x
648      * @param y the y
649      * @param w the width
650      * @param h the height
651      */
652     @JsxFunction
653     public void rect(final double x, final double y, final double w, final double h) {
654         getRenderingBackend().rect(x, y, w, h);
655     }
656 
657     /**
658      * Pops state stack and restore state.
659      */
660     @JsxFunction
661     public void restore() {
662         getRenderingBackend().restore();
663     }
664 
665     /**
666      * Adds a rotation to the transformation matrix.
667      * @param angle the angle
668      */
669     @JsxFunction
670     public void rotate(final double angle) {
671         getRenderingBackend().rotate(angle);
672     }
673 
674     /**
675      * Pushes state on state stack.
676      */
677     @JsxFunction
678     public void save() {
679         getRenderingBackend().save();
680     }
681 
682     /**
683      * Changes the transformation matrix to apply a scaling transformation with the given characteristics.
684      * @param x the scale factor in the horizontal direction
685      * @param y the scale factor in the vertical direction
686      */
687     @JsxFunction
688     public void scale(final Object x, final Object y) {
689         LOG.info("CanvasRenderingContext2D.scale() not yet implemented");
690     }
691 
692     /**
693      * Dummy placeholder.
694      */
695     @JsxFunction
696     public void setLineDash() {
697         LOG.info("CanvasRenderingContext2D.setLineDash() not yet implemented");
698     }
699 
700     /**
701      * Resets (overrides) the current transformation to the identity matrix,
702      * and then invokes a transformation described by the arguments of this method.
703      * This lets you scale, rotate, translate (move), and skew the context.
704      * @param m11 Horizontal scaling. A value of 1 results in no scaling
705      * @param m12 Vertical skewing
706      * @param m21 Horizontal skewing
707      * @param m22 Vertical scaling. A value of 1 results in no scaling
708      * @param dx Horizontal translation (moving)
709      * @param dy Vertical translation (moving).
710      */
711     @JsxFunction
712     public void setTransform(final double m11, final double m12,
713                     final double m21, final double m22, final double dx, final double dy) {
714         getRenderingBackend().setTransform(m11, m12, m21, m22, dx, dy);
715     }
716 
717     /**
718      * Calculates the strokes of all the subpaths of the current path.
719      */
720     @JsxFunction
721     public void stroke() {
722         getRenderingBackend().stroke();
723     }
724 
725     /**
726      * Strokes the specified rectangular area.
727      * @param x the x
728      * @param y the y
729      * @param w the width
730      * @param h the height
731      */
732     @JsxFunction
733     public void strokeRect(final int x, final int y, final int w, final int h) {
734         getRenderingBackend().strokeRect(x, y, w, h);
735     }
736 
737     /**
738      * Dummy placeholder.
739      */
740     @JsxFunction
741     public void strokeText() {
742         LOG.info("CanvasRenderingContext2D.strokeText() not yet implemented");
743     }
744 
745     /**
746      * Multiplies the current transformation with the matrix described by the
747      * arguments of this method. This lets you scale, rotate, translate (move),
748      * and skew the context.
749      * @param m11 Horizontal scaling. A value of 1 results in no scaling
750      * @param m12 Vertical skewing
751      * @param m21 Horizontal skewing
752      * @param m22 Vertical scaling. A value of 1 results in no scaling
753      * @param dx Horizontal translation (moving)
754      * @param dy Vertical translation (moving).
755      */
756     @JsxFunction
757     public void transform(final double m11, final double m12,
758                     final double m21, final double m22, final double dx, final double dy) {
759         getRenderingBackend().transform(m11, m12, m21, m22, dx, dy);
760     }
761 
762     /**
763      * Changes the transformation matrix to apply a translation transformation with the given characteristics.
764      * @param x the translation distance in the horizontal direction
765      * @param y the translation distance in the vertical direction
766      */
767     @JsxFunction
768     public void translate(final int x, final int y) {
769         getRenderingBackend().translate(x, y);
770     }
771 
772     /**
773      * Returns the associated {@link HTMLCanvasElement}.
774      * @return the associated {@link HTMLCanvasElement}
775      */
776     @JsxGetter
777     public HTMLCanvasElement getCanvas() {
778         return canvas_;
779     }
780 }