Extend the geometry APIs.

Add a RRect structure for rounded rect.

Define helpers for manipulating transformation matrices.

Define output formatters for geometry datatypes.

BUG=
R=viettrungluu@chromium.org, abarth, abarth@chromium.org, jamesr, viettrungluu

Review URL: https://codereview.chromium.org/1517853002 .

Review URL: https://codereview.chromium.org/1537633005 .
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart
index 90422dd..cc4fbc7 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart
@@ -9,8 +9,6 @@
 import 'package:mojo/bindings.dart' as bindings;
 import 'package:mojo/core.dart' as core;
 
-
-
 class Point extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -50,15 +48,13 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      
       result.x = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.y = decoder0.decodeInt32(12);
     }
     return result;
@@ -66,16 +62,18 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
+
     encoder0.encodeInt32(x, 8);
-    
+
     encoder0.encodeInt32(y, 12);
   }
 
   String toString() {
     return "Point("
-           "x: $x" ", "
-           "y: $y" ")";
+        "x: $x"
+        ", "
+        "y: $y"
+        ")";
   }
 
   Map toJson() {
@@ -86,7 +84,6 @@
   }
 }
 
-
 class PointF extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -126,15 +123,13 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      
       result.x = decoder0.decodeFloat(8);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.y = decoder0.decodeFloat(12);
     }
     return result;
@@ -142,16 +137,18 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
+
     encoder0.encodeFloat(x, 8);
-    
+
     encoder0.encodeFloat(y, 12);
   }
 
   String toString() {
     return "PointF("
-           "x: $x" ", "
-           "y: $y" ")";
+        "x: $x"
+        ", "
+        "y: $y"
+        ")";
   }
 
   Map toJson() {
@@ -162,7 +159,6 @@
   }
 }
 
-
 class Size extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -202,15 +198,13 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      
       result.width = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.height = decoder0.decodeInt32(12);
     }
     return result;
@@ -218,16 +212,18 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
+
     encoder0.encodeInt32(width, 8);
-    
+
     encoder0.encodeInt32(height, 12);
   }
 
   String toString() {
     return "Size("
-           "width: $width" ", "
-           "height: $height" ")";
+        "width: $width"
+        ", "
+        "height: $height"
+        ")";
   }
 
   Map toJson() {
@@ -238,7 +234,6 @@
   }
 }
 
-
 class Rect extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(24, 0)
@@ -280,23 +275,19 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      
       result.x = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.y = decoder0.decodeInt32(12);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.width = decoder0.decodeInt32(16);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.height = decoder0.decodeInt32(20);
     }
     return result;
@@ -304,22 +295,26 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
+
     encoder0.encodeInt32(x, 8);
-    
+
     encoder0.encodeInt32(y, 12);
-    
+
     encoder0.encodeInt32(width, 16);
-    
+
     encoder0.encodeInt32(height, 20);
   }
 
   String toString() {
     return "Rect("
-           "x: $x" ", "
-           "y: $y" ", "
-           "width: $width" ", "
-           "height: $height" ")";
+        "x: $x"
+        ", "
+        "y: $y"
+        ", "
+        "width: $width"
+        ", "
+        "height: $height"
+        ")";
   }
 
   Map toJson() {
@@ -332,7 +327,6 @@
   }
 }
 
-
 class RectF extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(24, 0)
@@ -374,23 +368,19 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      
       result.x = decoder0.decodeFloat(8);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.y = decoder0.decodeFloat(12);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.width = decoder0.decodeFloat(16);
     }
     if (mainDataHeader.version >= 0) {
-      
       result.height = decoder0.decodeFloat(20);
     }
     return result;
@@ -398,22 +388,26 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
+
     encoder0.encodeFloat(x, 8);
-    
+
     encoder0.encodeFloat(y, 12);
-    
+
     encoder0.encodeFloat(width, 16);
