Finding a pattern or formula to convert Sketch shape path points(json format) to svg path

859 views Asked by At

I am trying to find a pattern to convert Sketch shape points(json format) to svg path, as we know sketch files are just zip files, unzipping you will get json files. here are sample json codes

{
    "_class": "triangle",
    "do_objectID": "BE50CDBE-491C-4402-98EA-71E9C7B9F61B",
    "booleanOperation": -1,
    "frame": {
        "_class": "rect",
        "do_objectID": "0B39EF6D-9AD4-409B-85FA-D09B2B8A0692",
        "constrainProportions": false,
        "height": 56.86153846153846,
        "width": 66,
        "x": 0,
        "y": 0
    },
    "isFixedToViewport": false,
    "isFlippedHorizontal": false,
    "isFlippedVertical": false,
    "isLocked": false,
    "isVisible": true,
    "layerListExpandedType": 1,
    "name": "Triangle",
    "nameIsFixed": false,
    "resizingConstraint": 63,
    "resizingType": 0,
    "rotation": 0,
    "shouldBreakMaskChain": false,
    "userInfo": {
        "com.animaapp.stc-sketch-plugin": {
            "kModelPropertiesKey": {}
        }
    },
    "clippingMaskMode": 0,
    "hasClippingMask": false,
    "edited": false,
    "isClosed": true,
    "pointRadiusBehaviour": 1,
    "points": [
        {
            "_class": "curvePoint",
            "cornerRadius": 0,
            "curveFrom": "{0.49999999999999944, 0}",
            "curveMode": 1,
            "curveTo": "{0.49999999999999944, 0}",
            "hasCurveFrom": false,
            "hasCurveTo": false,
            "point": "{0.49999999999999944, 0}"
        },
        {
            "_class": "curvePoint",
            "cornerRadius": 0,
            "curveFrom": "{0.99999999999999889, 1}",
            "curveMode": 1,
            "curveTo": "{0.99999999999999889, 1}",
            "hasCurveFrom": false,
            "hasCurveTo": false,
            "point": "{0.99999999999999889, 1}"
        },
        {
            "_class": "curvePoint",
            "cornerRadius": 0,
            "curveFrom": "{0, 1}",
            "curveMode": 1,
            "curveTo": "{0, 1}",
            "hasCurveFrom": false,
            "hasCurveTo": false,
            "point": "{0, 1}"
        }
    ],
    "isEquilateral": false
}

this gives a Triangle with its svg path code

<polygon id="Triangle" points="33 0 66 56.8615385 0 56.8615385"></polygon>

for my understanding getting 33(x value) is done as follows, since above frame origin is at 0,0 (frame->x, frame->y) i thought to get its actual position is to take frame->width(since width corresponds to x axis) which is 66 times first x point in points, which is 0.49999999999999944, resulting to (32.99999999 approx 33), and first y point taking frame->height(56.86153846153846 times the first y point in points which is 0) resulting to the first point be 33 0

second point the same, to get x is taking frame->width(66) times second x point in points which is 0.99999999 resulting to 66, same with its corresponding y (56.86153846153846 times 1) which is 56.86153846153846, resulting to second point be 66 56.86153846153846 goes the same to third point..

Starts confusing if frame->x and frame->y has numbers greater than 0

consider this..

{
    "_class": "triangle",
    "do_objectID": "BB3FB5DE-F0A8-4AAD-8009-14F908023F19",
    "booleanOperation": -1,
    "frame": {
        "_class": "rect",
        "do_objectID": "316A9A7C-D0BF-4D71-BC65-1A0301846F20",
        "constrainProportions": true,
        "height": 32.49230769230769,
        "width": 37.56923076923076,
        "x": 14.21538461538461,
        "y": 22.33846153846153
    },
    "isFixedToViewport": false,
    "isFlippedHorizontal": false,
    "isFlippedVertical": false,
    "isLocked": false,
    "isVisible": true,
    "layerListExpandedType": 1,
    "name": "Triangle",
    "nameIsFixed": false,
    "resizingConstraint": 63,
    "resizingType": 0,
    "rotation": -180,
    "shouldBreakMaskChain": false,
    "userInfo": {
        "com.animaapp.stc-sketch-plugin": {
            "kModelPropertiesKey": {
                "constraints": {
                    "scaleFactor": 1,
                    "model_version": 0.1,
                    "aspectRatio": {
                        "multiplier": 37.56923,
                        "enabled": 1,
                        "model_version": 0.1,
                        "modelID": "constraint_dd3b6146-988c-411d-94e1-cb5ec2a60bc8",
                        "model_class": "ADModelConstraint",
                        "constant": 32.49231
                    },
                    "modelID": "viewConstraints_7f13ceae-6706-4965-b3af-3cde627fd97c",
                    "model_class": "ADModelViewConstraints",
                    "automatic": 1
                }
            }
        }
    },
    "clippingMaskMode": 0,
    "hasClippingMask": false,
    "edited": false,
    "isClosed": true,
    "pointRadiusBehaviour": 1,
    "points": [
        {
            "_class": "curvePoint",
            "cornerRadius": 0,
            "curveFrom": "{0.4999999999999995, 0}",
            "curveMode": 1,
            "curveTo": "{0.4999999999999995, 0}",
            "hasCurveFrom": false,
            "hasCurveTo": false,
            "point": "{0.4999999999999995, 0}"
        },
        {
            "_class": "curvePoint",
            "cornerRadius": 0,
            "curveFrom": "{0.999999999999999, 1.0000000000000009}",
            "curveMode": 1,
            "curveTo": "{0.999999999999999, 1.0000000000000009}",
            "hasCurveFrom": false,
            "hasCurveTo": false,
            "point": "{0.999999999999999, 1.0000000000000009}"
        },
        {
            "_class": "curvePoint",
            "cornerRadius": 0,
            "curveFrom": "{0, 1.0000000000000009}",
            "curveMode": 1,
            "curveTo": "{0, 1.0000000000000009}",
            "hasCurveFrom": false,
            "hasCurveTo": false,
            "point": "{0, 1.0000000000000009}"
        }
    ],
    "isEquilateral": false
}

