CurveScript is a language for programmatically generating vector graphics which may then be serialized to various formats such as SVG, XAML, and PDF.

CurveScript programs compile to a simple platform-independent instruction set that can be executed by an equally simple virtual machine. At the time of writing, CurveScript has 19 instructions, and the VM is implemented in just under 200 lines of Go. This simplicity makes it easy for applications to include a VM, which allows them to include resources as compiled CurveScript programs rather than fully rendered SVG/XAML/PDF, which may require significantly more storage.

If you're not interested in the details and just want to see it in action, head over to the online IDE.

The language is far from finalized, and will likely change as I use it more. Because of this, the guide is in very rough shape, favoring easy updating over thoroughness as the language changes. The guide will probably only be of much use to people already familiar with other languages.

- Statically typed
- Types are specified on function declarations and inferred in all other places
- Identifiers must be declared before being used
- Blocks can be nested to create new scopes
- Expressions may make use of identifiers from any parent scope
- Closures capture values rather than references
- If a function declaration is being stored in a variable, the body of the function may reference that variable as an easy way for a function to reference itself (e.g. for recursion purposes)
- Functions may be passed as values

An array is a reference to a sequence of elements of the same type. Each element is identified by its 0-based position in the sequence. Arrays are instantiated by enclosing elements in brackets. Array types are specified by enclosing the element type in square brackets.

```
secondElement := function(elements [number]|number) {
```

return(elements[1])

}

radii := [-1.5,42,-100]

radius := secondElement(radii)

shape := circle(radius)

return(translate(shape, 100, 100))

Values can be assigned to named storage locations using the equal sign between the name of the storage location and the value to be stored. If the storage location needs to be allocated, the equal sign is prefixed with a colon.

```
radius := -10
```

radius = 50

shape := circle(radius)

return(translate(shape, 100, 100))

If a function call returns multiple values, multiple comma-separated storage locations are used.

```
doubleTriple := function(value number|number, number) {
```

return(value * 2, value * 3)

}

width, height := doubleTriple(10)

shape := rectangle(width, height)

return(translate(shape, 100, 100))

Blocks contain a sequence of statements, including sub-blocks. Blocks are used to hold the body of functions and conditionals. Blocks can also be used to restrict the scope of a variable.

```
radius := -5
```

{

factor := -6

radius = radius * factor

}

shape := circle(radius)

return(translate(shape, 100, 100))

Boolean is a built-in type with only two possible values: true, and false.

```
wide := true
```

width := 40

height := 40

if wide {

width = width * 2

}

shape := rectangle(width, height)

return(translate(shape, 100, 100))

Functions can be called using a pair of parentheses that contain one comma-separated argument for each parameter of the function. The call evaluates to the set of values returned by the called function.

```
swap := function(first number, second number|number, number) {
```

return(second, first)

}

portraitWidth := 10

portraitHeight := 20

landscapeWidth, landscapeHeight := swap(portraitWidth, portraitHeight)

shape := rectangle(landscapeWidth, landscapeHeight)

return(translate(shape, 100, 100))

Functions are defined with the function keyword, followed by parentheses containing the inputs and outputs, followed by a block containing the implementation.

The inputs are a comma-separated list of identifier and type pairs. The outputs are a comma-separated list of types. The input list and output list are separated by a vertical bar.

```
swap := function(first number, second number|number, number) {
```

return(second, first)

}

portraitWidth := 10

portraitHeight := 20

landscapeWidth, landscapeHeight := swap(portraitWidth, portraitHeight)

shape := rectangle(landscapeWidth, landscapeHeight)

return(translate(shape, 100, 100))

Blocks can be conditionally executed with the if keyword, followed by any expression that evaluates to the boolean, followed by the block to execute if the condition evaluates to true.

Additional conditions can be added with an "else if". Only a single block within a chain will be executed (the first block whose expression evaluates to true).

Finally, a block can be execute if none of the conditions are met by prefixing it with the "else" keyword.

```
width := 10
```

height := 20

if 25 < 10 {

width = 1

} else if 15 < 10 {

width = 10

} else if 5 < 10 {

width = 20

} else {

width = 30

}

shape := rectangle(width, height)

return(translate(shape, 100, 100))

Infix operators combine two expressions and produce a single value. The following operators are defined:

`+`

Add - Combine two numbers into their sum`-`

Subtract - Combine two numbers into their difference`*`

Multiply - Combine two numbers into their product`/`

Divide - Combine two numbers into their quotient`>`

Greater - Combine two numbers into a boolean indicating whether the first number is larger than the second`<`

Less - Combine two numbers into a boolean indicating whether the first number is smaller than the second`==`

Equal - Combine two numbers into a boolean indicating whether the first number is equal to the second`!=`

Differ - Combine two numbers into a boolean indicating whether the first number differs from the second

```
height := 20
```

width := 0

if 1 < 2 {

width = width + 1

}

if 1 > 2 {

width = 0

}

if 1 == 2 {

width = 0

}