-    
+
     encoder0.encodeFloat(height, 20);
   }
 
   String toString() {
     return "RectF("
-           "x: $x" ", "
-           "y: $y" ", "
-           "width: $width" ", "
-           "height: $height" ")";
+        "x: $x"
+        ", "
+        "y: $y"
+        ", "
+        "width: $width"
+        ", "
+        "height: $height"
+        ")";
   }
 
   Map toJson() {
@@ -426,6 +420,170 @@
   }
 }
 
+class RRect extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(56, 0)
+  ];
+  int x = 0;
+  int y = 0;
+  int width = 0;
+  int height = 0;
+  int topLeftRadiusX = 0;
+  int topLeftRadiusY = 0;
+  int topRightRadiusX = 0;
+  int topRightRadiusY = 0;
+  int bottomLeftRadiusX = 0;
+  int bottomLeftRadiusY = 0;
+  int bottomRightRadiusX = 0;
+  int bottomRightRadiusY = 0;
+
+  RRect() : super(kVersions.last.size);
+
+  static RRect deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static RRect decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    RRect result = new RRect();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      result.x = decoder0.decodeInt32(8);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.y = decoder0.decodeInt32(12);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.width = decoder0.decodeInt32(16);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.height = decoder0.decodeInt32(20);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.topLeftRadiusX = decoder0.decodeInt32(24);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.topLeftRadiusY = decoder0.decodeInt32(28);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.topRightRadiusX = decoder0.decodeInt32(32);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.topRightRadiusY = decoder0.decodeInt32(36);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.bottomLeftRadiusX = decoder0.decodeInt32(40);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.bottomLeftRadiusY = decoder0.decodeInt32(44);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.bottomRightRadiusX = decoder0.decodeInt32(48);
+    }
+    if (mainDataHeader.version >= 0) {
+      result.bottomRightRadiusY = decoder0.decodeInt32(52);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+
+    encoder0.encodeInt32(x, 8);
+
+    encoder0.encodeInt32(y, 12);
+
+    encoder0.encodeInt32(width, 16);
+
+    encoder0.encodeInt32(height, 20);
+
+    encoder0.encodeInt32(topLeftRadiusX, 24);
+
+    encoder0.encodeInt32(topLeftRadiusY, 28);
+
+    encoder0.encodeInt32(topRightRadiusX, 32);
+
+    encoder0.encodeInt32(topRightRadiusY, 36);
+
+    encoder0.encodeInt32(bottomLeftRadiusX, 40);
+
+    encoder0.encodeInt32(bottomLeftRadiusY, 44);
+
+    encoder0.encodeInt32(bottomRightRadiusX, 48);
+
+    encoder0.encodeInt32(bottomRightRadiusY, 52);
+  }
+
+  String toString() {
+    return "RRect("
+        "x: $x"
+        ", "
+        "y: $y"
+        ", "
+        "width: $width"
+        ", "
+        "height: $height"
+        ", "
+        "topLeftRadiusX: $topLeftRadiusX"
+        ", "
+        "topLeftRadiusY: $topLeftRadiusY"
+        ", "
+        "topRightRadiusX: $topRightRadiusX"
+        ", "
+        "topRightRadiusY: $topRightRadiusY"
+        ", "
+        "bottomLeftRadiusX: $bottomLeftRadiusX"
+        ", "
+        "bottomLeftRadiusY: $bottomLeftRadiusY"
+        ", "
+        "bottomRightRadiusX: $bottomRightRadiusX"
+        ", "
+        "bottomRightRadiusY: $bottomRightRadiusY"
+        ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["x"] = x;
+    map["y"] = y;
+    map["width"] = width;
+    map["height"] = height;
+    map["topLeftRadiusX"] = topLeftRadiusX;
+    map["topLeftRadiusY"] = topLeftRadiusY;
+    map["topRightRadiusX"] = topRightRadiusX;
+    map["topRightRadiusY"] = topRightRadiusY;
+    map["bottomLeftRadiusX"] = bottomLeftRadiusX;
+    map["bottomLeftRadiusY"] = bottomLeftRadiusY;
+    map["bottomRightRadiusX"] = bottomRightRadiusX;
+    map["bottomRightRadiusY"] = bottomRightRadiusY;
+    return map;
+  }
+}
 
 class Transform extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
