AIntrospector Documentation

AIntrospector 1.0.11 – 30 May 2018

This project has multiple goals.

Short Term

The short term goal is to provide ExtendScript access to Illustrator data that is currently inaccessible to ExtendScript scripts.

There is a discrepancy between what can be accessed using the C++ API and what can be accessed from ExtendScript.

This plug-in attempts to bridge that gap.

The plug-in currently provides an additional DOM (Document Object Model), unrelated to the ExtendScript DOM.

The AIntrospector DOM is more low-level than the ExtendScript DOM.

This alternative DOM can be used from ExtendScript alongside the ExtendScript DOM and offers access to areas that are inaccessible to the ExtendScript DOM.

At this time, the AIntrospector DOM is still very sparse: it is populated with new attributes and new features on an ‘as-needed’ basis for some real-life projects. For example, the AIntrospector DOM gives you access to information about ‘type on a path’ that is not available in the ExtendScript DOM (nor in the C++ API).

The current focal areas, where the DOM is being extended, are determined by the needs of current customers who have a solid commitment to licensing AIntrospector.

Low-level DOM: optimizing for asynchronous use

The low-level AIntrospector DOM can be accessed from CEP/JavaScript as well as from ExtendScript.

When accessing the DOM from CEP/JavaScript, there is no ‘intermediate’ use of ExtendScript: JavaScript calls are handled by the plug-in without any use of ExtendScript. This allows the creation of CEP panels that are totally free of any ExtendScript.

PARM/MRAP errors

The hope/assumption is that this can put an end to PARM/MRAP errors which seem to be caused by some ‘disconnect’ between the ExtendScript engine and Illustrator.

When PARM/MRAP happens, Illustrator remains fully responsive and alive to the user, and at the same time, ExtendScript scripts can still be run, but somehow, ExtendScript seems to lose its grip on Illustrator, and anything you try causes a PARM/MRAP error, which can only be cured by restarting Illustrator.

Async DOM

One of the underlying principles is to support asynchronous access. The idea is that there are two realms: one ‘inside’ Illustrator, and one ‘outside’ Illustrator.

The AIntrospector API bridges these two realms.

When calling from ‘outside’ to ‘inside’ there is a substantial cost in time and performance, especially when calling from JavaScript/CEP: we’re switching between two very different contexts.

The AIntrospector DOM is being designed to reduce the number of such context switches as much as possible.

The ‘outside’ code can prepare a request object, which is a bit like a shopping list or ‘to do’ list.

The request object can express complex queries into the Illustrator data structures, and the idea is to shoehorn as much as possible into a single request.

The request is then fired off, the context switches, and the ‘inside’ resolves and execute the request, without needing to call back to the outside during processing.

If you have choice between formulating three small requests, or instead an equivalent single complex requests, you would choose the latter because you will only ‘pay’ for one context switch between ‘outside’ and ‘inside’.

Long Term

Once the low-level DOM nears completion, the mid term goal is to attempt to build a second, higher level DOM on top of the low-level AIntrospector DOM. This new DOM would be as close to the ExtendScript DOM as possible, and I would try and make the asynchronicity as invisible as possible, so existing ExtendScript scripts might be able to run in a JavaScript/CEP environment (as opposed to the ExtendScript engine) against the new DOM.

GPU features

Another aspect of the plug-in is to provide ‘GPU-like’ features. Some tasks are too massive to be handled in ExtendScript or JavaScript, but are feasible in C++. The plug-in will gradually provide functions to handle such requests. I’ve added one already as a proof of concept: simplifyPath.

Focus on CEP/JavaScript

Gradually, the focus will shift from ExtendScript to providing more features to CEP/JavaScript.

API

The API provided by the AIntrospectorUI extension works both in ExtendScript and in JavaScript.

From JavaScript, all calls into AIntrospector are asynchronous.

From ExtendScript, calls can be either synchronous or asynchronous.

To run stuff from ExtendScript-only setups

Make sure to start the script with something like

#include "<some path>/standalone.jsx" 

or equivalent.

This loads and initializes the ExtendScript infrastructure.

XUID