if 1 != 2 {

width = width + 2

}

width = width + 3

width = width - 1

width = width * 40

width = width / 10

shape := rectangle(width, height)

return(translate(shape, 100, 100))

The number type represents a 64-bit double-precision floating point value (IEEE754). Number literals must begin with a digit.

Prefix operators convert the value from a single following expression into a different value. The following prefix operators are defined:

`!`

Not - Converts the following boolean value into the opposite boolean value`-`

Negate - Converts the following number value into the negation of the value

```
height := 20
```

condition := false

width := -20

if !condition {

width = -width

}

shape := rectangle(width, height)

return(translate(shape, 100, 100))

The return keyword exits the current function and makes the function call evaluate to the specified parameters. The return must have the same number and type of parameters specified by the function being returned from.

```
size := function(top number, right number, bottom number, left number|number, number) {
```

return(bottom - top, right - left)

}

width, height := size(12, 250, 30, 100)

shape := rectangle(width, height)

return(translate(shape, 100, 100))

The while loop allows a block of code to be repeated while a condition evaluates to true.

```
height := 20
```

width := 0

iterations := 5

while iterations > 0 {

width = width + 4

iterations = iterations - 1

}

shape := rectangle(width, height)

return(translate(shape, 100, 100))

`curvescript`

provides APIs for manipulating various graphics objects:

`curve`

: a cubic bezier curve.

`contour`

: a start point, followed by a continuous sequence of curves, each starting where the
previous ended.

`shape`

: a set of contours that define a filled area. Overlapping/enclosed contours form voids
(e.g. a 2D ring has two concentric circular contours). Non-overlapping contours form disjoint areas within
a single shape. Contour order is visually irrelevant, as they are all combined to describe a filled area.

`group`

: a sequence of elements, each of which may be a `shape`

or another
`group`

. The order of nodes within the sequence determines the rendering order, with the content
of earlier elements appearing underneath later elements.

A `shape`

can be used in any situation that calls for a `group`

.

The ultimate purpose of a `curvescript`

program is to use the APIs to create and return a
`group`

that describe the desired image. This content of this `group`

is rendered into
the desired image format (e.g. SVG).

`curvescript`

uses a coordinate system starting at the top-left corner, with increasing x and y
coordinates towards the bottom-right.

`oval(width number, height number|shape)`

creates a new `shape`

containing an oval of the specified `width`

and
`height`

.

`circle(radius number|shape)`

creates a new `shape`

containing a circle of the specified `radius`

.

`rectangle(width number, height number|shape)`

creates a new `shape`

containing a rectangle of the specified `width`

and
`height`

.

`roundedRectangle(width number, height number, radius number|shape)`

creates a new `shape`

containing a rectangle of the specified `width`

and
`height`

, with corners rounded to the specified `radius`

.

`translate(group group, xOffset number, yOffset number|)`

moves the specified `group`

`xOffset`

units horizontally, and `yOffset`

units vertically.

`rotate(group group, degrees number|)`

rotates the specified `group`

about the origin, the specified number of `degrees`

.

`scale(group group, xScale number, yScale number|)`

scales the specified `group`

about the origin, horizontally by the factor specified in
`xScale`

, and vertically by the factor specified in `yScale`

.

`merge(first shape, second shape|shape)`

creates a new `shape`

consisting of all area included in either the `first`

or
`second`

shape.

`cut(first shape, second shape|shape)`

creates a new `shape`

consisting of all area included in the `first`

`shape`

that does not overlap with the `second`

`shape`

.

`overlap(first shape, second shape|shape)`

creates a new `shape`

consisting of all area included in both the `first`

`shape`

and `second`

`shape`

.

`xor(first shape, second shape|shape)`

creates a new `shape`

consisting of all area included in either the `first`

`shape`

or `second`

`shape`

, but not both.

`emptyShape(|shape)`

creates a new `shape`

containing no `contours`

s.

`startContour(shape shape, x number, y number|)`

creates a new `contour`

within the specified `shape`

, beginning at the specified
`x`

and `y`

coordinates.

`absoluteLine(shape shape, x number, y number|)`

extends the last `contour`

in the specified `shape`

with a line connecting the last
point in the `shape`

to the specified `x`

and `y`

coordinates.

`absoluteCurve(shape shape, endX number, endY number, startControlOffsetX number, startControlOffsetY number, endControlOffsetX number, endControlOffsetY number)`

extends the last `contour`

in the specified `shape`

with a `curve`

connecting the last point in the `contour`

to the specified `endX`

and
`endY`

coordinates. The curvature is determined by a control point extending from the start of
the `curve`

, and a control point extending from the end of the `curve`

. The start
control point is specified as an offset from the start point, via `startControlOffsetX`

and
`startControlOffsetY`

. The end control point is specified as an offset from the end point, via
`endControlOffsetX`

and `endControlOffsetY`

.

`absoluteArc(shape shape, endX number, endY number, startDirectionX number, startDirectionY number|)`

