Keyshape Plugin API Reference

This document describes the Keyshape plugin manifest file and the plugin JavaScript API.

Keyshape Plugin Files

Keyshape plugins are zip archives with the .keyshapeplugin file suffix. The zip file contains a manifest.json file and JavaScript files. Note that the archive contains the plugin files themselves and must not include a containing directory. Such zip files can be created in Terminal:

cd my-plugin
zip -r -FS ../my-plugin.keyshapeplugin *

Manifest File Description

The plugin must have a manifest.json file. The manifest file has the following fields.

  • id - Required. This must be a unique URL to identify the plugin. The URL doesn’t have to point to any actual page. Example: "https://www.example.com/myplugin"
  • name - Required. The name of the plugin.
  • keyshape_manifest_version - Required. The manifest version, must be 1 (number, not a string).
  • version - Required. The version number of the plugin. It must have one to four numbers separated by a dot.
  • homepage - The homepage URL of the plugin.
  • author - The author of the plugin.
  • description - The description for the plugin.
  • license - The license of the plugin. For common licenses, this should be a SPDX license identifier, e.g. “MIT”. For licenses without a SPDX identifier, add a license file in the root of the plugin, and use the value “SEE LICENSE IN <filename>”.
  • keyshape_min_version - The minimum required Keyshape version number, e.g. “1.5”
  • keyshape_max_version - The maximum Keyshape version number, e.g. “1.9”
  • exporters - An array of exporters. Each exporter in the array has these properties:
    • exporter_id - Required. A unique exporter id. This will be concatenated with the plugin id to create a unique URL for the exporter. Only alphanumerics, underscore and hyphen [0-9A-Za-z_-] are allowed characters.
    • name - Required. The name of the exporter. This is shown in the export dialog.
    • file_suffix - The filename suffix for the exported file. This is shown in the export dialog.
    • script - Required. The name of the JavaScript file containing handler functions.
    • filename_handler - Required. The name of the function which returns filenames that will be written. The overwrite confimation dialog uses the filenames to check if files exist.
    • export_handler - Required. The name of the function which exports the document to a file.
  • importers - An array of importers. Each importer has these properties:
    • name - Required. Name of the importer.
    • file_suffixes - Required. An array of supported file suffixes, e.g. “xml”.
    • script - Required. The name of the JavaScript file containing handler functions.
    • recognize_handler - Required. The name of the function which checks if a file is supported by this importer.
    • import_handler - Required. The name of the function which does the actual importing.
  • previewers - An array of previewers. Each previewer has these properties:
    • previewer_id - Required. Id of the previewer. Only alphanumerics, underscore and hyphen [0-9A-Za-z_-]
    • name - Required. Name of the previewer.
    • script - Required. The name of the JavaScript file containing handler functions.
    • preview_handler - Required. The name of the function which creates the preview files.

Example manifest.json file:

{
    "id": "https://example.com/my-plugin",
    "name": "My Plugin",
    "version": "1.0",
    "homepage": "https://example.com",
    "author": "John Doe",
    "description": "My cool plugin to show an example.",
    "license": "MIT",
    "exporters": [
      {
        "name": "My format",
        "exporter_id": "myformat",
        "file_suffix": "json",
        "script": "exporter.js",
        "filename_handler": "onFilename",
        "export_handler": "onExport"
      }
    ],
    "importers": [
      {
        "name": "My importer",
        "file_suffixes": [ "xml" ],
        "script": "importer.js",
        "recognize_handler": "onRecognize",
        "import_handler": "onImport"
      }
    ],
    "previewers": [
      {
        "previewer_id": "my_previewer",
        "name": "My previewer",
        "script": "previewer.js",
        "preview_handler": "previewAnimation"
      }
    ]
}

Exporters: Filename and Export Handler Functions

The filename handler function should return the filenames (file URLs) which will be exported. The function receives one argument, which is the filename the user typed in the export dialog. If only one file will be output, it is enough to just return the user selected filename.

The returned filenames are used to check if existing files will be overwritten and Keyshape displays a dialog to the user to confirm the overwrite. It is possible to return multiple filenames, if the exporter wants to write multiple files.