In order to reference objects in Illustrator (page items, colors, documents, artboards…) the plug-in uses XUID (eXtended UID).

For many object types, XUID are just plain GUID.

But some objects in Illustrator do not have the internal data structures to retain their GUID. There is no way for Illustrator to ‘remember’ what the GUID for those objects would be.

In order to handle such objects (e.g. Artboards), the plug-in uses the GUID of the ‘owner’ of the object, suffixed with a short string. Part of this string will be a four-letter code:

ArBd -> Artboard
ArSt -> Art Style
LiEf -> Live Effect
Layr -> Layer

Artboards

For example, if a document has GUID 706c0a02-32fd-4fae-b745-778ba6995328, then its artboards will have the following XUID:

706c0a02-32fd-4fae-b745-778ba6995328ArBd0
706c0a02-32fd-4fae-b745-778ba6995328ArBd1
706c0a02-32fd-4fae-b745-778ba6995328ArBd2
... and so on

For artboards, the owner (document) GUID is suffixed with a type code (ArBd = Artboard) and an index 0..n-1 where n = number of artboards.

By nature, artboards do not have ‘stable’ XUID: if the order of the artboards in a document changes, the XUID associated with any of the artboards will change.

Layers

Any group in an Illustrator document will have its own GUID.

Illustrator layers are always associated with a special group (a layer group), and becaused of that, they don’t have their own GUID.

Instead they have an XUID which is derived from the GUID of the underlying layer group.

So, if we have a layer ‘ABC’, and the associated layer group has a GUID of

1715b7bb-0b79-486f-9cbd-c56846e73400

then the layer will have XUID

1715b7bb-0b79-486f-9cbd-c56846e73400Layr

Live Effects

Live effects are owned by the application, not by a document.

The Illustrator application has a fixed GUID 21862fc3-f16b-42b6-acb3-00165cedd121, and the live effects have an XUID based on their name derived from that.

21862fc3-f16b-42b6-acb3-00165cedd121LiEfLive Effect Name

Art Styles

Art styles are owned by a document.

Art styles have an XUID based on their name derived from the document GUID.

1715b7bb-0b79-486f-9cbd-c56846e73400ArStArt Style Name

Synchronous vs Asynchronous

Asynchronous use of the API is compatible with both JavaScript and ExtendScript.

Asynchronous API call:

AIn.sendAIntrospectorQuery(query, function(result) {...});

Synchronous API call (ExtendScript only):

result = AIn.sendAIntrospectorQuery(query);

Synchronous call with ‘implicit reference’ (ExtendScript only):

In ExtendScript you can omit the reference attribute from a query object and instead use

result = AIn.sendPageItemIntrospectorQuery(pageItem, query);

This allows you to reference objects directly, rather than via the reference mechanism.

Symbolic constants

A lot of Illustrator calls in the C++ API take enumeration constants.

These are numerical (integer) constants that each represent a particular state or condition.

For example, in the C++ API, you’ll find

    /** Type constants for drawing modes. */
    enum AIDrawingMode
    {
        kAIDrawNormalMode = 0,
        kAIDrawBehindMode,
        kAIDrawInsideMode
    };

To keep things readable in C++ code, developers will rather use ‘kAIDrawNormalMode’ instead of a ‘0’. These two things are equivalent as far as the C++ compiler is concerned, but writing kAIDrawNormalMode makes it much easier to understand the code for the human reader of the C++ code.

The AIntrospector plug-in uses a similar mechanism, and will allow symbolic strings for nearly all parameters for API calls that need such integer values.

Any call to AIntrospector that has a JSON construct with a drawing mode parameter will accept either an integer value, as well as a string value.

You can either use

    {
    ...
      drawingMode: 1
    ...
    }

or

    {
    ...
      drawingMode: "kAIDrawBehindMode"
    ...
    }

and both will work the same.

It will also return these symbolic strings rather than the ‘naked’ integer values, so the returned data from a ‘getinfo’ becomes is easier to understand.