extends the last `contour`

in the specified `shape`

with a circular arc connecting the
last point in the `contour`

to the specified `endX`

and `endY`

coordinates.
The arc begins in the direction specified by `startDirectionX`

and `startDirectionY`

(their magnitude does not matter, as the direction and endpoint imply the necessary radius the arc).

`line(shape shape, endOffsetX number, endOffsetY number|)`

extends the last `contour`

in the specified `shape`

with a line connecting the last
point in the `contour`

to the point `endOffsetX`

and `endOffsetY`

units
away.

`curve(shape shape, endOffsetX number, endOffsetY number, startControlAngle number, startControlLength number, endControlAngle number, endControlLength number|)`

extends the last `contour`

in the specified `shape`

with a `curve`

connecting the last point in the `contour`

to a point `endOffsetX`

away horizontally,
and `endOffsetY`

away vertically. The curvature is determined by a control point extending from
the start of the `curve`

, and a control point extending from the end of the `curve`

.
The start control point is `startControlLength`

away from the start point, with an angle of
`startControlAngle`

degrees between the start control point and the end point. The end control
point is `endControlLength`

away from the end point, with an angle of
`endControlAngle`

degrees between the end control point and the start point.

`arc(shape shape, startDirectionX number, startDirectionY number, angle number, radius number|)`

extends the last `contour`

in the specified `shape`

with a circular arc starting in
the direction specified by `startDirectionX`

and `startDirectionY`

, circumscribing
`angle`

degrees, with the specified `radius`

. The magnitude of
`startDirectionX`

and `startDirectionY`

do not matter, as the radius is explicitly
specified.

`smoothLine(shape shape, length number|)`

extends the last `contour`

in the specified `shape`

with a line of the specified
`length`

, continuing in the same direction as the end of the `contour`

.

`corner(shape shape, angle number, radius number|)`

extends the last `contour`

in the specified `shape`

with a circular arc starting in
the same direction as the end of the `contour`

, circumscribing `angle`

degrees with
the specified `radius`

.

`smoothCurve(shape shape, endOffsetX number, endOffsetY number, startControlLength number, endControlAngle number, endControlLength number|)`

extends the last `contour`

in the specified `shape`

with a `curve`

connecting the last point in the `contour`

to a point `endOffsetX`

away horizontally,
and `endOffsetY`

away vertically. The curvature is determined by a control point extending from
the start of the `curve`

, and a control point extending from the end of the `curve`

.
The start control point is `startControlLength`

away from the start point, continuing in the same
direction as the end of the shape. The end control point is `endControlLength`

away from the end
point, with an angle of `endControlAngle`

degrees between the end control point and the start
point.

`close(shape shape|)`

extends the last `contour`

in the specified `shape`

with a line connecting the last
point in the `contour`

to the start point in the `contour`

.

`endOffset(shape shape|number, number)`

returns the horizontal and vertical distance from the start of the last `contour`

to the end of
the last `contour`

in the specified `shape`

.

`endPoint(shape shape|number, number)`

returns the horizontal and vertical coordinates of the end of the last `contour`

in the specified
`shape`

.

`startPoint(shape shape|number, number)`

returns the horizontal and vertical coordinates of the start of the last `contour`

in the
specified `shape`

.

`point(shape shape, curveNumber number|number number)`

returns the horizontal and vertical coordinates of the end of the last `curve`

indicated by
`curveNumber`

. Point 0 is the start of the `contour`

, point 1 is the end of the first
`curve`

, point 2 is the end of the second `curve`

, and so on.

`symmetricControl(shape shape, endOffsetX number, endOffsetY number|number, number)`

computes the angle and length of the starting control point required to make a symmetric `curve`

from the end of the last `contour`

in the specified `shape`

to a `curve`

ending `endOffsetX`

and `endOffsetY`

away from the end of the last
`contour`

.

`bounds(group group|number, number, number, number)`

returns the left, top, right, and bottom coordinates of a rectangle enclosing the specified
`group`

.

`center(group group|group)`

centers the specified `group`

about the origin and returns the specified `group`

.

`trim(group group|group)`

moves the top left corner of the specified `group`

to the origin and returns the specified
`group`

.

`fill(group group, hue number, saturation number, lightness number, opacity number|)`

sets the fill of each shape in the specified `group`

to the color with the specified
`hue`

, `saturation`

, `lightness`

, and sets the fill to the specified
`opacity`

.

`stroke(group group, hue number, saturation number, lightness number, opacity number|)`

sets the outline of each shape in the specified `group`

to the color with the specified
`hue`

, `saturation`

, `lightness`

, and sets the outline to the specified
`opacity`

.

`clone(shape shape|shape)`

returns a duplicate of the specified `shape`

, containing duplicates of all the contained
`contours`

.

`group(elements [group]|group)`

returns a new group containing the specified `elements`

.

`append(group group, elements [group]|)`

appends the specified `elements`

to the specified `group`

.