[canvaskit] Expose parseColorString

This is handy for interacting with <input type=color>

Change-Id: I7946c08ef10a2481016885d58cc52f76f5cd40e7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/272344
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index 2582285..bbb08e1 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -15,6 +15,7 @@
 ### Added
  - `SkSurface.drawOnce` for drawing a single frame (in addition to already existing
    `SkSurface.requestAnimationFrame` for animation logic).
+ - `CanvasKit.parseColorString` which processes color strings like "#2288FF".
 
 ### Changed
  - We now compile/ship with Emscripten v1.39.6.
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
index ca01cbf..d8bc9ac 100644
--- a/modules/canvaskit/externs.js
+++ b/modules/canvaskit/externs.js
@@ -23,7 +23,7 @@
  */
 
 var CanvasKit = {
-	// public API (i.e. things we declare in the pre-js file)
+	// public API (i.e. things we declare in the pre-js file or in the cpp bindings)
 	Color: function() {},
 	/** @return {CanvasKit.SkRect} */
 	LTRBRect: function() {},
@@ -73,6 +73,7 @@
 	getDecodeCacheUsageBytes: function() {},
 	getSkDataBytes: function() {},
 	multiplyByAlpha: function() {},
+	parseColorString: function() {},
 	setCurrentContext: function() {},
 	setDecodeCacheLimitBytes: function() {},
 
diff --git a/modules/canvaskit/helper.js b/modules/canvaskit/helper.js
index 3171057..28752615 100644
--- a/modules/canvaskit/helper.js
+++ b/modules/canvaskit/helper.js
@@ -29,6 +29,76 @@
   ]
 }
 
+// parseColorString takes in a CSS color value and returns a CanvasKit.Color
+// (which is just a 32 bit integer, 8 bits per channel). An optional colorMap
+// may be provided which maps custom strings to values (e.g. {'springgreen':4278255487}).
+// In the CanvasKit canvas2d shim layer, we provide this map for processing
+// canvas2d calls, but not here for code size reasons.
+CanvasKit.parseColorString = function(colorStr, colorMap) {
+  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 if (colorMap) {
+    // Try for named color
+    var nc = colorMap[colorStr];
+    if (nc !== undefined) {
+      return nc;
+    }
+  }
+  SkDebug('unrecognized color ' + colorStr);
+  return CanvasKit.BLACK;
+}
+
+function valueOrPercent(aStr) {
+  if (aStr === undefined) {
+    return 1; // default to opaque.
+  }
+  var a = parseFloat(aStr);
+  if (aStr && aStr.indexOf('%') !== -1) {
+    return a / 100;
+  }
+  return a;
+}
+
 CanvasKit.multiplyByAlpha = function(color, alpha) {
   if (alpha === 1) {
     return color;
diff --git a/modules/canvaskit/htmlcanvas/color.js b/modules/canvaskit/htmlcanvas/color.js
index 7fe17a1..606d92e 100644
--- a/modules/canvaskit/htmlcanvas/color.js
+++ b/modules/canvaskit/htmlcanvas/color.js
@@ -30,69 +30,8 @@
   }
 }
 
-function valueOrPercent(aStr) {
-  if (aStr === undefined) {
-    return 1; // default to opaque.
-  }
-  var a = parseFloat(aStr);
-  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;
-    }
-  }
-  SkDebug('unrecognized color ' + colorStr);
-  return CanvasKit.BLACK;
+  return CanvasKit.parseColorString(colorStr, colorMap);
 }
 
 CanvasKit._testing['parseColor'] = parseColor;
diff --git a/modules/canvaskit/tests/canvas2d.spec.js b/modules/canvaskit/tests/canvas2d.spec.js
index 1569d64..97b9202 100644
--- a/modules/canvaskit/tests/canvas2d.spec.js
+++ b/modules/canvaskit/tests/canvas2d.spec.js
@@ -21,7 +21,7 @@
 
         it('parses hex color strings', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
-                const parseColor = CanvasKit._testing.parseColor;
+                const parseColor = CanvasKit.parseColorString;
                 expect(parseColor('#FED')).toEqual(
                     CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
                 expect(parseColor('#FEDC')).toEqual(
@@ -35,7 +35,7 @@
         });
         it('parses rgba color strings', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
-                const parseColor = CanvasKit._testing.parseColor;
+                const parseColor = CanvasKit.parseColorString;
                 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(
@@ -55,6 +55,8 @@
         });
         it('parses named color strings', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
+                // Keep this one as the _testing version, because we don't include the large
+                // color map by default.
                 const parseColor = CanvasKit._testing.parseColor;
                 expect(parseColor('grey')).toEqual(
                     CanvasKit.Color(128, 128, 128, 1.0));