Example:

    {
      "uid": "460cec74-8915-477b-8baa-bc527d0cc094",
      "name": "my group",
      "type": "art",
      "artType": "kTextFrameArt",
      "artBounds:top": "-293.2734375",
      "filePath": "",
      "pathObject": "3c336f5b-ccf4-4d20-9f6a-389a3af3d30f",
      "typeOnPath": {
        "typeOnPathEffect": "kTypeOnPathRainbow",
        "typeOnPathFlipped": "1"
      }
    }

artType is returned as ‘kTextFrameArt’, which is equivalent with the integer constant ’12’. Seeing ‘kTextFrameArt’ is a lot easier to understand than seeing ’12’.

Reference

To refer to an object in Illustrator you can use a reference object.

There are four types of references: symbolic, uid, indexed and relative

A single reference object can refer to multiple Illustrator objects (e.g. referring to the current selection)

Symbolic

To refer to the current document:

    {
        type: "symbolic",
        symbolType: "curDoc"
    }

To refer to the current selection (which might refer to multiple elements):

    {
        type: "symbolic",
        symbolType: "selected"
    }

To refer to the application object (this should always return the pre-determined GUID AIn.C.APPLICATION_UID = “21862fc3-f16b-42b6-acb3-00165cedd121”):

    {
        type: "symbolic",
        symbolType: "application"
    }

uid

To refer to an object by its XUID:

{
    type: "uid",
    uid: "706c0a02-32fd-4fae-b745-778ba6995328"
}

indexed

This is a recursive definition: you need a nested reference object to use the index with.

To refer to an object ‘below’ another object by its index:

    {
        type: "indexed",
        index: anIdx,
        reference: {
            ... a reference object for the parent object ...
        }
    }

relative

This is a recursive definition. you need a nested reference object as the base for the relation.

The available relations are:

ancestor
child
descendant
next
parent
previous

ancestor = recursive parent descendant = recursive child

The ancestor and descendant references track down via parent or child relations.

To refer to an object relative to another object:

    {
        type: "relative",
        relation: "parent",
        reference: {
            ... a reference object for the child object ...
        }
    }


    {
        type: "relative",
        relation: "child",
        reference: {
            ... a reference object for the parent object ...
        }
    }


    {
        type: "relative",
        relation: "next",
        reference: {
            ... a reference object for the sibling object ...
        }
    }


    {
        type: "relative",
        relation: "previous",
        reference: {
            ... a reference object for the sibling object ...
        }
    }


    {
        type: "relative",
        relation: "ancestor",
        includeRoots: true/false, // optional, default = false
        reference: {
            ... a reference object for the descendant object ..
        },
        filter: ... a filter ...,
        visitFilter: ... a filter ...
    }


    {
        type: "relative",
        relation: "descendant",
        includeRoots: true/false, // optional, default = false
        reference: {
            ... a reference object for the ancestor object ...
        },
        filter: ... a filter ...,
        visitFilter: ... a filter ...
    }

filter

The filter determines which elements are to be selected by the reference

visitFilter

The visitFilter determines how to recurse up or down: if an element does not match the visitFilter the recursion will not descend or ascend into it.

includeRoots

If includeRoots is true, then the referenced object(s) themselves are also tested for matching against the filter and can be included in the result.

By default only objects ‘one step removed’ – i.e. children or parents – of the referenced object(s) are tested.

filter objects

A basic filter object will compares attribute values:

    {
        attr1: value1,
        attr2: value2...
    }

This will filter all elements that have attr1 == value1 and attr2 == value2.

Example:

   {
      isArtClipping: "1"
   }

will filter all elements that have isArtClipping = 1.

To change the filter comparison operation from ‘equals’ to something else (<, <=, >, >=), you need to nest the filter into a ‘cmp’ object.

   {
        cmp: "<",
        filter : {
          ... a basic filter object ...
        }
    }

This causes the comparison to compare ‘attr < value’ instead of ‘attr == value’.

Example:

   {
        cmp: "<=",
        filter : {
          artType: "kCompoundPathArt"
        }
    }

will filter all art elements that have an art type <= kCompoundPathArt. kCompoundPathArt is a symbolic representation of the integer value 3.

Filters can be inverted or combined.

