Improve composite glyph bounds calculation

Extend GlyphCoordinates to transparently support float coordinates.
As a result, transformed glyph components now don't have their
coordinates rounded anymore.  This slightly changes bounding box
calculations.

There's also code added, but disabled, to calculate exact glyph
bounding box, but we don't seem to actually want that.
diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py
index acbb02f..8ed5d6d 100644
--- a/Lib/fontTools/misc/arrayTools.py
+++ b/Lib/fontTools/misc/arrayTools.py
@@ -18,6 +18,18 @@
     ys = [y for x, y in array]
     return min(xs), min(ys), max(xs), max(ys)
 
+def calcIntBounds(array):
+    """Return the integer bounding rectangle of a 2D points array as a
+    tuple: (xMin, yMin, xMax, yMax)
+    """
+    xMin, yMin, xMax, yMax = calcBounds(array)
+    xMin = int(math.floor(xMin))
+    xMax = int(math.ceil(xMax))
+    yMin = int(math.floor(yMin))
+    yMax = int(math.ceil(yMax))
+    return xMin, yMin, xMax, yMax
+
+
 def updateBounds(bounds, p, min=min, max=max):
     """Return the bounding recangle of rectangle bounds and point (x, y)."""
     (x, y) = p
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
index fc47903..b3f5eaa 100644
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -6,7 +6,8 @@
 from fontTools.misc import sstruct
 from fontTools import ttLib
 from fontTools.misc.textTools import safeEval
-from fontTools.misc.arrayTools import calcBounds
+from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect
+from fontTools.misc.bezierTools import calcQuadraticBounds
 from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
 from . import DefaultTable
 from . import ttProgram
@@ -540,16 +541,69 @@
 				compressedflags.append(flag)
 			lastflag = flag
 		data = data + array.array("B", compressedflags).tostring()
-		xPoints = list(map(int, xPoints))  # work around struct >= 2.5 bug
-		yPoints = list(map(int, yPoints))
+		if coordinates.isFloat():
+			# Warn?
+			xPoints = [int(round(x)) for x in xPoints]
+			yPoints = [int(round(y)) for y in xPoints]
 		data = data + struct.pack(*(xFormat,)+tuple(xPoints))
 		data = data + struct.pack(*(yFormat,)+tuple(yPoints))
 		return data
 	
 	def recalcBounds(self, glyfTable):
-		coordinates, endPts, flags = self.getCoordinates(glyfTable)
-		if len(coordinates) > 0:
-			self.xMin, self.yMin, self.xMax, self.yMax = calcBounds(coordinates)
+		coords, endPts, flags = self.getCoordinates(glyfTable)
+		if len(coords) > 0:
+			if 0:
+				# This branch calculates exact glyph outline bounds
+				# analytically, handling cases without on-curve
+				# extremas, etc.  However, the glyf table header
+				# simply says that the bounds should be min/max x/y
+				# "for coordinate data", so I suppose that means no
+				# fancy thing here, just get extremas of all coord
+				# points (on and off).  As such, this branch is
+				# disabled.
+
+				# Collect on-curve points
+				onCurveCoords = [coords[j] for j in range(len(coords))
+						 if flags[j] & flagOnCurve]
+				# Add implicit on-curve points
+				start = 0
+				for end in endPts:
+					last = end
+					for j in range(start, end + 1):
+						if not ((flags[j] | flags[last]) & flagOnCurve):
+							x = (coords[last][0] + coords[j][0]) / 2
+							y = (coords[last][1] + coords[j][1]) / 2
+							onCurveCoords.append((x,y))
+						last = j
+					start = end + 1
+				# Add bounds for curves without an explicit extrema
+				start = 0
+				for end in endPts:
+					last = end
+					for j in range(start, end + 1):
+						if not (flags[j] & flagOnCurve):
+							next = j + 1 if j < end else start
+							bbox = calcBounds([coords[last], coords[next]])
+							if not pointInRect(coords[j], bbox):
+								# Ouch!
+								warnings.warn("Outline has curve with implicit extrema.")
+								# Ouch!  Find analytical curve bounds.
+								pthis = coords[j]
+								plast = coords[last]
+								if not (flags[last] & flagOnCurve):
+									plast = ((pthis[0]+plast[0])/2, (pthis[1]+plast[1])/2)
+								pnext = coords[next]
+								if not (flags[next] & flagOnCurve):
+									pnext = ((pthis[0]+pnext[0])/2, (pthis[1]+pnext[1])/2)
+								bbox = calcQuadraticBounds(plast, pthis, pnext)
+								onCurveCoords.append((bbox[0],bbox[1]))
+								onCurveCoords.append((bbox[2],bbox[3]))
+						last = j
+					start = end + 1
+
+				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(onCurveCoords)
+			else:
+				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords)
 		else:
 			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
 	
@@ -897,6 +951,22 @@
 		self._a = array.array("h")
 		self.extend(iterable)
 
+	def isFloat(self):
+		return self._a.typecode == 'f'
+
+	def _ensureFloat(self):
+		if self.isFloat():
+			return
+		self._a = array.array("f", self._a)
+
+	def _checkFloat(self, p):
+		if not any(isinstance(v, float) for v in p):
+			return
+		p = [int(v) if int(v) == v else v for v in p]
+		if not any(isinstance(v, float) for v in p):
+			return
+		self._ensureFloat()
+
 	@staticmethod
 	def zeros(count):
 		return GlyphCoordinates([(0,0)] * count)
@@ -921,17 +991,20 @@
 			for j,i in enumerate(indices):
 				self[i] = v[j]
 			return
+		self._checkFloat(v)
 		self._a[2*k],self._a[2*k+1] = v
 
 	def __repr__(self):
 		return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
 
 	def append(self, p):
+		self._checkFloat(p)
 		self._a.extend(tuple(p))
 
 	def extend(self, iterable):
-		for x,y in iterable:
-			self._a.extend((x,y))
+		for p in iterable:
+			self._checkFloat(p)
+			self._a.extend(p)
 
 	def relativeToAbsolute(self):
 		a = self._a
@@ -963,8 +1036,9 @@
 		for i in range(len(a) // 2):
 			x = a[2*i  ]
 			y = a[2*i+1]
-			a[2*i  ] = int(.5 + x * t[0][0] + y * t[1][0])
-			a[2*i+1] = int(.5 + x * t[0][1] + y * t[1][1])
+			px = x * t[0][0] + y * t[1][0]
+			py = x * t[0][1] + y * t[1][1]
+			self[i] = (px, py)
 
 	def __eq__(self, other):
 		if type(self) != type(other):