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