Ben Szymanski

Software Engineer • Vlogging about efoils, tech and music • 🇺🇸 & 🐊

Thinking through the challenges of how SVG commands map to CoreGraphics APIs.

Recently I have been converting shapes I drew in Adobe Illustrator to shapes drawn by Core Graphics. The process that I've been using is to save the Illustrator document as a SVG graphic, then manually convert the paths into the Core Graphics API. The paths and control points that make up the shape are encoded into a sort of syntax or markup that is specific to SVG. It looks kind of like this:

<path d="M0.0,0.0c0,0,62.437,40.725,56.436,45.807s-43.09,8.187-42,38.433z"/>

Luckily, most of SVG commands line up pretty well with the Core Graphics API, but I have run into two downfalls. The first is that Illustrator likes to export paths and points as relative instead of absolutely positioned. This means that y ou have to look at the ending point of the last path, then use that as the base for which you add all the new coordinate pairs on to. For example:

CGPathMoveToPoint(2.0, 3.0)
CGPathAddCurveToPoint((2.0 + 0.0), (3.0 + 0.0), 
  (2.0 + 62.437), (3.0 + 40.725), 
  (2.0 + 56.436), (3.0 + 45.807))
// CGPathAddCurveToPoint(CTRLPoint1x, CTRLPoint1y, CTRLPoint2x, CTRLPoint2y, x, y)

The second is that Illustrator will also sometimes write out a cubic bezier curve command using "shorthand" notation.

Where a full cubic bezier command will either have the following format:

C cp1x, cp1y, cp2x, cp2y, x, y
c cp1x, cp1y, cp2x, cp2y, x, y

A shorthand cubic bezier command has the following format:

S cp2x, cp2y, x, y
s cp2x, cp2y, x, y

There's no shorthand cubic bezier drawing command in the Core Graphics API, which means that you need to figure out what cp1x and cp1y are in order to use the CGPathAddCurveToPoint function.

This is where I struggled, because the spec didn't make it clear enough for me. The spec says this:

Draws a cubic Bézier curve from the current point to (x,y). The first control point is assumed to be the reflection of the second control point on the previous command relative to the current point. (If there is no previous command or if the previous command was not an C, c, S or s, assume the first control point is coincident with the current point.) (x2,y2) is the second control point (i.e., the control point at the end of the curve)... At the end of the command, the new current point becomes the final (x,y) coordinate pair used in the polybézier.

SVG Spec

What you need to do is the following:

previous curve ending x point - previous curve control point 2 x = difference
previous curve ending x point + difference = shorthand curve control point 1 x

previous curve ending y point - previous curve control point 2 y = difference
previous curve ending y point + difference = shorthand curve control point 1 y

If the ending point for the first cubic bezier curve is 56.436, 45.807, and the second control point is 62.437, 40.725, we get the following:

56.436 - 62.437 = -6.001
56.436 + (-6.001) = 50.435

45.807 - 40.725 = 5.082
45.807 + 5.082 = 50.889

CGPathAddCurveToPoint(50.435, 50.889, 
  (56.436 + (-43.09)), (45.807 + 8.187), 
  (56.436 + (-42.0)), (45.807 + 38.433))

As I said, the spec was confusing to me, and I didn't entirely understand what it was saying until I found a website that did the conversion for you, which allowed me to sort of reverse-out how it was getting its calculated values for the missing control point 1 x and y values.

I also referenced these sites on reflections and drawing cubic bezier curves:

Proudly powered by Pelican, which takes great advantage of Python.