1
2
3
4
5
6
7
8
9
10
11
12
13
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
44
45
46
47
48
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
60
61 public CanvasRenderingContext2D() {
62 super();
63 }
64
65
66
67
68 @JsxConstructor
69 public void jsConstructor() {
70
71 }
72
73
74
75
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
95
96
97
98 @JsxGetter
99 public double getGlobalAlpha() {
100 return getRenderingBackend().getGlobalAlpha();
101 }
102
103
104
105
106
107 @JsxSetter
108 public void setGlobalAlpha(final double globalAlpha) {
109 getRenderingBackend().setGlobalAlpha(globalAlpha);
110 }
111
112
113
114
115
116 @JsxGetter
117 public HtmlUnitScriptable getFillStyle() {
118 LOG.info("CanvasRenderingContext2D.getFillStyle() not yet implemented");
119 return null;
120 }
121
122
123
124
125
126 @JsxSetter
127 public void setFillStyle(final String fillStyle) {
128 getRenderingBackend().setFillStyle(fillStyle);
129 }
130
131
132
133
134
135 @JsxGetter
136 public HtmlUnitScriptable getStrokeStyle() {
137 LOG.info("CanvasRenderingContext2D.getStrokeStyle() not yet implemented");
138 return null;
139 }
140
141
142
143
144
145 @JsxSetter
146 public void setStrokeStyle(final String strokeStyle) {
147 getRenderingBackend().setStrokeStyle(strokeStyle);
148 }
149
150
151
152
153
154 @JsxGetter
155 public double getLineWidth() {
156 return getRenderingBackend().getLineWidth();
157 }
158
159
160
161
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
175
176
177
178
179
180
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
190
191
192
193
194
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
204
205 @JsxFunction
206 public void beginPath() {
207 getRenderingBackend().beginPath();
208 }
209
210
211
212
213
214
215
216
217
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
227
228
229
230
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
239
240
241
242
243
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
275 }
276
277 canvas.getRenderingBackend().clip(WindingRule.NON_ZERO, null);
278 }
279
280
281
282
283 @JsxFunction
284 public void closePath() {
285 getRenderingBackend().closePath();
286 }
287
288
289
290
291
292
293
294
295
296
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
330
331
332
333
334
335
336
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
349
350 @JsxFunction
351 public void createPattern() {
352 LOG.info("CanvasRenderingContext2D.createPattern() not yet implemented");
353 }
354
355
356
357
358
359
360
361
362
363
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
376
377
378
379
380
381
382
383
384
385
386
387
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
400
401 if (JavaScriptEngine.isUndefined(sWidth)) {
402 getRenderingBackend().drawImage(imageData, 0, 0, null, null, sx, sy, null, null);
403 }
404
405
406
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
415
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
439
440
441
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
457
458
459
460
461
462
463
464
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
476
477 @JsxFunction
478 public void fill() {
479 getRenderingBackend().fill();
480 }
481
482
483
484
485
486
487
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
496
497
498
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
507
508
509
510
511
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
523
524 @JsxFunction(functionName = "getLineDash")
525 public void lineDash() {
526 LOG.info("CanvasRenderingContext2D.getLineDash() not yet implemented");
527 }
528
529
530
531
532 @JsxFunction(functionName = "getLineData")
533 public void lineData() {
534 LOG.info("CanvasRenderingContext2D.getLineData() not yet implemented");
535 }
536
537
538
539
540 @JsxFunction
541 public void isPointInPath() {
542 LOG.info("CanvasRenderingContext2D.isPointInPath() not yet implemented");
543 }
544
545
546
547
548
549
550 @JsxFunction
551 public void lineTo(final double x, final double y) {
552 getRenderingBackend().lineTo(x, y);
553 }
554
555
556
557
558
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
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
579
580
581
582 @JsxFunction
583 public void moveTo(final double x, final double y) {
584 getRenderingBackend().moveTo(x, y);
585 }
586
587
588
589
590
591
592
593
594
595
596
597
598
599
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
631
632
633
634
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
644
645
646
647
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
656
657 @JsxFunction
658 public void restore() {
659 getRenderingBackend().restore();
660 }
661
662
663
664
665
666 @JsxFunction
667 public void rotate(final double angle) {
668 getRenderingBackend().rotate(angle);
669 }
670
671
672
673
674 @JsxFunction
675 public void save() {
676 getRenderingBackend().save();
677 }
678
679
680
681
682
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
691
692 @JsxFunction
693 public void setLineDash() {
694 LOG.info("CanvasRenderingContext2D.setLineDash() not yet implemented");
695 }
696
697
698
699
700
701
702
703
704
705
706
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
716
717 @JsxFunction
718 public void stroke() {
719 getRenderingBackend().stroke();
720 }
721
722
723
724
725
726
727
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
736
737 @JsxFunction
738 public void strokeText() {
739 LOG.info("CanvasRenderingContext2D.strokeText() not yet implemented");
740 }
741
742
743
744
745
746
747
748
749
750
751
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
761
762
763
764 @JsxFunction
765 public void translate(final int x, final int y) {
766 getRenderingBackend().translate(x, y);
767 }
768
769
770
771
772
773 @JsxGetter
774 public HTMLCanvasElement getCanvas() {
775 return canvas_;
776 }
777 }