its corresponding svg path is

<polygon id="Triangle" transform="translate(33.000000, 38.584615) rotate(180.000000) translate(-33.000000, -38.584615) " points="33 22.3384615 51.7846154 54.8307692 14.2153846 54.8307692"></polygon>

Now if i do my math as above, i don't get points="33 22.3384615 51.7846154 54.8307692 14.2153846 54.8307692" as above svg code shows. This is only for triangle, if is a custom shape path like facebook icon, it confuses alot!. Sample sketch logo,

Sketch Facebook Icon

its corresponding Sketch json code is as follows.

{
    "_class": "group",
    "do_objectID": "4AA67C64-14B8-433F-8569-9BF7BAEC2914",
    "booleanOperation": -1,
    "frame": {
        "_class": "rect",
        "do_objectID": "1FB80667-369B-4F79-9D8E-76ABD9065B12",
        "constrainProportions": true,
        "height": 50,
        "width": 50,
        "x": 0,
        "y": 0
    },
    "isFixedToViewport": false,
    "isFlippedHorizontal": false,
    "isFlippedVertical": false,
    "isLocked": false,
    "isVisible": true,
    "layerListExpandedType": 0,
    "name": "Facebook",
    "nameIsFixed": true,
    "resizingConstraint": 63,
    "resizingType": 0,
    "rotation": 0,
    "shouldBreakMaskChain": false,
    "userInfo": {
        "com.animaapp.stc-sketch-plugin": {
            "kModelPropertiesKey": {
                "constraints": {
                    "scaleFactor": 1,
                    "model_version": 0.1,
                    "aspectRatio": {
                        "constant": 50,
                        "enabled": 1,
                        "multiplier": 50,
                        "modelID": "constraint_c03c7307-1fe6-4ded-9d49-e35c2e25c117",
                        "model_class": "ADModelConstraint",
                        "model_version": 0.1
                    },
                    "modelID": "viewConstraints_b34c9514-3166-464d-a3dd-f968c4f6144b",
                    "model_class": "ADModelViewConstraints",
                    "automatic": 1
                }
            }
        }
    },
    "clippingMaskMode": 0,
    "hasClippingMask": false,
    "hasClickThrough": false,
    "layers": [
        {
            "_class": "oval",
            "do_objectID": "DA55B46C-1069-46DC-AAD6-78DCC03F4B3C",
            "booleanOperation": -1,
            "frame": {
                "_class": "rect",
                "do_objectID": "58627B0B-C1B7-4C33-BF5C-2B0F6F0DBC8E",
                "constrainProportions": true,
                "height": 50,
                "width": 50,
                "x": 0,
                "y": 0
            },
            "isFixedToViewport": false,
            "isFlippedHorizontal": false,
            "isFlippedVertical": false,
            "isLocked": false,
            "isVisible": true,
            "layerListExpandedType": 0,
            "name": "Oval",
            "nameIsFixed": false,
            "resizingConstraint": 63,
            "resizingType": 0,
            "rotation": 0,
            "shouldBreakMaskChain": false,
            "userInfo": {
                "com.animaapp.stc-sketch-plugin": {
                    "kModelPropertiesKey": {
                        "constraints": {
                            "scaleFactor": 1,
                            "model_version": 0.1,
                            "aspectRatio": {
                                "constant": 32,
                                "enabled": 1,
                                "multiplier": 32,
                                "modelID": "constraint_5868769e-cc0b-4eec-a4af-d965ce1b6520",
                                "model_class": "ADModelConstraint",
                                "model_version": 0.1
                            },
                            "modelID": "viewConstraints_9d4aa04f-2445-427e-9182-b1ce723cefd2",
                            "model_class": "ADModelViewConstraints",
                            "automatic": 1
                        }
                    }
                }
            },
            "clippingMaskMode": 0,
            "hasClippingMask": false,
            "edited": false,
            "isClosed": true,
            "pointRadiusBehaviour": 1,
            "points": [
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.77614237490000004, 1}",
                    "curveMode": 2,
                    "curveTo": "{0.22385762510000001, 1}",
                    "hasCurveFrom": true,
                    "hasCurveTo": true,
                    "point": "{0.5, 1}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{1, 0.22385762510000001}",
                    "curveMode": 2,
                    "curveTo": "{1, 0.77614237490000004}",
                    "hasCurveFrom": true,
                    "hasCurveTo": true,
                    "point": "{1, 0.5}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.22385762510000001, 0}",
                    "curveMode": 2,
                    "curveTo": "{0.77614237490000004, 0}",
                    "hasCurveFrom": true,
                    "hasCurveTo": true,
                    "point": "{0.5, 0}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0, 0.77614237490000004}",
                    "curveMode": 2,
                    "curveTo": "{0, 0.22385762510000001}",
                    "hasCurveFrom": true,
                    "hasCurveTo": true,
                    "point": "{0, 0.5}"
                }
            ]
        },
        {
            "_class": "shapePath",
            "do_objectID": "C96926E4-56B4-4816-A68B-22789ACF9811",
            "booleanOperation": -1,
            "frame": {
                "_class": "rect",
                "do_objectID": "33C190C2-C868-48CC-B5A5-E033A65A5DC5",
                "constrainProportions": false,
                "height": 30,
                "width": 14,
                "x": 17,
                "y": 10
            },
            "isFixedToViewport": false,
            "isFlippedHorizontal": false,
            "isFlippedVertical": false,
            "isLocked": false,
            "isVisible": true,
            "layerListExpandedType": 1,
            "name": "facebook [#176]",
            "nameIsFixed": false,
            "resizingConstraint": 63,
            "resizingType": 0,
            "rotation": 0,
            "shouldBreakMaskChain": false,
            "userInfo": {
                "com.animaapp.stc-sketch-plugin": {
                    "kModelPropertiesKey": {}
                }
            },
            "clippingMaskMode": 0,
            "hasClippingMask": false,
            "edited": true,
            "isClosed": true,
            "pointRadiusBehaviour": 0,
            "points": [
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.68212824010914053, 1}",
                    "curveMode": 1,
                    "curveTo": "{0.68212824010914053, 1}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.68212824010914053, 1}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.68212824010914053, 0.55000000000000004}",
                    "curveMode": 1,
                    "curveTo": "{0.68212824010914053, 0.55000000000000004}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.68212824010914053, 0.55000000000000004}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.95536932371857075, 0.55000000000000004}",
                    "curveMode": 1,
                    "curveTo": "{0.95536932371857075, 0.55000000000000004}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.95536932371857075, 0.55000000000000004}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{1, 0.34999999999999998}",
                    "curveMode": 1,
                    "curveTo": "{1, 0.34999999999999998}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{1, 0.34999999999999998}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.68212824010914053, 0.34999999999999998}",
                    "curveMode": 1,
                    "curveTo": "{0.68212824010914053, 0.34999999999999998}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.68212824010914053, 0.34999999999999998}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.68212824010914053, 0.20109999999999956}",
                    "curveMode": 4,
                    "curveTo": "{0.68212824010914053, 0.2525999999999996}",
                    "hasCurveFrom": true,
                    "hasCurveTo": false,
                    "point": "{0.68212824010914053, 0.2525999999999996}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.82868836484116337, 0.14999999999999999}",
                    "curveMode": 4,
                    "curveTo": "{0.68475930617813163, 0.14999999999999999}",
                    "hasCurveFrom": false,
                    "hasCurveTo": true,
                    "point": "{0.82868836484116337, 0.14999999999999999}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.97446891444162931, 0.14999999999999999}",
                    "curveMode": 1,
                    "curveTo": "{0.97446891444162931, 0.14999999999999999}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.97446891444162931, 0.14999999999999999}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.97446891444162931, 0.0048499999999989992}",
                    "curveMode": 4,
                    "curveTo": "{0.97446891444162931, 0.0069999999999993175}",
                    "hasCurveFrom": true,
                    "hasCurveTo": false,
                    "point": "{0.97446891444162931, 0.0069999999999993175}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.45800038978756463, 0}",
                    "curveMode": 3,
                    "curveTo": "{0.84924965893587745, 0}",
                    "hasCurveFrom": true,
                    "hasCurveTo": true,
                    "point": "{0.72256870005847007, 0}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.29234067433248878, 0.23499999999999943}",
                    "curveMode": 4,
                    "curveTo": "{0.29234067433248878, 0.082849999999999119}",
                    "hasCurveFrom": false,
                    "hasCurveTo": true,
                    "point": "{0.29234067433248878, 0.23499999999999943}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.29234067433248878, 0.34999999999999998}",
                    "curveMode": 1,
                    "curveTo": "{0.29234067433248878, 0.34999999999999998}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.29234067433248878, 0.34999999999999998}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0, 0.34999999999999998}",
                    "curveMode": 1,
                    "curveTo": "{0, 0.34999999999999998}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0, 0.34999999999999998}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0, 0.55000000000000004}",
                    "curveMode": 1,
                    "curveTo": "{0, 0.55000000000000004}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0, 0.55000000000000004}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.29234067433248878, 0.55000000000000004}",
                    "curveMode": 1,
                    "curveTo": "{0.29234067433248878, 0.55000000000000004}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.29234067433248878, 0.55000000000000004}"
                },
                {
                    "_class": "curvePoint",
                    "cornerRadius": 0,
                    "curveFrom": "{0.29234067433248878, 1}",
                    "curveMode": 1,
                    "curveTo": "{0.29234067433248878, 1}",
                    "hasCurveFrom": false,
                    "hasCurveTo": false,
                    "point": "{0.29234067433248878, 1}"
                }
            ]
        }
    ]
}

