How to start programming in Adobe Illustrator. Part two

  • Tutorial

This post is a continuation of the first part , where the Expand Clipping Mask script was presented and described in detail what and how it does, and along the way the basic principles of creating such programs as a whole were considered. In this part, I will continue the story of how to add new functionality to the program in order to get a “finished product” from the “workpiece”. Here you can not do without a deeper immersion in the subject area, which is one of the necessary conditions for creating a full-fledged product. So, start the dive!


The following graphic primitives can be used as a mask outline in Adobe Illustrator: a simple outline (Path), a composite outline (Compound Path), a composite shape (Compound Shape), and text objects (Point Text and Text on the Path). At the moment, the script works only with simple contours, as can be seen from the code below, where PathItemis a call to the Path element.


      var clipGroup = sel[0].pageItems.length;
      for (var i = 0; i < clipGroup; i++) {
        if (sel[0].pageItems[i].typename == 'PathItem' &&
            sel[0].pageItems[i].clipping == true) {
          clipPath = sel[0].pageItems[i];
          break;
        };
      };

Before that, we declared a variable clipPath, but did not assign a value to it.


      var clipPath;

This means that its value has not yet been determined, i.e. it is undefined. If we now select a mask whose outline will be, say, the Compound Path and run the script, the program will produce an error on the last line of the functional part of the script,


      clipPath.remove();

since the condition in the loop will not be fulfilled, the variable clipPathwill remain so undefined, and remove()it is impossible to apply the method to something undefined. In order to prevent such a situation, we will do the following - we will assign a clipPathvalue nullthat, unlike undefined, is already something more definite that you can at least check.


Let's think about how to determine if some Compound Path is the outline of our mask. When I say, “let's think,” it means that I suggest looking into the documentation and finding the property we need. By analogy with PathItemwe are looking for a property clipping. It turns out that the object CompoundPathItemdoes not have such a property, but there is a property pathItemsthrough which you can get to simple contours PathItemthat have the property clipping.

Now we can turn our thoughts / searches into code. First of all, we check that clipPathit was not defined in the previous iteration, and then we copy the already written block of code and make small changes to it.


      if (clipPath == null) {
        var clipGroup = sel[0].pageItems.length;
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'CompoundPathItem' &&
              sel[0].pageItems[i].pathItems[0].clipping == true) {
            clipPath = sel[0].pageItems[i];
            break;
          };
        };
      };

Actually, the changes will affect only one line. As we can see here, 'PathItem' has changed to 'CompoundPathItem', and a new construction has been added, 'pathItems [0]', with which we refer to the compound path element.


if (sel[0].pageItems[i].typename == 'CompoundPathItem' && sel[0].pageItems[i].pathItems[0].clipping == true) {

The following is the functional block of code that has been created so far.


      var clipGroup = sel[0].pageItems.length;
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'PathItem' &&
              sel[0].pageItems[i].clipping == true) {
            clipPath = sel[0].pageItems[i];
            break;
          };
        };
      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'CompoundPathItem' &&
              sel[0].pageItems[i].pathItems[0].clipping == true) {
            clipPath = sel[0].pageItems[i];
            break;
          };
        };
      };

The next “patient” is the Compound Shape. Here it becomes quite interesting! In the documentation we do not find such an object at all. What to do? First, let's define which class of objects it belongs to. To find out, we will write a small auxiliary code, which we then throw out. As mentioned in the first part , we do not address the issue of the tools used for writing / debugging code. Therefore, suppose this is a separate file, which then simply goes to the trash. The code will be as follows:


var obj = app.activeDocument.selection[0];
alert(obj.typename);

In the first line, we create a link to the selected object, in the second - we display a message about what type it is. Select the outline of the mask in Adobe Illustrator, i.e. the same Compound Shape object and run the script. In the message box, we see that the Compound Shape is a PluginItem. We get rid of the auxiliary code, return to the documentation again, but we don’t find the clipping property or pathItems in PluginItem. In general, nothing that would help us to unequivocally indicate that this object is the outline of the mask. From the script you can’t even determine what kind of plugin it is. Some external module and that's it!


That's an ambush! - you exclaim in your hearts. And the brain is working feverishly, hoping to solve an unsolvable problem. And then, after going through all the possible and impossible options, you desperately click Deland delete the hated Compound Shape. And then out of the corner of your eye on the palette Layersyou notice that after this action, the mask container, which was Clip Group, became simple Group. What could it mean? And the fact that the property clippedof the mask object truehas become false. Here it is, a solution that might work! Of course, this is, by and large, a hack, but what difference does it make if it helps to determine the desired circuit.

The algorithm for determining the mask contour represented by the Compound Shape object will then be as follows: we loop through all the mask objects and when we find PluginItem, we delete it and check whether the property of clippedthe mask container has changed . If it has become false, then this is our circuit. The only thing for this hack to work is to update the DOM Illustrator after deleting the object, which can be done using the method app.redraw(). Then you still need to remember to return the remote object, which is done by the method app.undo().