The export handler function does the actual exporting. It has access to the document data and it can write data to files. It can only write to file URLs which were returned by the filename handler.

All filenames are file URLs: new URL("file:///Users/jdoe/example.txt").

Importers: Recognize and Import Handler Functions

The recognize handler function is given a filename URL, Uint8Array and string as parameters. The first 4 kilobytes of the file is stored as byte data in the array and as a string in the string parameter. The function should return a confidence number describing how certain it is that the importer can read the file. The function can use the filename and data to recognize the file. The returned values should be:

  • 0 - this importer cannot import the file
  • 50 - the filename (or its suffix) matches (if the suffix uniquely describes the file type)
  • 100 - the filename and data matches

The import handler function imports the file. It gets a filename as a parameter and can use app.fs.readFileSync() to read it. The importer should create elements to app.activeDocument.

Previewers: Preview Handler Function

The preview handler function gets a URL to the preview folder as a parameter. The function has access to the document data and it can write data to files. It can only write to file URLs under the folder which was given as the parameter to the previewer function. The function should return a URL which is the file which should be opened in the web browser.

Document Structure

Keyshape documents are very much like SVG documents. There are few deviations from the standard SVG format:

  • There is no distinction between SVG attributes and CSS properties. They are all accessed as properties. Properties with CSS names have inheritance, other properties do not have inheritance.
  • There is no transform property. Instead, transforms are described by "ks:positionX", "ks:positionY", "ks:scaleX", "ks:scaleY", "ks:skewX", "ks:skewY", "ks:rotate", "ks:anchorX" and "ks:anchorY".
  • There are no gradient elements. Instead, they are values starting with "-ks-linear-gradient" or "-ks-radial-gradient". The app.util.parseColor() function can be used to parse colors into JavaScript objects.

The standard SVG elements can have these additional properties (the default value is the first value):

  • The root <svg> element

    • ks:grid-visibility - visible | hidden - “visible” to show all visible grids.
    • ks:fps - document’s frames per second value.
    • ks:preview - svg/keyshapejs | svg/css - preview format.
    • ks:transparency - <color> [<color>] - a solid color or checkboard pattern to indicate transparency.
  • The <g> element

    • ks:type - normal | layer - if the value is “layer”, then the group element acts as a clickthrough layer.
  • Transformable objects (rect, ellipse, path, text, image, g)

    • ks:motion-rotation - none | auto - the value “auto” makes the element rotate along a motion path if the motion path has been defined.
  • All elements

    • ks:locked - none | locked - the value “locked” makes the element locked for selections and Object Tree shows a lock symbol.

Supported JavaScript Version

ECMAScript 6 (ES2015) is mostly supported. All scripts are run in the strict mode. Calling eval() or creating a Function with a code string is not allowed for security reasons. Importing functions from external files is not supported.

The standard built-in objects can be used, except the experimental, deprecated and non-standard functions. For instance, WebAssembly is not supported.

The Plugin JavaScript API

Documents are KSDocument objects and they contain KSElement objects. Each KSElement has properties and keyframes.

app

The app object is the global Keyshape application object. It is used to access the document, file system and utilities.

app.activeDocument - <KSDocument> - Read-only. The active document in Keyshape. This is null if the active document is not available (such as in importer recognizers).

Example:

let doc = app.activeDocument;

app.extension - <string> - Read-only. Access to the extension methods.

app.fs - <FileSystem> - Read-only. Access to the file system object and its methods.

app.util - <Util> - Read-only. Access to the Util object.

app.name - <string> - Read-only. The application’s name.

app.version - <string> - Read-only. The application’s version.

Extension

getURL(path)

  • path <string> - the file path in the plugin folder

Returns a URL file object, which is the plugin’s path appended with the given path.

Example:

let url = app.extension.getURL("mydata.txt");

KSDocument

getElementById(id)

  • id - <string> - id of the element

Returns the first KSElement for the given id or null if the id is not found.

Example:

let doc = app.activeDocument;
let elem = doc.getElementById("myid");

documentElement - <KSElement> - Read-only. The document root element.

