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.LineCap;
39 import org.htmlunit.platform.canvas.rendering.RenderingBackend.LineJoin;
40 import org.htmlunit.platform.canvas.rendering.RenderingBackend.WindingRule;
41 import org.htmlunit.protocol.data.DataURLConnection;
42 import org.htmlunit.util.MimeType;
43
44
45
46
47
48
49
50
51
52
53
54 @JsxClass
55 public class CanvasRenderingContext2D extends HtmlUnitScriptable {
56
57 private static final Log LOG = LogFactory.getLog(CanvasRenderingContext2D.class);
58
59 private HTMLCanvasElement canvas_;
60 private RenderingBackend renderingBackend_;
61
62
63
64
65 public CanvasRenderingContext2D() {
66 super();
67 }
68
69
70
71
72 @JsxConstructor
73 public void jsConstructor() {
74
75 }
76
77
78
79
80
81 public CanvasRenderingContext2D(final HTMLCanvasElement canvas) {
82 super();
83 canvas_ = canvas;
84 renderingBackend_ = null;
85 }
86
87 private RenderingBackend getRenderingBackend() {
88 if (renderingBackend_ == null) {
89 final int imageWidth = Math.max(1, canvas_.getWidth());
90 final int imageHeight = Math.max(1, canvas_.getHeight());
91
92 renderingBackend_ = Platform.getRenderingBackend(imageWidth, imageHeight);
93 }
94 return renderingBackend_;
95 }
96
97
98
99
100
101
102 @JsxGetter
103 public double getGlobalAlpha() {
104 return getRenderingBackend().getGlobalAlpha();
105 }
106
107
108
109
110
111 @JsxSetter
112 public void setGlobalAlpha(final double globalAlpha) {
113 getRenderingBackend().setGlobalAlpha(globalAlpha);
114 }
115
116
117
118
119
120 @JsxGetter
121 public HtmlUnitScriptable getFillStyle() {
122 LOG.info("CanvasRenderingContext2D.getFillStyle() not yet implemented");
123 return null;
124 }
125
126
127
128
129
130 @JsxSetter
131 public void setFillStyle(final String fillStyle) {
132 getRenderingBackend().setFillStyle(fillStyle);
133 }
134
135
136
137
138
139 @JsxGetter
140 public HtmlUnitScriptable getStrokeStyle() {
141 LOG.info("CanvasRenderingContext2D.getStrokeStyle() not yet implemented");
142 return null;
143 }
144
145
146
147
148
149 @JsxSetter
150 public void setStrokeStyle(final String strokeStyle) {
151 getRenderingBackend().setStrokeStyle(strokeStyle);
152 }
153
154
155
156
157
158 @JsxGetter
159 public double getLineWidth() {
160 return getRenderingBackend().getLineWidth();
161 }
162
163
164
165
166
167 @JsxSetter
168 public void setLineWidth(final Object lineWidth) {
169 if (!JavaScriptEngine.isUndefined(lineWidth)) {
170 final double width = JavaScriptEngine.toNumber(lineWidth);
171 if (!Double.isNaN(width)) {
172 getRenderingBackend().setLineWidth((float) width);
173 }
174 }
175 }
176
177
178
179
180
181
182
183
184
185
186 @JsxFunction
187 public void arc(final double x, final double y, final double radius, final double startAngle,
188 final double endAngle, final boolean anticlockwise) {
189 getRenderingBackend().arc(x, y, radius, startAngle, endAngle, anticlockwise);
190 }
191
192
193
194
195
196
197
198
199
200 @JsxFunction
201 public void arcTo(final double x1, final double y1, final double x2, final double y2,
202 final double radius) {
203 LOG.info("CanvasRenderingContext2D.arcTo() not yet implemented");
204 }
205
206
207
208
209 @JsxFunction
210 public void beginPath() {
211 getRenderingBackend().beginPath();
212 }
213
214
215
216
217
218
219
220
221
222
223 @JsxFunction
224 public void bezierCurveTo(final double cp1x, final double cp1y, final double cp2x, final double cp2y,
225 final double x, final double y) {
226 getRenderingBackend().bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
227 }
228
229
230
231
232
233
234
235
236 @JsxFunction
237 public void clearRect(final double x, final double y, final double w, final double h) {
238 getRenderingBackend().clearRect(x, y, w, h);
239 }
240
241
242
243
244
245
246
247
248
249 @JsxFunction
250 public static void clip(final Context context, final VarScope scope,
251 final Scriptable thisObj, final Object[] args, final Function function) {
252 if (!(thisObj instanceof CanvasRenderingContext2D canvas)) {
253 throw JavaScriptEngine.reportRuntimeError(
254 "CanvasRenderingContext2D.clip() failed - this is not a CanvasRenderingContext2D");
255 }
256
257 RenderingBackend.WindingRule windingRule = WindingRule.NON_ZERO;
258 if (args.length == 1) {
259 final String windingRuleParam = JavaScriptEngine.toString(args[0]);
260 if ("evenodd".contentEquals(windingRuleParam)) {
261 windingRule = WindingRule.EVEN_ODD;
262 }
263 canvas.getRenderingBackend().clip(windingRule, null);
264 return;
265 }
266
267 if (args.length > 1) {
268 if (!(args[0] instanceof Path2D)) {
269 throw JavaScriptEngine.reportRuntimeError(
270 "CanvasRenderingContext2D.clip() failed - the first parameter has to be a Path2D");
271 }
272
273 final String windingRuleParam = JavaScriptEngine.toString(args[1]);
274 if ("evenodd".contentEquals(windingRuleParam)) {
275 windingRule = WindingRule.EVEN_ODD;
276 }
277
278 LOG.info("CanvasRenderingContext2D.clip(path, fillRule) not yet implemented");
279
280 return;
281 }
282
283 canvas.getRenderingBackend().clip(WindingRule.NON_ZERO, null);
284 }
285
286
287
288
289 @JsxFunction
290 public void closePath() {
291 getRenderingBackend().closePath();
292 }
293
294
295
296
297
298
299
300
301
302
303
304 @JsxFunction
305 public static ImageData createImageData(final Context context, final VarScope scope,
306 final Scriptable thisObj, final Object[] args, final Function function) {
307 if (!(thisObj instanceof CanvasRenderingContext2D canvas)) {
308 throw JavaScriptEngine.reportRuntimeError(
309 "CanvasRenderingContext2D.createImageData() failed - this is not a CanvasRenderingContext2D");
310 }
311
312 if (args.length > 0 && args[0] instanceof ImageData imageDataParameter) {
313 final ImageData imageData = new ImageData(null,
314 0, 0, imageDataParameter.getWidth(), imageDataParameter.getHeight());
315 imageData.setParentScope(scope);
316 imageData.setPrototype(canvas.getPrototype(imageData.getClass()));
317 return imageData;
318 }
319
320 if (args.length > 1) {
321 final int width = Math.abs((int) JavaScriptEngine.toInteger(args, 0));
322 final int height = Math.abs((int) JavaScriptEngine.toInteger(args, 1));
323 final ImageData imageData = new ImageData(null, 0, 0, width, height);
324 imageData.setParentScope(canvas.getParentScope());
325 imageData.setPrototype(canvas.getPrototype(imageData.getClass()));
326 return imageData;
327 }
328
329 throw JavaScriptEngine.reportRuntimeError(
330 "CanvasRenderingContext2D.createImageData() failed - "
331 + "wrong parameters given (" + StringUtils.join(args, ", ") + ")");
332 }
333
334
335
336
337
338
339
340
341
342 @JsxFunction
343 public CanvasGradient createLinearGradient(final double x0, final double y0, final double x1,
344 final Object y1) {
345 final CanvasGradient canvasGradient = new CanvasGradient();
346 canvasGradient.setParentScope(getParentScope());
347 canvasGradient.setPrototype(getPrototype(canvasGradient.getClass()));
348 return canvasGradient;
349 }
350
351
352
353
354 @JsxFunction
355 public void createPattern() {
356 LOG.info("CanvasRenderingContext2D.createPattern() not yet implemented");
357 }
358
359
360
361
362
363
364
365
366
367
368
369 @JsxFunction
370 public CanvasGradient createRadialGradient(final double x0, final double y0,
371 final double r0, final double x1, final double y1, final double r1) {
372 final CanvasGradient canvasGradient = new CanvasGradient();
373 canvasGradient.setParentScope(getParentScope());
374 canvasGradient.setPrototype(getPrototype(canvasGradient.getClass()));
375 return canvasGradient;
376 }
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 @JsxFunction
394 @SuppressWarnings("unused")
395 public void drawImage(final Object image, final int sx, final int sy, final Object sWidth, final Object sHeight,
396 final Object dx, final Object dy, final Object dWidth, final Object dHeight) {
397
398 if (image instanceof HTMLImageElement imageElem) {
399 try {
400 final org.htmlunit.platform.image.ImageData imageData
401 = ((HtmlImage) imageElem.getDomNodeOrDie()).getImageData();
402
403
404
405 if (JavaScriptEngine.isUndefined(sWidth)) {
406 getRenderingBackend().drawImage(imageData, 0, 0, null, null, sx, sy, null, null);
407 }
408
409
410
411 else if (JavaScriptEngine.isUndefined(dx)) {
412 final int dWidthI = JavaScriptEngine.toInt32(sWidth);
413 final int dHeightI = JavaScriptEngine.toInt32(sHeight);
414
415 getRenderingBackend().drawImage(imageData, 0, 0, null, null, sx, sy, dWidthI, dHeightI);
416 }
417
418
419
420 else {
421 final int sWidthI = JavaScriptEngine.toInt32(sWidth);
422 final int sHeightI = JavaScriptEngine.toInt32(sHeight);
423
424 final int dxI = JavaScriptEngine.toInt32(dx);
425 final int dyI = JavaScriptEngine.toInt32(dy);
426 final int dWidthI = JavaScriptEngine.toInt32(dWidth);
427 final int dHeightI = JavaScriptEngine.toInt32(dHeight);
428
429 getRenderingBackend().drawImage(imageData,
430 sx, sy, sWidthI, sHeightI, dxI, dyI, dWidthI, dHeightI);
431 }
432 }
433 catch (final IOException ex) {
434 LOG.info("There is no ImageReader available for your image with src '" + imageElem.getSrc() + "'. "
435 + "Please have a look at https://www.htmlunit.org/images-howto.html "
436 + "for a possible solution.");
437 }
438 }
439 }
440
441
442
443
444
445
446
447 public String toDataURL(String type) {
448 try {
449 if (type == null) {
450 type = MimeType.IMAGE_PNG;
451 }
452 return DataURLConnection.DATA_PREFIX + type + ";base64," + getRenderingBackend().encodeToString(type);
453 }
454 catch (final IOException ex) {
455 throw JavaScriptEngine.throwAsScriptRuntimeEx(ex);
456 }
457 }
458
459
460
461
462
463
464
465
466
467
468
469
470 @JsxFunction
471 public void ellipse(final double x, final double y,
472 final double radiusX, final double radiusY,
473 final double rotation, final double startAngle, final double endAngle,
474 final boolean anticlockwise) {
475 getRenderingBackend().ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
476 }
477
478
479
480
481
482
483
484
485
486 @JsxFunction
487 public static void fill(final Context context, final VarScope scope,
488 final Scriptable thisObj, final Object[] args, final Function function) {
489 if (!(thisObj instanceof CanvasRenderingContext2D renderingCtx)) {
490 throw JavaScriptEngine.reportRuntimeError(
491 "CanvasRenderingContext2D.fill() failed - this is not a CanvasRenderingContext2D");
492 }
493
494
495
496
497 String fillRuleStr = null;
498 if (args.length > 1) {
499 fillRuleStr = JavaScriptEngine.toString(args[1]);
500 }
501 else if (args.length > 0) {
502 fillRuleStr = JavaScriptEngine.toString(args[0]);
503 }
504
505 RenderingBackend.WindingRule windingRule = RenderingBackend.WindingRule.NON_ZERO;
506 if (fillRuleStr != null) {
507 if ("evenodd".equals(fillRuleStr)) {
508 windingRule = RenderingBackend.WindingRule.EVEN_ODD;
509 }
510 else if (!"nonzero".equals(fillRuleStr)) {
511
512
513
514
515 if (LOG.isWarnEnabled()) {
516 LOG.warn("fill() called with unrecognised fillRule: '" + fillRuleStr + "', using 'nonzero'.");
517 }
518 }
519 }
520
521 renderingCtx.getRenderingBackend().fill(windingRule);
522 }
523
524
525
526
527
528
529
530
531 @JsxFunction
532 public void fillRect(final int x, final int y, final int w, final int h) {
533 getRenderingBackend().fillRect(x, y, w, h);
534 }
535
536
537
538
539
540
541
542 @JsxFunction
543 public void fillText(final String text, final double x, final double y) {
544 getRenderingBackend().fillText(text, x, y);
545 }
546
547
548
549
550
551
552
553
554
555 @JsxFunction
556 public ImageData getImageData(final int sx, final int sy, final int sw, final int sh) {
557 final ImageData imageData = new ImageData(getRenderingBackend(), sx, sy, sw, sh);
558 imageData.setParentScope(getParentScope());
559 imageData.setPrototype(getPrototype(imageData.getClass()));
560 return imageData;
561 }
562
563
564
565
566 @JsxFunction(functionName = "getLineDash")
567 public void lineDash() {
568 LOG.info("CanvasRenderingContext2D.getLineDash() not yet implemented");
569 }
570
571
572
573
574 @JsxFunction(functionName = "getLineData")
575 public void lineData() {
576 LOG.info("CanvasRenderingContext2D.getLineData() not yet implemented");
577 }
578
579
580
581
582 @JsxFunction
583 public void isPointInPath() {
584 LOG.info("CanvasRenderingContext2D.isPointInPath() not yet implemented");
585 }
586
587
588
589
590
591
592 @JsxFunction
593 public void lineTo(final double x, final double y) {
594 getRenderingBackend().lineTo(x, y);
595 }
596
597
598
599
600
601
602 @JsxFunction
603 public TextMetrics measureText(final Object text) {
604 if (text == null || JavaScriptEngine.isUndefined(text)) {
605 throw JavaScriptEngine.typeError("Missing argument for CanvasRenderingContext2D.measureText().");
606 }
607
608 final String textValue = JavaScriptEngine.toString(text);
609
610
611 final int width = textValue.length() * getBrowserVersion().getPixelsPerChar();
612
613 final TextMetrics metrics = new TextMetrics(width);
614 metrics.setParentScope(getParentScope());
615 metrics.setPrototype(getPrototype(metrics.getClass()));
616 return metrics;
617 }
618
619
620
621
622
623
624 @JsxFunction
625 public void moveTo(final double x, final double y) {
626 getRenderingBackend().moveTo(x, y);
627 }
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643 @JsxFunction
644 public void putImageData(final ImageData imageData,
645 final int dx, final int dy, final Object dirtyX, final Object dirtyY,
646 final Object dirtyWidth, final Object dirtyHeight) {
647 int dirtyXArg = 0;
648 int dirtyYArg = 0;
649 int dirtyWidthArg = imageData.getWidth();
650 int dirtyHeightArg = imageData.getHeight();
651
652 if (!JavaScriptEngine.isUndefined(dirtyX)) {
653 dirtyXArg = (int) JavaScriptEngine.toInteger(dirtyX);
654
655 if (JavaScriptEngine.isUndefined(dirtyY)
656 || JavaScriptEngine.isUndefined(dirtyWidth)
657 || JavaScriptEngine.isUndefined(dirtyHeight)) {
658 throw JavaScriptEngine.reportRuntimeError(
659 "CanvasRenderingContext2D.putImageData() failed - seven parameters expected");
660 }
661 dirtyYArg = (int) JavaScriptEngine.toInteger(dirtyY);
662 dirtyWidthArg = (int) JavaScriptEngine.toInteger(dirtyWidth);
663 dirtyHeightArg = (int) JavaScriptEngine.toInteger(dirtyHeight);
664 }
665
666 getRenderingBackend().putImageData(
667 imageData.getData().getBuffer().getBuffer(), imageData.getWidth(), imageData.getHeight(),
668 dx, dy, dirtyXArg, dirtyYArg, dirtyWidthArg, dirtyHeightArg);
669 }
670
671
672
673
674
675
676
677
678 @JsxFunction
679 public void quadraticCurveTo(final double controlPointX, final double controlPointY,
680 final double endPointX, final double endPointY) {
681 getRenderingBackend().quadraticCurveTo(controlPointX, controlPointY, endPointX, endPointY);
682 }
683
684
685
686
687
688
689
690
691 @JsxFunction
692 public void rect(final double x, final double y, final double w, final double h) {
693 getRenderingBackend().rect(x, y, w, h);
694 }
695
696
697
698
699 @JsxFunction
700 public void restore() {
701 getRenderingBackend().restore();
702 }
703
704
705
706
707
708 @JsxFunction
709 public void rotate(final double angle) {
710 getRenderingBackend().rotate(angle);
711 }
712
713
714
715
716 @JsxFunction
717 public void save() {
718 getRenderingBackend().save();
719 }
720
721
722
723
724
725
726 @JsxFunction
727 public void scale(final Object x, final Object y) {
728 LOG.info("CanvasRenderingContext2D.scale() not yet implemented");
729 }
730
731
732
733
734 @JsxFunction
735 public void setLineDash() {
736 LOG.info("CanvasRenderingContext2D.setLineDash() not yet implemented");
737 }
738
739
740
741
742
743
744
745
746
747
748
749
750 @JsxFunction
751 public void setTransform(final double m11, final double m12,
752 final double m21, final double m22, final double dx, final double dy) {
753 getRenderingBackend().setTransform(m11, m12, m21, m22, dx, dy);
754 }
755
756
757
758
759 @JsxFunction
760 public void stroke() {
761 getRenderingBackend().stroke();
762 }
763
764
765
766
767
768
769
770
771 @JsxFunction
772 public void strokeRect(final double x, final double y, final double w, final double h) {
773 getRenderingBackend().strokeRect(x, y, w, h);
774 }
775
776
777
778
779 @JsxFunction
780 public void strokeText() {
781 LOG.info("CanvasRenderingContext2D.strokeText() not yet implemented");
782 }
783
784
785
786
787
788
789
790
791
792
793
794
795 @JsxFunction
796 public void transform(final double m11, final double m12,
797 final double m21, final double m22, final double dx, final double dy) {
798 getRenderingBackend().transform(m11, m12, m21, m22, dx, dy);
799 }
800
801
802
803
804
805
806 @JsxFunction
807 public void translate(final double x, final double y) {
808 getRenderingBackend().translate(x, y);
809 }
810
811
812
813
814
815 @JsxGetter
816 public HTMLCanvasElement getCanvas() {
817 return canvas_;
818 }
819
820
821
822
823
824
825
826
827 @JsxGetter
828 public String getLineJoin() {
829 switch (getRenderingBackend().getLineJoin()) {
830 case ROUND:
831 return "round";
832 case BEVEL:
833 return "bevel";
834 default:
835 return "miter";
836 }
837 }
838
839
840
841
842
843 @JsxSetter
844 public void setLineJoin(final String lineJoin) {
845 switch (lineJoin) {
846 case "round":
847 getRenderingBackend().setLineJoin(LineJoin.ROUND);
848 break;
849 case "bevel":
850 getRenderingBackend().setLineJoin(LineJoin.BEVEL);
851 break;
852 case "miter":
853 getRenderingBackend().setLineJoin(LineJoin.MITER);
854 break;
855 default:
856
857 }
858 }
859
860
861
862
863
864
865
866
867 @JsxGetter
868 public String getLineCap() {
869 switch (getRenderingBackend().getLineCap()) {
870 case BUTT:
871 return "butt";
872 case ROUND:
873 return "round";
874 default:
875 return "square";
876 }
877 }
878
879
880
881
882
883 @JsxSetter
884 public void setLineCap(final String lineCap) {
885 switch (lineCap) {
886 case "butt":
887 getRenderingBackend().setLineCap(LineCap.BUTT);
888 break;
889 case "round":
890 getRenderingBackend().setLineCap(LineCap.ROUND);
891 break;
892 case "square":
893 getRenderingBackend().setLineCap(LineCap.SQUARE);
894 break;
895 default:
896
897 }
898 }
899 }