Adding end cap to strokes.
diff --git a/renderer/viewer/assets/stroke_caps.riv b/renderer/viewer/assets/stroke_caps.riv
new file mode 100644
index 0000000..d9622cb
--- /dev/null
+++ b/renderer/viewer/assets/stroke_caps.riv
Binary files differ
diff --git a/renderer/viewer/src/viewer.cpp b/renderer/viewer/src/viewer.cpp
index f4804a5..af75240 100644
--- a/renderer/viewer/src/viewer.cpp
+++ b/renderer/viewer/src/viewer.cpp
@@ -114,7 +114,8 @@
 	// std::string filename = "assets/off_road_car.riv";
 	// std::string filename = "assets/simple_stroke.riv";
 	// std::string filename = "assets/leg_issues.riv";
-	std::string filename = "assets/control.riv";
+	// std::string filename = "assets/control.riv";
+	std::string filename = "assets/stroke_caps.riv";
 	FILE* fp = fopen(filename.c_str(), "r");
 	fseek(fp, 0, SEEK_END);
 	fileBytesLength = ftell(fp);
diff --git a/src/contour_stroke.cpp b/src/contour_stroke.cpp
index a0e70eb..aa7f540 100644
--- a/src/contour_stroke.cpp
+++ b/src/contour_stroke.cpp
@@ -6,7 +6,6 @@
 using namespace rive;
 
 static const int subdivisionArcLength = 4.0f;
-bool first = true;
 
 void ContourStroke::reset()
 {
@@ -332,23 +331,47 @@
 		m_TriangleStrip[startOffset] = m_TriangleStrip[last - 1];
 		m_TriangleStrip[startOffset + 1] = m_TriangleStrip[last];
 	}
-
-	m_Offsets.push_back(m_TriangleStrip.size());
-
-	if (first)
+	else
 	{
-		printf("CLOSED: %i %i\n", isClosed, points.size() - 4);
-		for (auto pt : points)
+		switch (cap)
 		{
-			printf("P: %f %f\n", pt[0], pt[1]);
-		}
-		printf("---\n");
-		first = false;
-
-		for (auto v : m_TriangleStrip)
-		{
-			printf("%f %f\n", v[0], v[1]);
+			case StrokeCap::square:
+			{
+				auto l = m_TriangleStrip.size();
+				Vec2D squareA, squareB;
+				Vec2D strokeDiff = Vec2D(lastDiffNormalized[0] * strokeWidth,
+				                         lastDiffNormalized[1] * strokeWidth);
+				Vec2D::add(squareA, m_TriangleStrip[l - 2], strokeDiff);
+				Vec2D::add(squareB, m_TriangleStrip[l - 1], strokeDiff);
+				m_TriangleStrip.push_back(squareA);
+				m_TriangleStrip.push_back(squareB);
+				break;
+			}
+			case StrokeCap::round:
+			{
+				Vec2D capDirection =
+				    Vec2D(-lastDiffNormalized[1], lastDiffNormalized[0]);
+				float arcLength = std::abs(M_PI * strokeWidth);
+				int steps = (int)std::ceil(arcLength / subdivisionArcLength);
+				float angleTo = std::atan2(capDirection[1], capDirection[0]);
+				float inc = M_PI / steps;
+				float angle = angleTo;
+				// make sure to draw the full cap due triangle strip
+				for (int j = 0; j <= steps; j++)
+				{
+					m_TriangleStrip.push_back(lastPoint);
+					m_TriangleStrip.push_back(
+					    Vec2D(lastPoint[0] + std::cos(angle) * strokeWidth,
+					          lastPoint[1] + std::sin(angle) * strokeWidth));
+					angle -= inc;
+				}
+				break;
+			}
+			default:
+				break;
 		}
 	}
+
+	m_Offsets.push_back(m_TriangleStrip.size());
 }
 #endif