selectedElements - <Array> - An array of KSElements. Commands are performed for these elements.

Example:

let doc = app.activeDocument;
doc.selectedElements = [ elem1, elem2 ];

cmd - <KSDocumentCommands> - Read-only. Access to commands.

getPlayRange()

Returns an object containing the following properties:

  • in - play range in time in milliseconds
  • out - play range out time in milliseconds, can be Infinity
  • definiteOut - play range out time in milliseconds, if “ks:playRangeOut” property is “infinity”, then this property is set to the time of the last keyframe

getMediaList()

Returns an Array of strings for each image used in the document. The strings can be absolute paths ("file:///test.png"), relative paths ("test.png") or embedded images ("data:embedded-1").

Example:

let list = app.activeDocument.getMediaList();

getMediaData(urlString)

  • urlString - <string> - path string of the image as a string, e.g. "file:///test.png", "example.png" or "data:embedded-1"

Returns a Uint8Array for the given string or null if data is not found. The returned Uint8Array contains raw PNG or JPEG image data.

Example:

let data = app.activeDocument.getMediaData("data:embedded-1");

getMediaInfo(urlString)

  • urlString - <string> - path string of the image, e.g. "file:///test.png", "example.png" or "data:embedded-1"

Returns an object or null if data is not found. The object contains the following keys:

  • width - the width of the image in pixels
  • height - the height of the image in pixels
  • mimetype - the MIME type of the image, "image/png", "image/jpeg"

Example:

let info = app.activeDocument.getMediaInfo("data:embedded-1");
let type = info.mimetype;

createElement(tagName)

  • tagName - <string> - element tag name. Only supported elements can be created.

Returns a new element.

Supported elements are: rect, ellipse, path, text, image, g, a, svg, defs, desc, title, symbol, use, ks:grid.

Example:

let p = app.activeDocument.createElement("path");
app.activeDocument.documentElement.append(p);

makePathDataKeyframesInterpolatable(pathDataKeyframeArray)

  • pathDataKeyframeArray <Array> - an array of SVG path data keyframes

Returns an Array of keyframes, where the path commands have been made similar. If the conversion isn’t possible, then keyframes are returned without modifications.

Example:

let newKeyframes = app.activeDocument.makePathDataKeyframesInterpolatable(
                        element.timeline().getKeyframes("d"));

KSElement

getProperty(propertyName, [returnInheritedValue])

  • propertyName <string>
  • returnInheritedValue <boolean> Optional. Defaults to true.

Returns the string value of the given property. If returnInheritedValue is set to true and the property is not set for the element, then returns the inherited value for CSS properties. Otherwise, returns null.

Note: the returned values are not validated and may contain invalid values for the property.

Examples:

let value = elem.getProperty("fill");
// returns "#000000" (inherited value) if fill is not set for the element or its ancestors

let value = elem.getProperty("fill", false);
// returns null if fill is not set for the element

hasProperty(propertyName)

  • propertyName <string>

Returns true if the element has the given property, otherwise returns false.

getPropertyNames()

Returns names of all properties in the element as an Array of strings.

setProperty(propertyName, value)

  • propertyName <string>
  • value <string>

Sets the value of a property. If the property is animated, then this method has no effect.

Example:

let elem = app.activeDocument.documentElement;
elem.setProperty("opacity", "0.5");

append(obj… objs)

  • obj <KSElement> or <string>

Appends the given elements or strings as children to this element. Strings can be appended to text elements only.

Example:

let t = doc.createElement("text");
let ts = doc.createElement("tspan");
ts.append("Golden", " fish");
t.append(ts);

insertAt(position, obj… objs)

  • position <number>
  • obj <KSElement> or <string>

Inserts the given elements or strings as children to this element at the given node position. Strings can be inserted to text elements only.

Example:

let t = doc.createElement("text");
t.insertAt(0, "This", " fish");        // insert at beginning
t.insertAt(1, "  small", " golden");   // inserts between "This" and " fish"
console.log(t.childArray);             // prints [ "This", " small", " golden", " fish" ]

contains(elem)

  • elem <KSElement>

Returns true if the given element is a child of this element, otherwise returns false.