its svg code is

<g id="Facebook">
    <circle id="Oval" fill="#5B76AF" cx="25" cy="25" r="25"></circle>
    <path d="M26.5497954,40 L26.5497954,26.5 L30.3751705,26.5 L31,20.5 L26.5497954,20.5 L26.5497954,17.578 C26.5497954,16.033 26.5866303,14.5 28.6016371,14.5 L30.6425648,14.5 L30.6425648,10.21 C30.6425648,10.1455 28.8894952,10 27.1159618,10 C23.4120055,10 21.0927694,12.4855 21.0927694,17.05 L21.0927694,20.5 L17,20.5 L17,26.5 L21.0927694,26.5 L21.0927694,40 L26.5497954,40 Z" id="facebook-[#176]" fill="#FFFFFF"></path>
</g>

I understand <circle /> comes from the first layer, which i have no trouble with it, the issue is second layer, path which is f letter, as its sketch json points are mapped to <path d="M26.5497954,40 L26.5497954,26.5 L30.3751705,26.5 L31,20.5 L26.5497954,20.5 L26.5497954,17.578 C26.5497954,16.033 26.5866303,14.5 28.6016371,14.5 L30.6425648,14.5 L30.6425648,10.21 C30.6425648,10.1455 28.8894952,10 27.1159618,10 C23.4120055,10 21.0927694,12.4855 21.0927694,17.05 L21.0927694,20.5 L17,20.5 L17,26.5 L21.0927694,26.5 L21.0927694,40 L26.5497954,40 Z" id="facebook-[#176]" fill="#FFFFFF"></path>