To invert a filter:

   {
        invert: true
        filter : {
        ... a filter object ...
        }
    }

To combine two or more filters with ‘and’:

   {
        and: 
        [
             {
               ... filter object 1 ...
             },
             {
               ... filter object 2 ...
             }
             ...
        ]
    }

To combine two or more filters with ‘or’:

   {
        or: 
        [
             {
               ... filter object 1 ...
             },
             {
               ... filter object 2 ...
             }
             ...
        ]
    }

Complex references

It is allowed to have multiple nested reference objects.

For example, this works:

    {
        opcode: "getinfo", 
        attr: [ "uid", "name" ],
        reference: {
            type: "relative", 
            relation: "child",
            reference: {
                type: "relative", 
                relation: "child",
                reference: {
                    type: "uid", 
                    uid: AIn.C.APPLICATION_UID
                }
            }
        }
    }

It will retrieve all the children (e.g. layers, art styles) for all open documents: documents are children of the application, and layers and art styles are children of the documents.

Request

Requests allow us to ‘do’ something (i.e. extract data, or make changes to the document).

Asynchronous calls will always need to use a request that contains a reference object, so the plug-in can know what objects to target with the request.

Synchronous call with ‘implicit reference’ can omit the reference object from the request. They target the object that is provided in the synchronous call.

Each request needs at least an opcode (OPeration CODE).

The following opcodes are currently supported (work in progress):

disposeArt
getinfo
newArt
reorderArt
select
setinfo
simplifyPath

disposeArt

    {
        opcode: "disposeArt",
        reference: {
            .... a reference object ...
        }
    }

This will remove the referenced art from the document.

getinfo

This request retrieves information about one or more objects and returns it.

    {
        opcode: "getinfo",
        alwaysReturnArray: true/false, // optional, defaults to false
        attr: ... a single attr or an array of attr ...
        reference: {
            .... a reference object ...
        }
    }

When alwaysReturnArray is true, the returned value is always an array (which could be empty or have only a single element).

If alwaysReturnArray is false the returned value is either a single item if there is only a single result, or an array if there are multiple results.

There is some support for function-like calls. Instead of just an attribute name, they are specified by 2-element arrays [ <<attr>>, <<param>> ]

Supported function-like attr:

    attr = allArt, param = { artType: ..., whichAttr:..., matchAttr:...}
           each of those either a symbolic string, 
           e.g. 'kArtSelected' or an integer
    attr = artOrder, param = string with artUID to compare with
    attr = artUserAttr, param = whichAttr (either a symbolic string, 
           e.g. 'kArtSelected' or an integer)
    attr = pathSegments, param = { segNum: ..., segCount: .... }. 
           pathSegments can also be used as a normal attribute
    attr = pathLength,  param = AIReal flatness
    attr = validArt, param = bool searchAllLayerLists

To call these, you need to provide a two-element array: first element is the attr name, second element is a JSON-encoded parameter construct.

attr can currently be one or more selected from (work in progress):

    artBounds
    artBoundsOptions
    artDrawingMode
    artHasFill
    artHasStroke
    artIsGraph
    artStrokeParams
    artStyle
    artStyleName
    artStyleScaleFactor
    artSubType
    artTargettingAction
    artTimeStampOptions
    artTransferAttrsOptions
    artType
    artXMP
    ascent
    autoKernType
    autoLeading
    baseLineShift
    characterRotation
    childCount
    contents
    descent
    dictionary
    distanceToBaseLine
    entryType
    extractedText
    filePath
    font
    fontBaselineOption
    fontCapsOption
    fontSize
    frameIndex
    glyphOrientation
    hasNote
    horizontalScale
    isArtClipping
    isArtStyledArt
    isEnvelope
    layerGroup
    leading
    lineOrientation
    matrix
    maxCapHeight
    minCapHeight
    name
    next
    note
    onPathTextTRange
    origins
    paintOrder
    parent
    parsedStyle
    partOfLinkedText
    pathArea
    pathClosed
    pathGuide
    pathHasLength
    pathIsClip
    pathObject
    pathPolarity
    pathSegmentCount
    pathSegments (can also be used as a function)
    placedType
    pointTextAnchor
    prepArt
    previous
    spaceGlyphWidth
    storyIndex
    styledArt
    text
    textAntialias
    textFrameColumnCount
    textFrameColumnGutter
    textFrameMatrix
    textFrameOpticalAlignment
    textFrameRowCount
    textFrameRowGutter
    textFrameRowMajorOrder
    textFrameSpacing
    textFrameStoryDirection
    textFrameType
    textOrientation
    tracking
    type
    typeOnPath
    typeOnPathEffect
    typeOnPathFlipped
    uid
    underlinePosition
    underlineThickness
    verticalScale