@@ -465,25 +623,26 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
+          'Message newer than the last known version cannot be shorter than '
+          'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      
-      result.matrix = decoder0.decodeFloatArray(8, bindings.kNothingNullable, 16);
+      result.matrix =
+          decoder0.decodeFloatArray(8, bindings.kNothingNullable, 16);
     }
     return result;
   }
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
+
     encoder0.encodeFloatArray(matrix, 8, bindings.kNothingNullable, 16);
   }
 
   String toString() {
     return "Transform("
-           "matrix: $matrix" ")";
+        "matrix: $matrix"
+        ")";
   }
 
   Map toJson() {
@@ -492,5 +651,3 @@
     return map;
   }
 }
-
-
diff --git a/mojo/services/geometry/cpp/BUILD.gn b/mojo/services/geometry/cpp/BUILD.gn
index 2a42aa0..9225ce4 100644
--- a/mojo/services/geometry/cpp/BUILD.gn
+++ b/mojo/services/geometry/cpp/BUILD.gn
@@ -9,6 +9,9 @@
   restrict_external_deps = false
   public_configs = [ "../../public/build/config:mojo_services" ]
   sources = [
+    "formatting.cc",
+    "formatting.h",
+    "geometry_util.cc",
     "geometry_util.h",
   ]
 