children <Array> - Read-only. The child elements of this element as an array.

Example:

for (let child of elem.children)
    console.log("GOT: "+child.tagName);

childArray <Array> - Read-only. The child elements and texts as an array. The array contains KSElements or text strings.

Example:

for (let child of elem.childArray)
    console.log("GOT: "+child.tagName);

parentElement <KSElement> - Read-only. The parent element of this element or null if the element doesn’t have a parent element.

tagName <string> - Read-only. The tag name of this element.

textContent <string> - The text content of this and all descendant elements.

timeline()

Returns the KSElementTimeline object to access the keyframe data.

KSElementTimeline

getKeyframeNames()

Returns an array of keyframe property names or an empty array if no keyframes exist.

hasKeyframes(propertyName)

Returns true if keyframes exist for the given property, otherwise returns false.

Example:

let hasKf = elem.timeline().hasKeyframes("opacity");

getKeyframeParams(propertyName)

  • propertyName <string>

Returns an object containing parameters for the animated property or null if the property has no keyframes. The returned object has the following properties:

  • repeatEnd <number> - the end time of the repeat in milliseconds, null if no repeat and Infinity for a never-ending loop

Example:

let repeat = elem.timeline().getKeyframeParams("opacity").repeatEnd;

setKeyframeParams(propertyName, params)

  • propertyName <string>
  • params <Object>, containing the following properties:
    • repeatEnd <number> - Optional. The end time of the repeat in milliseconds, null to remove repeating, and Infinity for a never-ending loop. If the time is less than the time of the last keyframe, then repeating is removed.

Sets parameters for the animated property.

Example:

elem.timeline().setKeyframeParams("opacity", { repeatEnd: 3000 }

getKeyframes(propertyName)

Returns an array of keyframes for the animated property or an empty array if the property has no keyframes. Each keyframe has the following fields:

  • time <number> - the time of the keyframe in milliseconds
  • value <string> - the value of the keyframe.
  • easing <string> - the easing for the keyframe, such as "linear", "cubic-bezier(0, 0.2, 0.3, 0.4)", "steps(1, start)", "ease-in", "-ks-ease-in-quad" or any other easing function.

Examples:

let kfs = elem.timeline().getKeyframes("opacity");
for (let kf of kfs)
    console.log("Keyframe time: "+kf.time+" value: "+kf.value+" easing: "+kf.easing);

addKeyframe(propertyName, time)

  • propertyName <string>
  • time <number> - time in milliseconds

Adds a keyframe for the given property and time. The added keyframe will have the value returned by getAnimatedValue(propertyName, time).

setKeyframe(propertyName, time, value, [easing])

  • propertyName <string>
  • time <number> - time in milliseconds
  • value <string>
  • easing <string> - Optional. Defaults to “linear”.

Adds a keyframe for the given property and time. The keyframe’s value is set to the given value. The keyframe will use the optional easing value or linear easing if the easing value is not given. Easing is ignored for the last keyframe.

removeKeyframe(propertyName, time)

  • propertyName <string>
  • time <number> - time in milliseconds

Removes a keyframe for the given property and time.

getAnimatedValue(propertyName, time)

  • propertyName <string>
  • time <number> - time in milliseconds

Returns an interpolated value for the property at the given time. The returned value is a string. If the property is not animated, then the result is the same as calling getProperty(propertyName, true).

getMotionPath()

Returns the motion path of the element as SVG path string or null if the element has no motion path.

setMotionPath(pathData)

  • pathData <string>

Sets the motion path data for the element. The path string must be an SVG path, it must have the same amount of commands as there are keyframes and the path must not be closed.

Throws an exception if the position dimensions are separated.

Example:

elem.timeline().setMotionPath("M10,10L200,10L200,40");

isSeparated(propertyName)

  • propertyName <string>

Returns false if the X and Y dimensions of the property are not separated and true if they are separated.

Example:

let sep = elem.timeline().isSeparated("ks:positionX");

setSeparated(propertyName, separation)

  • propertyName <string>
  • separation <boolean>

Sets the X and Y dimensions of the property separated if separation is true. The dimensions are unseparated if separation is false.

If the property cannot be separated, then nothing happens.

Examples:

elem.timeline().setSeparated("ks:positionX", true);
elem.timeline().setSeparated("ks:scaleX", false);

getTransform(time)

  • time <number> - time in milliseconds

Returns an interpolated value for the transform at the given time. The returned value is a DOMMatrix.

simplifyEasings(propertyName)

  • propertyName <string>

Simplifies easing values for the given property. Converts built-in and custom easings to cubic bezier, step and linear easings.

Example:

elem.timeline().simplifyEasings("opacity");

KSDocumentCommands

convertToPath()

Converts rect, ellipse and text elements to paths. Only the elements listed in document.selectedElements are converted.

Example:

doc.selectedElements = [ myRectElement ];
doc.cmd.convertToPath();    // converts myRectElement to a path

detachFromSymbol()

Detaches use elements from their symbols or other references. Only the elements listed in document.selectedElements are detached.

Example:

doc.selectedElements = [ myUseElement ];
doc.cmd.detachFromSymbol();    // converts myUseElement to a group

FileSystem

readFileSync(file, [options])

  • file <URL> - a file URL
  • options <Object> - Optional. A dictionary of options.
    • encoding <string> | <null> - Optional. Default: null.

Reads data from a file. If errors occur, an exception is thrown.

If the encoding option is set to “utf-8”, then a string is returned. Otherwise a Uint8Array is returned.

Example:

let str = app.fs.readFileSync(new URL("file:///test.txt"), { encoding: 'utf-8' });

writeFileSync(file, data)

  • file <URL> - a file URL
  • data <string> | <Uint8Array>

Writes the given data to a file. If errors occur, an exception is thrown.

Example:

app.fs.writeFileSync(new URL("file:///test.txt"), "hello");

copyFileSync(src, dest)

  • src <URL> - a source file URL
  • dest <URL> - a destination file URL

Copies the given source file to destination file. If errors occur, an exception is thrown.

Example:

app.fs.copyFileSync(new URL("file:///test.txt"), new URL("file:///test-new.txt"));

mkdirSync(file)

  • file <URL> - a file URL

Creates a directory with the given filename. If errors occur, an exception is thrown.

Example:

app.fs.mkdirSync(new URL("file:///test.txt"));

existsSync(file)

  • file <URL> - a file URL

Returns true if a file exists with the given filename. If errors occur, an exception is thrown.

Example:

let exists = app.fs.existsSync(new URL("file:///test.txt"));

Console

console.log(data)

  • data <any>

Prints data with newline to the log. To see these, open Mac Console.app and filter the messages by “Keyshape”.

Util

parseColor(color)

  • color <string> - the color to be parsed, e.g., "#ff0000", "-ks-linear-gradient(...)"

Parses color and returns an object containing the following keys:

  • type: “none”, “color”, “linear-gradient”, “radial-gradient”

For the color type:

  • red: the red color component, a value between 0 and 1
  • green: the green color component, a value between 0 and 1
  • blue: the blue color component, a value between 0 and 1

For the linear-gradient type:

  • x1: the start x coordinate
  • y1: the start y coordinate
  • x2: the end x coordinate
  • y2: the end y coordinate

For the radial-gradient type:

  • r: the radius
  • cx: the gradient center x coordinate
  • cy: the gradient center y coordinate
  • fx: the gradient focal point x coordinate
  • fy: the gradient focal point y coordinate

For the linear and radial gradient types:

  • gradientUnits: “userSpaceOnUse” or “objectBoundingBox”
  • spreadMethod: “pad”, “reflect” or “repeat”
  • gradientTransform: the transform matrix of the gradient
  • stops: an array of stops, each stop having these properties:
    • offset: the offset of the stop, a value between 0 and 1
    • red: the red color component, a value between 0 and 1
    • green: the green color component, a value between 0 and 1
    • blue: the blue color component, a value between 0 and 1
    • alpha: the alpha color component, a value between 0 and 1

Example:

let col = app.util.parseColor("#ff8020");
console.log("R: "+col.red+" G: "+col.green+" B: "+col.blue);

renameDuplicateIds()

Renames all duplicate ids in the active document so that they are unique. This method can be called only in an export handler.

trimToTimeRange(start, end)

  • start <number> - the trim start time in milliseconds
  • end <number> - the trim end time in milliseconds

Trims all active document keyframes to the given start and end time range. This method can be called only in an export handler.

KSPathData

KSPathData(pathdata)

  • pathdata <string> - SVG path data.

Creates a path object. Any SVG path can be given as the argument, but it will be converted to a path which contains only “M”, “L”, “C”, and “Z” commands.

Example:

let path = KSPathData("M0,0L10,10C10,20,20,20,30,10Z");

commands - <Array> - Read-only. The path commands as an array. Array items have these properties:

  • command - ‘M’, ‘L’, ‘C’ or ‘Z’ path command
  • x1 - the x1 coordinate (for the ‘C’ command)
  • y1 - the y1 coordinate (for the ‘C’ command)
  • x2 - the x2 coordinate (for the ‘C’ command)
  • y2 - the y2 coordinate (for the ‘C’ command)
  • x - the x coordinate (not for the ‘Z’ command)
  • y - the y coordinate (not for the ‘Z’ command)

getTotalLength()

Returns the total length of the path.

Example:

let path = new KSPathData("M0,0L200,200");
let len = path.getTotalLength(); // returns 282.842...

getPointAtLength(length)

  • length <number>

Returns the point at the given length.

Example:

let path = new KSPathData("M0,0L200,200");
let point = path.getPointAtLength(50); // returns DOMPoint(100, 100)

getAngleAtLength(length)

  • length <number>

Returns the angle in degrees at the given length.

Example:

let path = new KSPathData("M0,0L200,200");
let degrees = path.getAngleAtLength(50); // returns 45

transform(matrix)

  • matrix <DOMMatrix>

Returns a new path, which has been transformed by matrix.

Example:

let path = new KSPathData("M0,0L200,200");
let path2 = path.transform(new DOMMatrix([2, 0, 0, 2, -10, -20]));

toString()

Returns the path as a string.

TextDecoder

TextDecoder([encoding])

  • encoding <string> - Optional. Only “utf-8” is supported.

Constructor to create a new TextDecoder.

encoding <string> - Read-only. The encoding, always “utf-8”.

decode(array)

  • array <Uint8Array>

Returns a string decoded from the given array.

Example:

let td = new TextDecoder();
let str = td.decode(array);

TextEncoder

TextEncoder()

Constructor to create a new TextEncoder.

encoding <string> - Read-only. Returns the encoding, always “utf-8”.

encode(str)

  • str <string>

Returns an Uint8Array, which contains the given string encoded in UTF-8.

Example:

let te = new TextEncoder();
let array = te.encode("hello");

URL

URL(url, [base])

  • url <string> - A string representing an absolute or relative URL. If url is a relative URL, base is required. If url is an absolute url, base is ignored.
  • base <string> - Optional. A string representing the base URL. A URL object can be used as the base, because it gets converted to a string.

Note: Only ‘file:’ protocol is supported.

Examples:

let a = new URL("file:///Users/jdoe/hello.txt");
let b = new URL("bye.txt", a);    // Creates a URL pointing to 'file:///Users/jdoe/bye.txt'

url.href <string> - Gets and sets the URL.

url.pathname <string> - returns the path portion of the URL. Note that this doesn’t include the hash or search portions and should not be used as a convenience function to get the path of a file URL.

url.hash <string> - returns the string after the ‘#’ character. Setting is not supported.

url.host <string> - returns blank, setting is ignored.

url.hostname <string> - returns blank, setting is ignored.

url.origin <string> - returns null.

url.password <string> - returns blank, setting is ignored.

url.port <string> - returns blank, setting is ignored.

url.protocol <string> - returns “file:”, only setting to “file:” is supported.

url.search <string> - returns the string after the ‘?’ character. Setting is not supported.

url.username <string> - returns blank, setting is ignored.

DOMPoint

The same as DOMPoint.

DOMMatrix

The same as DOMMatrix without the is2D and isIdentity properties and any methods.