Not all attributes make sense for all object types. If the attribute is not defined, an empty string will be returned.

If the reference resolves to multiple objects, an array of replies will be returned.

The reply object will contain an entry for each requested attr.

Example:

    {
        opcode: "getinfo",
        attr: "uid"
        reference: {
            type: "symbolic",
            symbolType: "root"
        }
    }

will return a single object

    {
        uid: "706c0a02-32fd-4fae-b745-778ba6995328"
    }

single uid, the uid of the document.

Example:

    {
        opcode: "getinfo",
        attr: "name"
        reference: {
            type: "uid",
            uid: ["706c0a02-32fd-4fae-b745-778ba6995328", 
                  "807c0a02-32fd-4fae-b745-778ba6995439"]
        }
    }

will return an array of two objects, the names of these objects:

    [
        {
            name: "name 1"
        },
        {
            name: "name 2"
        }
    ]

Example:

    {
        opcode: "getinfo",
        attr: [ "name", "childCount" ]
        reference: {
            type: "uid",
            uid: ["706c0a02-32fd-4fae-b745-778ba6995328", 
                  "807c0a02-32fd-4fae-b745-778ba6995439"]
        }
    }

will return an array of two objects:

    [
        {
            name: "name 1",
            childCount: 5
        },
        {
            name: "name 2"
            childCount: 0
        }
    ]

Example:

    {
        opcode: "getinfo",
        attr:  [ "allArt", { "artType" : "kAnyArt" } ],
        reference: {
            .... a reference to a document ...
        }
    }

will call allArt({ “artType” : “kAnyArt” }) and return all UIDS of all art in the referenced document.

newArt

    {
        opcode: "newArt",
        params: {
          artType: ... art type...,
          paintOrder: ... paint order...,
          prepArt: ... a reference to a preparatory art object ...
        }
    }

This will create a new, empty art object.

It will execute the C++ AIArtSuite call:

    AIAPI AIErr(*NewArt)(ai::int16 type, ai::int16 paintOrder, 
        AIArtHandle prep, AIArtHandle *newArt)

reorderArt

    {
        opcode: "reorderArt",
        reference: {
           ... an art object...
        }
        params: {
          paintOrder: ... paint order...,
          prepArt: ... a reference to a preparatory art object ...
        }
    }

This will reorder the targeted art relative to the prep art.

It will execute the C++ AIArtSuite call:

    AIAPI AIErr (*ReorderArt) (AIArtHandle thisArt, ai::int16 paintOrder, 
      AIArtHandle prep)

select

Will attempt to select the referenced items

setinfo

This request will change some attributes or related data on the referenced objects.

simplifyPath

Will attempt to simplify the path. Smoothing uses a sliding average of smoothAveragePointCount to average out points along the path as long as they are no more than smoothToleranceInPoints apart.

If a point between two other points is less than toleranceInPoints away from the other two, it will be removed.

If a compound path subsegment has less than minSegmentCount segments, it will be deleted

  {
      opcode: "simplifyPath",
      smoothToleranceInPoints: 10,
      smoothAveragePointCount: 10,
      toleranceInPoints: 0.4,
      minSegmentCount: 4,
      reference: ...
  }

This seems to work well on bitmaps that have been converted to paths and zig-zag all over the place. I’ve been able to reduce a sample compound path from > 200,000 path segments down to a few 10,000 while retaining something visually very close to the original. It also helps in making a path more manageable for manual editing.