Below is the code for the Compound Shape path:


      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'PluginItem') {
            sel[0].pageItems[i].remove();
            app.redraw();
            if (sel[0].clipped == false) {
              app.undo();
              clipPath = sel[0].pageItems[i];
              break;
            }
            else {
              app.undo();
            }
          };
        };
      };

Now, of all the possible options for the type of objects that can be the outline of the mask, only text remains (or TextFrameItem, in the terminology of illustrator scripting references ). We turn to the documentation again and again we do not find the properties there clipping. But this time, we no longer worry so much about this and calmly find out that TextFrameItemthere is a property kindthat determines the type of text object (TextType) We find out that there can be three types: AREATEXT, POINTTEXT and PATHTEXT. The first type is not interesting to us, since it cannot be used as a mask outline, and the other two are still as interesting. It remains only to find a hack that will help us determine now not the outline, but the text object, which is the outline of the mask. And that hack will be the team, Convert To Area Type, which will convert POINTTEXT to AREATEXT. As with Compound Shape, this implicitly changes the property clipped.


Accordingly, the code for a TextFrameItem of type POINTTEXT will be as follows:


      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'TextFrame' &&
              sel[0].pageItems[i].kind == 'TextType.POINTTEXT') {
            sel[0].pageItems[i].convertPointObjectToAreaObject();
            app.redraw();
            if (sel[0].clipped == false) {
              app.undo();
              clipPath = sel[0].pageItems[i];
              break;
            }
            else {
              app.undo();
            }
          };
        };
      };

Only TextFrameItemtype PATHTEXT left. Unfortunately, when converting PATHTEXT to AREATEXT, the property clippeddoes not change. But since this is the last possible candidate for the title "mask outline", it is possible to use just such his behavior. That is, we verify that after executing the command, the Convert To Area Typeproperty clippedremains true. Below is the code for a TextFrameItem of type PATHTEXT.


      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'TextFrame' &&
              sel[0].pageItems[i].kind == 'TextType.PATHTEXT') {
            sel[0].pageItems[i].convertPointObjectToAreaObject();
            app.redraw();
            if (sel[0].clipped == true) {
             clipPath = sel[0].pageItems[i];
             break;
            }
            else {
              app.undo();
            }
          };
        };
      };

Thus, if we put together sequentially written pieces of code, including a block of checks, we will get such code, the execution of which, as stated in the first part of the post, will implement the action of the new Expand Clipping Mask command in Adobe Illustrator.


#target illustrator
if (app.documents.length > 0) {
  var doc = app.activeDocument;
  var sel = doc.selection;
  var clipPath = null;
  if (sel.length > 0) {
    if (sel[0].typename == 'GroupItem' && sel[0].clipped == true) {
      var clipGroup = sel[0].pageItems.length;
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'PathItem' &&
              sel[0].pageItems[i].clipping == true) {
            clipPath = sel[0].pageItems[i];
            break;
          };
        };
      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'CompoundPathItem' &&
              sel[0].pageItems[i].pathItems[0].clipping == true) {
            clipPath = sel[0].pageItems[i];
            break;
          };
        };
      };
      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'PluginItem') {
            sel[0].pageItems[i].remove();
            app.redraw();
            if (sel[0].clipped == false) {
              app.undo();
              clipPath = sel[0].pageItems[i];
              break;
            }
            else {
              app.undo();
            }
          };
        };
      };
      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'TextFrame' &&
              sel[0].pageItems[i].kind == 'TextType.POINTTEXT') {
            sel[0].pageItems[i].convertPointObjectToAreaObject();
            app.redraw();
            if (sel[0].clipped == false) {
              app.undo();
              clipPath = sel[0].pageItems[i];
              break;
            }
            else {
              app.undo();
            }
          };
        };
      };
      if (clipPath == null) {
        for (var i = 0; i < clipGroup; i++) {
          if (sel[0].pageItems[i].typename == 'TextFrame' &&
              sel[0].pageItems[i].kind == 'TextType.PATHTEXT') {
            sel[0].pageItems[i].convertPointObjectToAreaObject();
            app.redraw();
            if (sel[0].clipped == true) {
              clipPath = sel[0].pageItems[i];
              break;
            }
            else {
              app.undo();
            }
          };
        };
      };
      app.executeMenuCommand('releaseMask');
      clipPath.remove();
    }
    else {
      alert ('Выделение не является объектом-маской!');
    };
  }
  else {
    alert ('Нет выделенных объектов!');
  };
}
else {
  alert ('Нет открытых документов!');
};

Here you can put an end to. No, a semicolon is better.


I hope that with these posts I helped you get a little closer to your goal - to start programming in Adobe Illustrator. Thank you for attention!


Also popular now: