[canvaskit] Expand canvas2d API
Made addPath take one more arg to allow for append/expand
(which makes emulating the HTML canvas easier).
Add Gold test for various lineTo/pathTo, etc.
Make CanvasKit.Color() choose a better value for alpha
when omitted (was 0, should be 1).
Add some parsing logic to deal with colors/font sizes.
Fonts are going to be rather complex it seems.
Moves some arc-related logic to the JS side, (although
this should preserve the behavior of CanvasKit.arc() to
behave like the Canvas implementation)
Make Examples and tests to a side-by-side comparison to
HTML canvas where applicable.
Add a Changelog for PathKit. There was a bug (I thought), but
turns out I was wrong. The Changelog will be for future
bug fixes.
Bug: skia:
Change-Id: I1bd603fdb518232604b098e24543e3453015b504
Reviewed-on: https://skia-review.googlesource.com/c/170446
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/experimental/canvaskit/.gitignore b/experimental/canvaskit/.gitignore
new file mode 100644
index 0000000..d8b83df
--- /dev/null
+++ b/experimental/canvaskit/.gitignore
@@ -0,0 +1 @@
+package-lock.json
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index 6173ff9..fc387ad 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -9,7 +9,8 @@
border: 1px dashed #AAA;
}
- #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding {
+ #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding
+ #api1_c, #api2_c {
width: 300px;
height: 300px;
}
@@ -38,7 +39,10 @@
<canvas id=nima_example width=300 height=300></canvas>
<h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
-<img id=api1 width=300 height=300/>
+<img id=api1 width=300 height=300>
+<canvas id=api1_c width=300 height=300></canvas>
+<img id=api2 width=300 height=300>
+<canvas id=api2_c width=300 height=300></canvas>
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
@@ -81,6 +85,7 @@
NimaExample(CanvasKit, nimaFile, nimaTexture);
CanvasAPI1(CanvasKit);
+ CanvasAPI2(CanvasKit);
VertexAPI1(CanvasKit);
VertexAPI2(CanvasKit, bonesImage);
@@ -424,26 +429,77 @@
function CanvasAPI1(CanvasKit) {
if (CanvasKit.gpu) {
+ console.warn("Skipping canvas example because its GPU build");
return;
}
- let canvas = CanvasKit.MakeCanvas(300, 300);
+ let skcanvas = CanvasKit.MakeCanvas(300, 300);
+ let realCanvas = document.getElementById('api1_c');
- let ctx = canvas.getContext('2d');
- ctx.font = '30px Impact'
- ctx.rotate(.1);
- let text = ctx.measureText('Awesome');
- ctx.fillText('Awesome ', 50, 100);
- ctx.strokeText('Groovy!', 60+text.width, 100);
+ for (let canvas of [skcanvas, realCanvas]) {
+ let ctx = canvas.getContext('2d');
+ ctx.font = '30px Impact'
+ ctx.rotate(.1);
+ let text = ctx.measureText('Awesome');
+ ctx.fillText('Awesome ', 50, 100);
+ ctx.strokeText('Groovy!', 60+text.width, 100);
- // Draw line under Awesome
- ctx.strokeStyle = 'rgba(125,0,0,0.5)';
- ctx.beginPath();
- ctx.lineTo(50, 102);
- ctx.lineTo(50 + text.width, 102);
- ctx.stroke();
+ // Draw line under Awesome
+ ctx.strokeStyle = 'rgba(125,0,0,0.5)';
+ ctx.beginPath();
+ ctx.lineWidth=6;
+ ctx.lineTo(50, 105);
+ ctx.lineTo(50 + text.width, 105);
+ ctx.stroke();
+ }
// TODO load image
- document.getElementById('api1').src = canvas.toDataURL();
+ document.getElementById('api1').src = skcanvas.toDataURL();
+ }
+
+ function CanvasAPI2(CanvasKit) {
+ if (CanvasKit.gpu) {
+ console.warn("Skipping canvas example because its GPU build");
+ return;
+ }
+ let skcanvas = CanvasKit.MakeCanvas(200, 200);
+ let realCanvas = document.getElementById('api2_c');
+ realCanvas.width = 200;
+ realCanvas.height = 200;
+
+ for (let canvas of [skcanvas, realCanvas]) {
+ let ctx = canvas.getContext('2d');
+ ctx.moveTo(20, 5);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(40, 10);
+ ctx.lineTo(50, 20);
+ ctx.lineTo(60, 0);
+ ctx.lineTo(20, 5);
+
+ ctx.moveTo(20, 80);
+ ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+ ctx.moveTo(36, 148);
+ ctx.quadraticCurveTo(66, 188, 120, 136);
+ ctx.lineTo(36, 148);
+
+ ctx.rect(5, 170, 20, 25);
+
+ ctx.moveTo(150, 180);
+ ctx.arcTo(150, 100, 50, 200, 20);
+ ctx.lineTo(160, 160);
+
+ ctx.moveTo(20, 120);
+ ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
+ ctx.lineTo(20, 120);
+
+ ctx.moveTo(150, 5);
+ ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
+
+ ctx.lineWidth = 4/3;
+
+ ctx.stroke();
+ }
+ document.getElementById('api2').src = skcanvas.toDataURL();
}
function NimaExample(CanvasKit, nimaFile, nimaTexture) {
@@ -522,7 +578,6 @@
shader.delete();
paint.delete();
surface.delete();
-
}
function VertexAPI2(CanvasKit, bonesImage) {
@@ -589,13 +644,7 @@
//surface.delete();
}
- /**
- SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW};
- SkPaint paint;
- paint.setShader(SkGradientShader::MakeRadial(
- SkPoint::Make(128.0f, 128.0f), 180.0f,
- colors, nullptr, 2, SkShader::kClamp_TileMode, 0, nullptr));
- canvas->drawPaint(paint);*/
+
function GradiantAPI1(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('gradient1');
if (!surface) {
diff --git a/experimental/canvaskit/canvaskit/node.example.js b/experimental/canvaskit/canvaskit/node.example.js
index 55140e6..955b281 100644
--- a/experimental/canvaskit/canvaskit/node.example.js
+++ b/experimental/canvaskit/canvaskit/node.example.js
@@ -18,6 +18,7 @@
// Draw line under Awesome
ctx.strokeStyle = 'rgba(125,0,0,0.5)';
ctx.beginPath();
+ ctx.lineWidth=6;
ctx.lineTo(50, 102);
ctx.lineTo(50 + text.width, 102);
ctx.stroke();
@@ -36,7 +37,7 @@
const paint = new CanvasKit.SkPaint();
const textPaint = new CanvasKit.SkPaint();
- textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+ textPaint.setColor(CanvasKit.Color(40, 0, 0));
textPaint.setTextSize(30);
textPaint.setAntiAlias(true);
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 15d64bf..a7fb875 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -151,23 +151,27 @@
// These Apply methods, combined with the smarter binding code allow for chainable
// commands that don't leak if the return value is ignored (i.e. when used intuitively).
+void ApplyAddArc(SkPath& orig, const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
+ orig.addArc(oval, startAngle, sweepAngle);
+}
+
void ApplyAddPath(SkPath& orig, const SkPath& newPath,
SkScalar scaleX, SkScalar skewX, SkScalar transX,
SkScalar skewY, SkScalar scaleY, SkScalar transY,
- SkScalar pers0, SkScalar pers1, SkScalar pers2) {
+ SkScalar pers0, SkScalar pers1, SkScalar pers2,
+ bool extendPath) {
SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
skewY , scaleY, transY,
pers0 , pers1 , pers2);
- orig.addPath(newPath, m);
+ orig.addPath(newPath, m, extendPath ? SkPath::kExtend_AddPathMode :
+ SkPath::kAppend_AddPathMode);
}
-void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
- SkScalar startAngle, SkScalar endAngle, bool ccw) {
- SkPath temp;
- SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
- const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
- temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
- path.addPath(temp, SkPath::kExtend_AddPathMode);
+void ApplyAddRect(SkPath& path, SkScalar left, SkScalar top,
+ SkScalar right, SkScalar bottom, bool ccw) {
+ path.addRect(left, top, right, bottom,
+ ccw ? SkPath::Direction::kCW_Direction :
+ SkPath::Direction::kCCW_Direction);
}
void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
@@ -495,9 +499,11 @@
class_<SkPath>("SkPath")
.constructor<>()
.constructor<const SkPath&>()
+ .function("_addArc", &ApplyAddArc)
// interface.js has 3 overloads of addPath
.function("_addPath", &ApplyAddPath)
- .function("_arc", &ApplyAddArc)
+ // interface.js has 4 overloads of addRect
+ .function("_addRect", &ApplyAddRect)
.function("_arcTo", &ApplyArcTo)
.function("_close", &ApplyClose)
.function("_conicTo", &ApplyConicTo)
@@ -505,7 +511,7 @@
.function("_lineTo", &ApplyLineTo)
.function("_moveTo", &ApplyMoveTo)
.function("_quadTo", &ApplyQuadTo)
- .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
+ .function("_transform", select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
// PathEffects
.function("_dash", &ApplyDash)
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index b1b8cc2..7c5159f 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -52,6 +52,10 @@
_getRasterN32PremulSurface: function() {},
_getWebGLSurface: function() {},
+ // The testing object is meant to expose internal functions
+ // for more fine-grained testing, e.g. parseColor
+ _testing: {},
+
// Objects and properties on CanvasKit
NimaActor: {
@@ -90,11 +94,17 @@
_encodeToDataWithFormat: function() {},
},
+ SkMatrix: {
+ rotated: function() {},
+ },
+
SkPath: {
// public API (from C++ bindings)
// private API
+ _addArc: function() {},
_addPath: function() {},
+ _addRect: function() {},
_arc: function() {},
_arcTo: function() {},
_close: function() {},
@@ -165,16 +175,16 @@
gpu: {},
skottie: {},
PaintStyle: {
- FILL: {},
- STROKE: {},
- STROKE_AND_FILL: {},
+ Fill: {},
+ Stroke: {},
+ StrokeAndFill: {},
},
FillType: {
- WINDING: {},
- EVENODD: {},
- INVERSE_WINDING: {},
- INVERSE_EVENODD: {},
+ Winding: {},
+ EvenOdd: {},
+ InverseWinding: {},
+ InverseEvenOdd: {},
},
ImageFormat: {
@@ -213,7 +223,9 @@
// Public API things that are newly declared in the JS should go here.
// It's not enough to declare them above, because closure can still erase them
// unless they go on the prototype.
+CanvasKit.SkPath.prototype.addArc = function() {};
CanvasKit.SkPath.prototype.addPath = function() {};
+CanvasKit.SkPath.prototype.addRect = function() {};
CanvasKit.SkPath.prototype.arc = function() {};
CanvasKit.SkPath.prototype.arcTo = function() {};
CanvasKit.SkPath.prototype.close = function() {};
diff --git a/experimental/canvaskit/helper.js b/experimental/canvaskit/helper.js
index 6cc1564..fa035ab 100644
--- a/experimental/canvaskit/helper.js
+++ b/experimental/canvaskit/helper.js
@@ -1,5 +1,5 @@
-// Adds any extra JS functions/helpers we want to CanvasKit.
+// Adds any extra JS functions/helpers we want to add to CanvasKit.
// Wrapped in a function to avoid leaking global variables.
(function(CanvasKit){
@@ -10,7 +10,11 @@
// Colors are just a 32 bit number with 8 bits each of a, r, g, b
// The API is the same as CSS's representation of color rgba(), that is
// r,g,b are 0-255, and a is 0.0 to 1.0.
+ // if a is omitted, it will be assumed to be 1.0
CanvasKit.Color = function(r, g, b, a) {
+ if (a === undefined) {
+ a = 1;
+ }
return (clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0);
}
}(Module)); // When this file is loaded in, the high level object is "Module";
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/_namedcolors.js b/experimental/canvaskit/htmlcanvas/_namedcolors.js
new file mode 100644
index 0000000..f7c9ac8
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/_namedcolors.js
@@ -0,0 +1,165 @@
+// This node script is meant to pre-compute the named colors.
+// node ./htmlcanvas/_namedcolors.js --expose-wasm
+// Put the result in canvas2d.js
+// This should likely never need to be re-run.
+
+const CanvasKitInit = require('../canvaskit/bin/canvaskit.js');
+
+CanvasKitInit({
+ locateFile: (file) => __dirname + '/../canvaskit/bin/'+file,
+}).then((CanvasKit) => {
+ let colorMap = {
+ // From https://drafts.csswg.org/css-color/#named-colors
+ 'aliceblue': CanvasKit.Color(240, 248, 255),
+ 'antiquewhite': CanvasKit.Color(250, 235, 215),
+ 'aqua': CanvasKit.Color(0, 255, 255),
+ 'aquamarine': CanvasKit.Color(127, 255, 212),
+ 'azure': CanvasKit.Color(240, 255, 255),
+ 'beige': CanvasKit.Color(245, 245, 220),
+ 'bisque': CanvasKit.Color(255, 228, 196),
+ 'black': CanvasKit.Color(0, 0, 0),
+ 'blanchedalmond': CanvasKit.Color(255, 235, 205),
+ 'blue': CanvasKit.Color(0, 0, 255),
+ 'blueviolet': CanvasKit.Color(138, 43, 226),
+ 'brown': CanvasKit.Color(165, 42, 42),
+ 'burlywood': CanvasKit.Color(222, 184, 135),
+ 'cadetblue': CanvasKit.Color(95, 158, 160),
+ 'chartreuse': CanvasKit.Color(127, 255, 0),
+ 'chocolate': CanvasKit.Color(210, 105, 30),
+ 'coral': CanvasKit.Color(255, 127, 80),
+ 'cornflowerblue': CanvasKit.Color(100, 149, 237),
+ 'cornsilk': CanvasKit.Color(255, 248, 220),
+ 'crimson': CanvasKit.Color(220, 20, 60),
+ 'cyan': CanvasKit.Color(0, 255, 255),
+ 'darkblue': CanvasKit.Color(0, 0, 139),
+ 'darkcyan': CanvasKit.Color(0, 139, 139),
+ 'darkgoldenrod': CanvasKit.Color(184, 134, 11),
+ 'darkgray': CanvasKit.Color(169, 169, 169),
+ 'darkgreen': CanvasKit.Color(0, 100, 0),
+ 'darkgrey': CanvasKit.Color(169, 169, 169),
+ 'darkkhaki': CanvasKit.Color(189, 183, 107),
+ 'darkmagenta': CanvasKit.Color(139, 0, 139),
+ 'darkolivegreen': CanvasKit.Color(85, 107, 47),
+ 'darkorange': CanvasKit.Color(255, 140, 0),
+ 'darkorchid': CanvasKit.Color(153, 50, 204),
+ 'darkred': CanvasKit.Color(139, 0, 0),
+ 'darksalmon': CanvasKit.Color(233, 150, 122),
+ 'darkseagreen': CanvasKit.Color(143, 188, 143),
+ 'darkslateblue': CanvasKit.Color(72, 61, 139),
+ 'darkslategray': CanvasKit.Color(47, 79, 79),
+ 'darkslategrey': CanvasKit.Color(47, 79, 79),
+ 'darkturquoise': CanvasKit.Color(0, 206, 209),
+ 'darkviolet': CanvasKit.Color(148, 0, 211),
+ 'deeppink': CanvasKit.Color(255, 20, 147),
+ 'deepskyblue': CanvasKit.Color(0, 191, 255),
+ 'dimgray': CanvasKit.Color(105, 105, 105),
+ 'dimgrey': CanvasKit.Color(105, 105, 105),
+ 'dodgerblue': CanvasKit.Color(30, 144, 255),
+ 'firebrick': CanvasKit.Color(178, 34, 34),
+ 'floralwhite': CanvasKit.Color(255, 250, 240),
+ 'forestgreen': CanvasKit.Color(34, 139, 34),
+ 'fuchsia': CanvasKit.Color(255, 0, 255),
+ 'gainsboro': CanvasKit.Color(220, 220, 220),
+ 'ghostwhite': CanvasKit.Color(248, 248, 255),
+ 'gold': CanvasKit.Color(255, 215, 0),
+ 'goldenrod': CanvasKit.Color(218, 165, 32),
+ 'gray': CanvasKit.Color(128, 128, 128),
+ 'green': CanvasKit.Color(0, 128, 0),
+ 'greenyellow': CanvasKit.Color(173, 255, 47),
+ 'grey': CanvasKit.Color(128, 128, 128),
+ 'honeydew': CanvasKit.Color(240, 255, 240),
+ 'hotpink': CanvasKit.Color(255, 105, 180),
+ 'indianred': CanvasKit.Color(205, 92, 92),
+ 'indigo': CanvasKit.Color(75, 0, 130),
+ 'ivory': CanvasKit.Color(255, 255, 240),
+ 'khaki': CanvasKit.Color(240, 230, 140),
+ 'lavender': CanvasKit.Color(230, 230, 250),
+ 'lavenderblush': CanvasKit.Color(255, 240, 245),
+ 'lawngreen': CanvasKit.Color(124, 252, 0),
+ 'lemonchiffon': CanvasKit.Color(255, 250, 205),
+ 'lightblue': CanvasKit.Color(173, 216, 230),
+ 'lightcoral': CanvasKit.Color(240, 128, 128),
+ 'lightcyan': CanvasKit.Color(224, 255, 255),
+ 'lightgoldenrodyellow': CanvasKit.Color(250, 250, 210),
+ 'lightgray': CanvasKit.Color(211, 211, 211),
+ 'lightgreen': CanvasKit.Color(144, 238, 144),
+ 'lightgrey': CanvasKit.Color(211, 211, 211),
+ 'lightpink': CanvasKit.Color(255, 182, 193),
+ 'lightsalmon': CanvasKit.Color(255, 160, 122),
+ 'lightseagreen': CanvasKit.Color(32, 178, 170),
+ 'lightskyblue': CanvasKit.Color(135, 206, 250),
+ 'lightslategray': CanvasKit.Color(119, 136, 153),
+ 'lightslategrey': CanvasKit.Color(119, 136, 153),
+ 'lightsteelblue': CanvasKit.Color(176, 196, 222),
+ 'lightyellow': CanvasKit.Color(255, 255, 224),
+ 'lime': CanvasKit.Color(0, 255, 0),
+ 'limegreen': CanvasKit.Color(50, 205, 50),
+ 'linen': CanvasKit.Color(250, 240, 230),
+ 'magenta': CanvasKit.Color(255, 0, 255),
+ 'maroon': CanvasKit.Color(128, 0, 0),
+ 'mediumaquamarine': CanvasKit.Color(102, 205, 170),
+ 'mediumblue': CanvasKit.Color(0, 0, 205),
+ 'mediumorchid': CanvasKit.Color(186, 85, 211),
+ 'mediumpurple': CanvasKit.Color(147, 112, 219),
+ 'mediumseagreen': CanvasKit.Color(60, 179, 113),
+ 'mediumslateblue': CanvasKit.Color(123, 104, 238),
+ 'mediumspringgreen': CanvasKit.Color(0, 250, 154),
+ 'mediumturquoise': CanvasKit.Color(72, 209, 204),
+ 'mediumvioletred': CanvasKit.Color(199, 21, 133),
+ 'midnightblue': CanvasKit.Color(25, 25, 112),
+ 'mintcream': CanvasKit.Color(245, 255, 250),
+ 'mistyrose': CanvasKit.Color(255, 228, 225),
+ 'moccasin': CanvasKit.Color(255, 228, 181),
+ 'navajowhite': CanvasKit.Color(255, 222, 173),
+ 'navy': CanvasKit.Color(0, 0, 128),
+ 'oldlace': CanvasKit.Color(253, 245, 230),
+ 'olive': CanvasKit.Color(128, 128, 0),
+ 'olivedrab': CanvasKit.Color(107, 142, 35),
+ 'orange': CanvasKit.Color(255, 165, 0),
+ 'orangered': CanvasKit.Color(255, 69, 0),
+ 'orchid': CanvasKit.Color(218, 112, 214),
+ 'palegoldenrod': CanvasKit.Color(238, 232, 170),
+ 'palegreen': CanvasKit.Color(152, 251, 152),
+ 'paleturquoise': CanvasKit.Color(175, 238, 238),
+ 'palevioletred': CanvasKit.Color(219, 112, 147),
+ 'papayawhip': CanvasKit.Color(255, 239, 213),
+ 'peachpuff': CanvasKit.Color(255, 218, 185),
+ 'peru': CanvasKit.Color(205, 133, 63),
+ 'pink': CanvasKit.Color(255, 192, 203),
+ 'plum': CanvasKit.Color(221, 160, 221),
+ 'powderblue': CanvasKit.Color(176, 224, 230),
+ 'purple': CanvasKit.Color(128, 0, 128),
+ 'rebeccapurple': CanvasKit.Color(102, 51, 153),
+ 'red': CanvasKit.Color(255, 0, 0),
+ 'rosybrown': CanvasKit.Color(188, 143, 143),
+ 'royalblue': CanvasKit.Color(65, 105, 225),
+ 'saddlebrown': CanvasKit.Color(139, 69, 19),
+ 'salmon': CanvasKit.Color(250, 128, 114),
+ 'sandybrown': CanvasKit.Color(244, 164, 96),
+ 'seagreen': CanvasKit.Color(46, 139, 87),
+ 'seashell': CanvasKit.Color(255, 245, 238),
+ 'sienna': CanvasKit.Color(160, 82, 45),
+ 'silver': CanvasKit.Color(192, 192, 192),
+ 'skyblue': CanvasKit.Color(135, 206, 235),
+ 'slateblue': CanvasKit.Color(106, 90, 205),
+ 'slategray': CanvasKit.Color(112, 128, 144),
+ 'slategrey': CanvasKit.Color(112, 128, 144),
+ 'snow': CanvasKit.Color(255, 250, 250),
+ 'springgreen': CanvasKit.Color(0, 255, 127),
+ 'steelblue': CanvasKit.Color(70, 130, 180),
+ 'tan': CanvasKit.Color(210, 180, 140),
+ 'teal': CanvasKit.Color(0, 128, 128),
+ 'thistle': CanvasKit.Color(216, 191, 216),
+ 'transparent': CanvasKit.Color(0, 0, 0, 0),
+ 'tomato': CanvasKit.Color(255, 99, 71),
+ 'turquoise': CanvasKit.Color(64, 224, 208),
+ 'violet': CanvasKit.Color(238, 130, 238),
+ 'wheat': CanvasKit.Color(245, 222, 179),
+ 'white': CanvasKit.Color(255, 255, 255),
+ 'whitesmoke': CanvasKit.Color(245, 245, 245),
+ 'yellow': CanvasKit.Color(255, 255, 0),
+ 'yellowgreen': CanvasKit.Color(154, 205, 50)
+ };
+ console.log(JSON.stringify(colorMap));
+
+});
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/canvas2d.js b/experimental/canvaskit/htmlcanvas/canvas2d.js
index 0d13fb0..9a91fde 100644
--- a/experimental/canvaskit/htmlcanvas/canvas2d.js
+++ b/experimental/canvaskit/htmlcanvas/canvas2d.js
@@ -6,6 +6,17 @@
var isNode = typeof btoa === undefined;
+ function argsAreFinite(args) {
+ for (var i = 0; i < args.length; i++) {
+ if (!Number.isFinite(args[0])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ CanvasKit._testing = {};
+
function HTMLCanvas(skSurface) {
this._surface = skSurface;
this._context = new CanvasRenderingContext2D(skSurface.getCanvas());
@@ -18,7 +29,9 @@
return null;
}
- this.toDataURL = function() {
+ this.toDataURL = function(codec) {
+ // TODO(kjlubick): maybe support other codecs?
+ // For now, just to png
this._surface.flush();
var img = this._surface.makeImageSnapshot();
@@ -41,47 +54,125 @@
}
return 'data:image/png;base64,' + b64encoded;
}
+
+ this.dispose = function() {
+ this._context._dispose();
+ this._surface.dispose();
+ }
}
function CanvasRenderingContext2D(skcanvas) {
this._canvas = skcanvas;
this._paint = new CanvasKit.SkPaint();
this._paint.setAntiAlias(true);
- this._currentPath = null;
+ this._paint.setStrokeWidth(2);
+ this._currentSubPath = null;
+ this._paths = [];
this._pathStarted = false;
+ this._dispose = function() {
+ this._paths.forEach(function(path) {
+ path.delete();
+ });
+ this._paint.delete();
+ // Don't delete this._canvas as it will be disposed
+ // by the surface of which it is based.
+ }
+
Object.defineProperty(this, 'font', {
enumerable: true,
set: function(newStyle) {
- //TODO
- this._paint.setTextSize(30);
+ var size = parseFontSize(newStyle);
+ // TODO styles
+ this._paint.setTextSize(size);
+ }
+ });
+
+ Object.defineProperty(this, 'lineWidth', {
+ enumerable: true,
+ set: function(newWidth) {
+ if (newWidth <= 0 || !newWidth) {
+ // Spec says to ignore NaN/Inf/0/negative values
+ return;
+ }
+ this._paint.setStrokeWidth(newWidth);
}
});
Object.defineProperty(this, 'strokeStyle', {
enumerable: true,
set: function(newStyle) {
- // TODO
+ this._paint.setColor(parseColor(newStyle));
}
});
- this.beginPath = function() {
- if (this._currentPath) {
- this._currentPath.delete();
+ this.arc = function(x, y, radius, startAngle, endAngle, ccw) {
+ // As per https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arc
+ // arc is essentially a simpler version of ellipse.
+ this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, ccw);
+ }
+
+ this.arcTo = function(x1, y1, x2, y2, radius) {
+ if (!argsAreFinite(arguments)) {
+ return;
}
- this._currentPath = new CanvasKit.SkPath();
+ this._ensureSubpath(x1, y1);
+ if (radius < 0) {
+ throw 'radii cannot be negative';
+ }
+ this._currentSubPath.arcTo(x1, y1, x2, y2, radius);
+ }
+
+ this.beginPath = function() {
+ this._currentSubPath = new CanvasKit.SkPath();
+ this._paths.push(this._currentSubPath);
this._pathStarted = false;
}
+ this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+ if (!argsAreFinite(arguments)) {
+ return;
+ }
+ this._ensureSubpath(cp1x, cp1y);
+ this._currentSubPath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+ }
+
+ this.closePath = function() {
+ if (this._currentSubPath) {
+ this._currentSubPath.close();
+ this._currentSubPath = null;
+ this._pathStarted = false;
+ }
+ }
+
+ this.ellipse = function(x, y, radiusX, radiusY, rotation,
+ startAngle, endAngle, ccw) {
+ if (!argsAreFinite(arguments)) {
+ return;
+ }
+ if (radiusX < 0 || radiusY < 0) {
+ throw 'radii cannot be negative';
+ }
+ this._pathStarted = true;
+
+ var bounds = CanvasKit.LTRBRect(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
+ var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
+ var temp = new CanvasKit.SkPath();
+ temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
+ var m = CanvasKit.SkMatrix.rotated(radiansToDegrees(rotation), x, y);
+ this._currentSubPath.addPath(temp, m, true);
+ temp.delete();
+ }
+
// ensureSubpath makes SkPath behave like the browser's path object
- // in that the first lineTo/cubicTo, etc, really acts like a moveTo.
+ // in that the first lineTo really acts like a moveTo.
// ensureSubpath is the term used in the canvas spec:
// https://html.spec.whatwg.org/multipage/canvas.html#ensure-there-is-a-subpath
// ensureSubpath returns true if the drawing command can proceed,
- // false otherwise (i.e. it was the first command and replaced
+ // false otherwise (i.e. it was the first command and may be replaced
// with a moveTo).
this._ensureSubpath = function(x, y) {
- if (!this._currentPath) {
+ if (!this._currentSubPath) {
this.beginPath();
}
if (!this._pathStarted) {
@@ -98,8 +189,13 @@
}
this.lineTo = function(x, y) {
+ if (!argsAreFinite(arguments)) {
+ return;
+ }
+ // lineTo is the odd-ball in the sense that a line-to without
+ // a previous subpath is the same as a moveTo.
if (this._ensureSubpath(x, y)) {
- this._currentPath.lineTo(x, y);
+ this._currentSubPath.lineTo(x, y);
}
}
@@ -111,9 +207,31 @@
}
this.moveTo = function(x, y) {
- if (this._ensureSubpath(x, y)) {
- this._currentPath.moveTo(x, y);
+ if (!argsAreFinite(arguments)) {
+ return;
}
+ if (this._ensureSubpath(x, y)) {
+ this._currentSubPath.moveTo(x, y);
+ }
+ }
+
+ this.quadraticCurveTo = function(cpx, cpy, x, y) {
+ if (!argsAreFinite(arguments)) {
+ return;
+ }
+ this._ensureSubpath(cpx, cpy);
+ this._currentSubPath.quadTo(cpx, cpy, x, y);
+ }
+
+ this.rect = function(x, y, width, height) {
+ if (!argsAreFinite(arguments)) {
+ return;
+ }
+ // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
+ this.beginPath();
+ this._currentSubPath.addRect(x, y, x+width, y+height);
+ this.beginPath();
+ this._currentSubPath.moveTo(x, y);
}
this.resetTransform = function() {
@@ -129,6 +247,10 @@
this._canvas.scale(sx, sy);
}
+ this.scale = function(sx, sy) {
+ this._canvas.scale(sx, sy);
+ }
+
this.setTransform = function(a, b, c, d, e, f) {
this._canvas.setMatrix([a, c, e,
b, d, f,
@@ -140,9 +262,12 @@
}
this.stroke = function() {
- if (this._currentPath) {
+ if (this._currentSubPath) {
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
- this._canvas.drawPath(this._currentPath, this._paint);
+ this._paint.setLine
+ for (var i = 0; i < this._paths.length; i++) {
+ this._canvas.drawPath(this._paths[i], this._paint);
+ }
}
}
@@ -155,10 +280,23 @@
this.translate = function(dx, dy) {
this._canvas.translate(dx, dy);
}
+
+
+ // Not supported operations (e.g. for Web only)
+ this.addHitRegion = function() {};
+ this.clearHitRegions = function() {};
+ this.drawFocusIfNeeded = function() {};
+ this.removeHitRegion = function() {};
+ this.scrollPathIntoView = function() {};
+
+ Object.defineProperty(this, 'canvas', {
+ value: null,
+ writable: false
+ });
}
CanvasKit.MakeCanvas = function(width, height) {
- // TODO do fonts the "correct" way
+ // TODO(kjlubick) do fonts the "correct" way
CanvasKit.initFonts();
var surf = CanvasKit.MakeSurface(width, height);
if (surf) {
@@ -167,4 +305,114 @@
return null;
}
+ var units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q';
+ var fontSizeRegex = new RegExp('([\\d\\.]+)(' + units + ')');
+ var defaultHeight = 12;
+ // Based off of node-canvas's parseFont
+ // returns font size in *points* (original impl was in px);
+ function parseFontSize(fontStr) {
+ // This is naive and doesn't account for line-height yet
+ // (but neither does node-canvas's?)
+ var fontSize = fontSizeRegex.exec(fontStr);
+ if (!fontSize) {
+ console.error('Could not parse font size', fontStr);
+ return 16;
+ }
+ var size = fontSize[1];
+ var unit = fontSize[2];
+ switch (unit) {
+ case 'pt':
+ return size;
+ case 'px':
+ return size * 3/4;
+ case 'pc':
+ return size * 12;
+ case 'in':
+ return size * 72;
+ case 'cm':
+ return size * 72.0 / 2.54;
+ case 'mm':
+ return size * (72.0 / 25.4);
+ case '%':
+ return size * (defaultHeight / 100);
+ case 'em':
+ case 'rem':
+ return size * defaultHeight;
+ case 'q':
+ return size * (96 / 25.4 / 3);
+ }
+ }
+
+ function valueOrPercent(aStr) {
+ var a = parseFloat(aStr) || 1;
+ if (aStr && aStr.indexOf('%') !== -1) {
+ return a / 100;
+ }
+ return a;
+ }
+
+ function parseColor(colorStr) {
+ colorStr = colorStr.toLowerCase();
+ // See https://drafts.csswg.org/css-color/#typedef-hex-color
+ if (colorStr.startsWith('#')) {
+ var r, g, b, a = 255;
+ switch (colorStr.length) {
+ case 9: // 8 hex chars #RRGGBBAA
+ a = parseInt(colorStr.slice(7, 9), 16);
+ case 7: // 6 hex chars #RRGGBB
+ r = parseInt(colorStr.slice(1, 3), 16);
+ g = parseInt(colorStr.slice(3, 5), 16);
+ b = parseInt(colorStr.slice(5, 7), 16);
+ break;
+ case 5: // 4 hex chars #RGBA
+ // multiplying by 17 is the same effect as
+ // appending another character of the same value
+ // e.g. e => ee == 14 => 238
+ a = parseInt(colorStr.slice(4, 5), 16) * 17;
+ case 4: // 6 hex chars #RGB
+ r = parseInt(colorStr.slice(1, 2), 16) * 17;
+ g = parseInt(colorStr.slice(2, 3), 16) * 17;
+ b = parseInt(colorStr.slice(3, 4), 16) * 17;
+ break;
+ }
+ return CanvasKit.Color(r, g, b, a/255);
+
+ } else if (colorStr.startsWith('rgba')) {
+ // Trim off rgba( and the closing )
+ colorStr = colorStr.slice(5, -1);
+ var nums = colorStr.split(',');
+ return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+ valueOrPercent(nums[3]));
+ } else if (colorStr.startsWith('rgb')) {
+ // Trim off rgba( and the closing )
+ colorStr = colorStr.slice(4, -1);
+ var nums = colorStr.split(',');
+ // rgb can take 3 or 4 arguments
+ return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+ valueOrPercent(nums[3]));
+ } else if (colorStr.startsWith('gray(')) {
+ // TODO
+ } else if (colorStr.startsWith('hsl')) {
+ // TODO
+ } else {
+ // Try for named color
+ var nc = colorMap[colorStr];
+ if (nc !== undefined) {
+ return nc;
+ }
+ }
+ console.error('unrecognized color ', colorStr);
+ return CanvasKit.BLACK;
+ }
+
+ CanvasKit._testing['parseColor'] = parseColor;
+
+ // Create the following with
+ // node ./htmlcanvas/_namedcolors.js --expose-wasm
+ // JS/closure doesn't have a constexpr like thing which
+ // would really help here. Since we don't, we pre-compute
+ // the map, which saves (a tiny amount of) startup time
+ // and (a small amount of) code size.
+ var colorMap = {"aliceblue":-984833,"antiquewhite":-332841,"aqua":-16711681,"aquamarine":-8388652,"azure":-983041,"beige":-657956,"bisque":-6972,"black":-16777216,"blanchedalmond":-5171,"blue":-16776961,"blueviolet":-7722014,"brown":-5952982,"burlywood":-2180985,"cadetblue":-10510688,"chartreuse":-8388864,"chocolate":-2987746,"coral":-32944,"cornflowerblue":-10185235,"cornsilk":-1828,"crimson":-2354116,"cyan":-16711681,"darkblue":-16777077,"darkcyan":-16741493,"darkgoldenrod":-4684277,"darkgray":-5658199,"darkgreen":-16751616,"darkgrey":-5658199,"darkkhaki":-4343957,"darkmagenta":-7667573,"darkolivegreen":-11179217,"darkorange":-29696,"darkorchid":-6737204,"darkred":-7667712,"darksalmon":-1468806,"darkseagreen":-7357297,"darkslateblue":-12042869,"darkslategray":-13676721,"darkslategrey":-13676721,"darkturquoise":-16724271,"darkviolet":-7077677,"deeppink":-60269,"deepskyblue":-16728065,"dimgray":-9868951,"dimgrey":-9868951,"dodgerblue":-14774017,"firebrick":-5103070,"floralwhite":-1296,"forestgreen":-14513374,"fuchsia":-65281,"gainsboro":-2302756,"ghostwhite":-460545,"gold":-10496,"goldenrod":-2448096,"gray":-8355712,"green":-16744448,"greenyellow":-5374161,"grey":-8355712,"honeydew":-983056,"hotpink":-38476,"indianred":-3318692,"indigo":-11861886,"ivory":-16,"khaki":-989556,"lavender":-1644806,"lavenderblush":-3851,"lawngreen":-8586240,"lemonchiffon":-1331,"lightblue":-5383962,"lightcoral":-1015680,"lightcyan":-2031617,"lightgoldenrodyellow":-329006,"lightgray":-2894893,"lightgreen":-7278960,"lightgrey":-2894893,"lightpink":-18751,"lightsalmon":-24454,"lightseagreen":-14634326,"lightskyblue":-7876870,"lightslategray":-8943463,"lightslategrey":-8943463,"lightsteelblue":-5192482,"lightyellow":-32,"lime":-16711936,"limegreen":-13447886,"linen":-331546,"magenta":-65281,"maroon":-8388608,"mediumaquamarine":-10039894,"mediumblue":-16777011,"mediumorchid":-4565549,"mediumpurple":-7114533,"mediumseagreen":-12799119,"mediumslateblue":-8689426,"mediumspringgreen":-16713062,"mediumturquoise":-12004916,"mediumvioletred":-3730043,"midnightblue":-15132304,"mintcream":-655366,"mistyrose":-6943,"moccasin":-6987,"navajowhite":-8531,"navy":-16777088,"oldlace":-133658,"olive":-8355840,"olivedrab":-9728477,"orange":-23296,"orangered":-47872,"orchid":-2461482,"palegoldenrod":-1120086,"palegreen":-6751336,"paleturquoise":-5247250,"palevioletred":-2396013,"papayawhip":-4139,"peachpuff":-9543,"peru":-3308225,"pink":-16181,"plum":-2252579,"powderblue":-5185306,"purple":-8388480,"rebeccapurple":-10079335,"red":-65536,"rosybrown":-4419697,"royalblue":-12490271,"saddlebrown":-7650029,"salmon":-360334,"sandybrown":-744352,"seagreen":-13726889,"seashell":-2578,"sienna":-6270419,"silver":-4144960,"skyblue":-7876885,"slateblue":-9807155,"slategray":-9404272,"slategrey":-9404272,"snow":-1286,"springgreen":-16711809,"steelblue":-12156236,"tan":-2968436,"teal":-16744320,"thistle":-2572328,"transparent":0,"tomato":-40121,"turquoise":-12525360,"violet":-1146130,"wheat":-663885,"white":-1,"whitesmoke":-657931,"yellow":-256,"yellowgreen":-6632142};
+
}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index b671369..9106ae7 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -7,41 +7,114 @@
// after onRuntimeInitialized, otherwise, it can happen outside of that scope.
CanvasKit.onRuntimeInitialized = function() {
// All calls to 'this' need to go in externs.js so closure doesn't minify them away.
+
+
+ // Add some helpers for matrices. This is ported from SkMatrix.cpp
+ // to save complexity and overhead of going back and forth between
+ // C++ and JS layers.
+ CanvasKit.SkMatrix = {};
+ function sdot(a, b, c, d, e, f) {
+ e = e || 0;
+ f = f || 0;
+ return a * b + c * d + e * f;
+ }
+
+ // Return a matrix representing a rotation by n degrees.
+ // px, py optionally say which point the rotation should be around
+ // with the default being (0, 0);
+ CanvasKit.SkMatrix.rotated = function(degrees, px, py) {
+ px = px || 0;
+ py = py || 0;
+ var rad = degreesToRadians(degrees);
+ var sinV = Math.sin(rad);
+ var cosV = Math.cos(rad);
+ return [
+ cosV, -sinV, sdot( sinV, py, 1 - cosV, px),
+ sinV, cosV, sdot(-sinV, px, 1 - cosV, py),
+ 0, 0, 1,
+ ];
+ };
+ // TODO(kjlubick): translated, scaled
+
+
+ CanvasKit.SkPath.prototype.addArc = function(oval, startAngle, sweepAngle) {
+ // see arc() for the HTMLCanvas version
+ // note input angles are degrees.
+ this._addArc(oval, startAngle, sweepAngle);
+ return this;
+ };
+
CanvasKit.SkPath.prototype.addPath = function() {
- // Takes 1, 2, or 10 args, where the first arg is always the path.
+ // Takes 1, 2, 7, or 10 required args, where the first arg is always the path.
+ // The last arg is optional and chooses between add or extend mode.
// The options for the remaining args are:
- // - an array of 9 parameters
- // - the 9 parameters of a full matrix
- // an array of 6 parameters (omitting perspective)
+ // - an array of 6 or 9 parameters (perspective is optional)
+ // - the 9 parameters of a full matrix or
// the 6 non-perspective params of a matrix.
- if (arguments.length === 1) {
- // Add path, unchanged. Use identify matrix
- this._addPath(arguments[0], 1, 0, 0,
- 0, 1, 0,
- 0, 0, 1);
- } else if (arguments.length === 2) {
+ var args = Array.prototype.slice.call(arguments);
+ var path = args[0];
+ var extend = false;
+ if (typeof args[args.length-1] === "boolean") {
+ extend = args.pop();
+ }
+ if (args.length === 1) {
+ // Add path, unchanged. Use identity matrix
+ this._addPath(path, 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1,
+ extend);
+ } else if (args.length === 2) {
// User provided the 9 params of a full matrix as an array.
- var sm = arguments[1];
- this._addPath(arguments[0], a[1], a[2], a[3],
- a[4], a[5], a[6],
- a[7] || 0, a[8] || 0, a[9] || 1);
- } else if (arguments.length === 7 || arguments.length === 10) {
+ var a = args[1];
+ this._addPath(path, a[0], a[1], a[2],
+ a[3], a[4], a[5],
+ a[6] || 0, a[7] || 0, a[8] || 1,
+ extend);
+ } else if (args.length === 7 || args.length === 10) {
// User provided the 9 params of a (full) matrix directly.
// (or just the 6 non perspective ones)
// These are in the same order as what Skia expects.
- var a = arguments;
- this._addPath(arguments[0], a[1], a[2], a[3],
- a[4], a[5], a[6],
- a[7] || 0, a[8] || 0, a[9] || 1);
+ var a = args;
+ this._addPath(path, a[1], a[2], a[3],
+ a[4], a[5], a[6],
+ a[7] || 0, a[8] || 0, a[9] || 1,
+ extend);
} else {
- console.err('addPath expected to take 1, 2, 7, or 10 args. Got ' + arguments.length);
+ console.error('addPath expected to take 1, 2, 7, or 10 required args. Got ' + args.length);
+ return null;
+ }
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.addRect = function() {
+ // Takes 1, 2, 4 or 5 args
+ // - SkRect
+ // - SkRect, isCCW
+ // - left, top, right, bottom
+ // - left, top, right, bottom, isCCW
+ if (arguments.length === 1 || arguments.length === 2) {
+ var r = arguments[0];
+ var ccw = arguments[1] || false;
+ this._addRect(r.fLeft, r.fTop, r.fRight, r.fBottom, ccw);
+ } else if (arguments.length === 4 || arguments.length === 5) {
+ var a = arguments;
+ this._addRect(a[0], a[1], a[2], a[3], a[4] || false);
+ } else {
+ console.error('addRect expected to take 1, 2, 4, or 5 args. Got ' + arguments.length);
return null;
}
return this;
};
CanvasKit.SkPath.prototype.arc = function(x, y, radius, startAngle, endAngle, ccw) {
- this._arc(x, y, radius, startAngle, endAngle, !!ccw);
+ // emulates the HTMLCanvas behavior. See addArc() for the SkPath version.
+ // Note input angles are radians.
+ var bounds = CanvasKit.LTRBRect(x-radius, y-radius, x+radius, y+radius);
+ var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
+ var temp = new CanvasKit.SkPath();
+ temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
+ this.addPath(temp, true);
+ temp.delete();
return this;
};
@@ -179,7 +252,6 @@
}
} // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic.
- // Likely only used for tests.
CanvasKit.LTRBRect = function(l, t, r, b) {
return {
fLeft: l,
@@ -356,3 +428,14 @@
}
}(Module)); // When this file is loaded in, the high level object is "Module";
+
+// Intentionally added outside the scope to allow usage in canvas2d.js and other
+// pre-js files. These names are unlikely to cause emscripten collisions.
+function radiansToDegrees(rad) {
+ return (rad / Math.PI) * 180;
+}
+
+function degreesToRadians(deg) {
+ return (deg / 180) * Math.PI;
+}
+
diff --git a/experimental/canvaskit/tests/canvas2d.spec.js b/experimental/canvaskit/tests/canvas2d.spec.js
new file mode 100644
index 0000000..c763362
--- /dev/null
+++ b/experimental/canvaskit/tests/canvas2d.spec.js
@@ -0,0 +1,154 @@
+// The increased timeout is especially needed with larger binaries
+// like in the debug/gpu build
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
+
+describe('CanvasKit\'s Canvas 2d Behavior', function() {
+ // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
+ var CanvasKit = null;
+ const LoadCanvasKit = new Promise(function(resolve, reject) {
+ if (CanvasKit) {
+ resolve();
+ } else {
+ CanvasKitInit({
+ locateFile: (file) => '/canvaskit/'+file,
+ }).then((_CanvasKit) => {
+ CanvasKit = _CanvasKit;
+ CanvasKit.initFonts();
+ resolve();
+ });
+ }
+ });
+
+ let container = document.createElement('div');
+ document.body.appendChild(container);
+ const CANVAS_WIDTH = 600;
+ const CANVAS_HEIGHT = 600;
+
+ beforeEach(function() {
+ container.innerHTML = `
+ <canvas width=600 height=600 id=test></canvas>`;
+ });
+
+ afterEach(function() {
+ container.innerHTML = '';
+ });
+
+ describe('color string parsing', function() {
+ function hex(s) {
+ return parseInt(s, 16);
+ }
+
+ it('parses hex color strings', function(done) {
+ LoadCanvasKit.then(catchException(done, () => {
+ let parseColor = CanvasKit._testing.parseColor;
+ expect(parseColor('#FED')).toEqual(
+ CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
+ expect(parseColor('#FEDC')).toEqual(
+ CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
+ expect(parseColor('#fed')).toEqual(
+ CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
+ expect(parseColor('#fedc')).toEqual(
+ CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
+ done();
+ }));
+ });
+ it('parses rgba color strings', function(done) {
+ LoadCanvasKit.then(catchException(done, () => {
+ let parseColor = CanvasKit._testing.parseColor;
+ expect(parseColor('rgba(117, 33, 64, 0.75)')).toEqual(
+ CanvasKit.Color(117, 33, 64, 0.75));
+ expect(parseColor('rgb(117, 33, 64, 0.75)')).toEqual(
+ CanvasKit.Color(117, 33, 64, 0.75));
+ expect(parseColor('rgba(117,33,64)')).toEqual(
+ CanvasKit.Color(117, 33, 64, 1.0));
+ expect(parseColor('rgb(117,33, 64)')).toEqual(
+ CanvasKit.Color(117, 33, 64, 1.0));
+ done();
+ }));
+ });
+ it('parses named color strings', function(done) {
+ LoadCanvasKit.then(catchException(done, () => {
+ let parseColor = CanvasKit._testing.parseColor;
+ expect(parseColor('grey')).toEqual(
+ CanvasKit.Color(128, 128, 128, 1.0));
+ expect(parseColor('blanchedalmond')).toEqual(
+ CanvasKit.Color(255, 235, 205, 1.0));
+ expect(parseColor('transparent')).toEqual(
+ CanvasKit.Color(0, 0, 0, 0));
+ done();
+ }));
+ });
+ }); // end describe('color string parsing')
+
+ function multipleCanvasTest(testname, done, test) {
+ if (CanvasKit.gpu) {
+ // TODO(kjlubick): add Software backend to GPU build skia:8548
+ console.log(`SKIPPING ${testname} on GPU`);
+ done();
+ return;
+ }
+ const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
+ skcanvas._config = 'software_canvas';
+ const realCanvas = document.getElementById('test');
+ realCanvas._config = 'html_canvas';
+ realCanvas.width = CANVAS_WIDTH;
+ realCanvas.height = CANVAS_HEIGHT;
+
+ let promises = [];
+
+ for (let canvas of [skcanvas, realCanvas]) {
+ test(canvas);
+ // canvas has .toDataURL (even though skcanvas is not a real Canvas)
+ // so this will work.
+ promises.push(reportCanvas(canvas, 'all_path_operations', canvas._config));
+ }
+ Promise.all(promises).then(() => {
+ skcanvas.dispose();
+ done();
+ }).catch(reportError(done));
+ }
+
+ describe('Path drawing API', function() {
+ it('supports all the line types', function(done) {
+ LoadCanvasKit.then(catchException(done, () => {
+ multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
+ let ctx = canvas.getContext('2d');
+ // TODO(kjlubick): scale doesn't work quite right yet, but putting
+ // it before all draws makes it consistent for now.
+ ctx.scale(3.0, 3.0);
+ ctx.moveTo(20, 5);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(40, 10);
+ ctx.lineTo(50, 20);
+ ctx.lineTo(60, 0);
+ ctx.lineTo(20, 5);
+
+ ctx.moveTo(20, 80);
+ ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+ ctx.moveTo(36, 148);
+ ctx.quadraticCurveTo(66, 188, 120, 136);
+ ctx.lineTo(36, 148);
+
+ ctx.rect(5, 170, 20, 25);
+
+ ctx.moveTo(150, 180);
+ ctx.arcTo(150, 100, 50, 200, 20);
+ ctx.lineTo(160, 160);
+
+ ctx.moveTo(20, 120);
+ ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
+ ctx.lineTo(20, 120);
+
+ ctx.moveTo(150, 5);
+ ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
+
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ });
+ }));
+ });
+ }); // end describe('Path drawing API')
+
+
+});