which is something i am trying hard to crack.. Is there a pattern to turn sketch json points to svg path and how can i do it..

Thanks.

1

There are 1 answers

0
Codo On BEST ANSWER

I don't have the full solution but a few good hints:

Coordinates

All layers seem to use a local coordinate system with x and y values between 0 and 1. So you need the layers size and position to calculate the coordinates used in SVG:

xsvg = frame.x + xlayer * frame.width
ysvg = frame.y + ylayer * frame.height

Commands

The mapping of commands is trickier as the Sketch system uses a model different from SVG. The main difference is: SVG uses commands to represent path segments (except for the starting M) while Sketch uses a list of points that are either sharp or soft corners. In order to derive the command, you need to look at two consecutive points. Let's call them p1 and p2:

p1.hasCurveFrom = false and p2.hasCurveTo = false:

That represents a straight line, thus the L command. The coordinate is taken from p2.point.

p1.hasCurveFrom = true and p2.hasCurveTo = true:

That represents a cubic spline, thus the C command. The three coordinates are taken from:

  1. p1.curveFrom
  2. p2.curveTo
  3. p2.point

Further hints

The path starts with an M command to p.point of the first point.

The attribute "isClosed": true obviously indicates a closed path. Therefore, the commands end with Z to close the path.

There are probably many more subtleties that I don't know.

If you want to implement a full Sketch to SVG converter, you will also need to implement the different shape classes and not just triangle, oval and shapePath and you will need to take care of all the other attributes like isFlippedHorizontal, isFlippedVertical, rotation, cornerRadius, resizingType etc.