diff --git a/mojo/services/geometry/cpp/formatting.cc b/mojo/services/geometry/cpp/formatting.cc
new file mode 100644
index 0000000..76162d7
--- /dev/null
+++ b/mojo/services/geometry/cpp/formatting.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/geometry/cpp/formatting.h"
+
+#include <ostream>
+
+namespace mojo {
+
+std::ostream& operator<<(std::ostream& os, const mojo::Point& value) {
+  return os << "{x=" << value.x << ", y=" << value.y << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const mojo::Size& value) {
+  return os << "{width=" << value.width << ", height=" << value.height << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const mojo::Rect& value) {
+  return os << "{x=" << value.x << ", y=" << value.y
+            << ", width=" << value.width << ", height=" << value.height << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const mojo::RRect& value) {
+  return os << "{x=" << value.x << ", y=" << value.y
+            << ", width=" << value.width << ", height=" << value.height
+            << ", top_left_radius_x=" << value.top_left_radius_x
+            << ", top_left_radius_y=" << value.top_left_radius_y
+            << ", top_right_radius_x=" << value.top_right_radius_x
+            << ", top_right_radius_y=" << value.top_right_radius_y
+            << ", bottom_left_radius_x=" << value.bottom_left_radius_x
+            << ", bottom_left_radius_y=" << value.bottom_left_radius_y
+            << ", bottom_right_radius_x=" << value.bottom_right_radius_x
+            << ", bottom_right_radius_y=" << value.bottom_right_radius_y << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const mojo::Transform& value) {
+  if (value.matrix) {
+    os << "[";
+    for (size_t i = 0; i < 4; i++) {
+      if (i != 0)
+        os << ", ";
+      os << "[";
+      for (size_t j = 0; j < 4; j++) {
+        if (j != 0)
+          os << ", ";
+        os << value.matrix[i * 4 + j];
+      }
+      os << "]";
+    }
+    os << "]";
+  } else {
+    os << "null";
+  }
+  return os;
+}
+
+}  // namespace mojo
diff --git a/mojo/services/geometry/cpp/formatting.h b/mojo/services/geometry/cpp/formatting.h
new file mode 100644
index 0000000..0942f2f
--- /dev/null
+++ b/mojo/services/geometry/cpp/formatting.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_SERVICES_GEOMETRY_CPP_FORMATTING_H_
+#define MOJO_SERVICES_GEOMETRY_CPP_FORMATTING_H_
+
+#include "mojo/public/cpp/bindings/formatting.h"
+#include "mojo/services/geometry/interfaces/geometry.mojom.h"
+
+namespace mojo {
+
+std::ostream& operator<<(std::ostream& os, const mojo::Point& value);
+std::ostream& operator<<(std::ostream& os, const mojo::Size& value);
+std::ostream& operator<<(std::ostream& os, const mojo::Rect& value);
+std::ostream& operator<<(std::ostream& os, const mojo::RRect& value);
+std::ostream& operator<<(std::ostream& os, const mojo::Transform& value);
+
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_GEOMETRY_CPP_FORMATTING_H_
diff --git a/mojo/services/geometry/cpp/geometry_util.cc b/mojo/services/geometry/cpp/geometry_util.cc
new file mode 100644
index 0000000..570f707
--- /dev/null
+++ b/mojo/services/geometry/cpp/geometry_util.cc
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/geometry/cpp/geometry_util.h"
+
+#include <string.h>
+
+namespace mojo {
+
+static const float kIdentityMatrix[]{
+    1.f, 0.f, 0.f, 0.f,  // comments to prevent
+    0.f, 1.f, 0.f, 0.f,  // auto formatter reflow
+    0.f, 0.f, 1.f, 0.f,  //
+    0.f, 0.f, 0.f, 1.f};
+
+void SetIdentityTransform(Transform* transform) {
+  transform->matrix.resize(16u);
+  memcpy(transform->matrix.data(), kIdentityMatrix, sizeof(kIdentityMatrix));
+}
+
+void SetTranslationTransform(Transform* transform, float x, float y, float z) {
+  SetIdentityTransform(transform);
+  transform->matrix[3] = x;
+  transform->matrix[7] = y;
+  transform->matrix[11] = z;
+}
+
+}  // namespace mojo
diff --git a/mojo/services/geometry/cpp/geometry_util.h b/mojo/services/geometry/cpp/geometry_util.h
index 3d4d35e..97271c9 100644
--- a/mojo/services/geometry/cpp/geometry_util.h
+++ b/mojo/services/geometry/cpp/geometry_util.h
@@ -5,7 +5,7 @@
 #ifndef MOJO_SERVICES_GEOMETRY_CPP_GEOMETRY_UTIL_H_
 #define MOJO_SERVICES_GEOMETRY_CPP_GEOMETRY_UTIL_H_
 
-#include "geometry/interfaces/geometry.mojom.h"
+#include "mojo/services/geometry/interfaces/geometry.mojom.h"
 
 namespace mojo {
 
@@ -34,6 +34,9 @@
   return !(lhs == rhs);
 }
 
+void SetIdentityTransform(Transform* transform);
+void SetTranslationTransform(Transform* transform, float x, float y, float z);
+
 }  // namespace mojo
 
 #endif  // MOJO_SERVICES_GEOMETRY_CPP_GEOMETRY_UTIL_H_
diff --git a/mojo/services/geometry/interfaces/geometry.mojom b/mojo/services/geometry/interfaces/geometry.mojom
index a6d2d4e..1f882b6 100644
--- a/mojo/services/geometry/interfaces/geometry.mojom
+++ b/mojo/services/geometry/interfaces/geometry.mojom
@@ -34,6 +34,21 @@
   float height;
 };
 
+struct RRect {
+  int32 x;
+  int32 y;
+  int32 width;
+  int32 height;
+  int32 top_left_radius_x;
+  int32 top_left_radius_y;
+  int32 top_right_radius_x;
+  int32 top_right_radius_y;
+  int32 bottom_left_radius_x;
+  int32 bottom_left_radius_y;
+  int32 bottom_right_radius_x;
+  int32 bottom_right_radius_y;
+};
+
 struct Transform {
   // Row major order.
   array<float, 16> matrix;