• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
3

Made a two-way AIA to JSON converter

Enthusiast ,
Apr 23, 2023 Apr 23, 2023

Copy link to clipboard

Copied

I've always disliked working with Actions in Illustrator scripting and it's always bothered me that even though many things we need in scripting are achievable through actions, the scripting API's use of them is extremely basic and not fleshed out. We only really have 3 interactions: whether to load, unload, or execute obscurely formatted text. A while back I began brainstorming a new API for them but was too frustrated I was only 99% done and couldn't perfect it in order to share.

 

A typical example of AIA text:

 

/version 3
/name [ 12
	5465737420416374696f6e73
]
/isOpen 1
/actionCount 3
/action-1 {
	/name [ 22
		4f706163697479203630202873656c656374696f6e29
	]
	/keyIndex 0
	/colorIndex 7
	/isOpen 0
	/eventCount 1
	/event-1 {
		/useRulersIn1stQuadrant 1
		/internalName (ai_plugin_transparency)
		/localizedName [ 12
			5472616e73706172656e6379
		]
		/isOpen 0
		/isOn 1
		/hasDialog 0
		/parameterCount 1
		/parameter-1 {
			/key 1869635939
			/showInPalette 1
			/type (unit real)
			/value 60.0000038147
			/unit 592474723
		}
	}
}
/action-2 {
	/name [ 11
		41646420416e63686f7273
	]
	/keyIndex 53
	/colorIndex 0
	/isOpen 0
	/eventCount 1
	/event-1 {
		/useRulersIn1stQuadrant 0
		/internalName (ai_plugin_AddAnchorPoint)
		/localizedName [ 17
			41646420416e63686f7220506f696e7473
		]
		/isOpen 0
		/isOn 1
		/hasDialog 0
		/parameterCount 0
	}
}
/action-3 {
	/name [ 8
		5365742046696c6c
	]
	/keyIndex 0
	/colorIndex 0
	/isOpen 0
	/eventCount 1
	/event-1 {
		/useRulersIn1stQuadrant 0
		/internalName (ai_plugin_setColor)
		/localizedName [ 9
			53657420636f6c6f72
		]
		/isOpen 1
		/isOn 1
		/hasDialog 0
		/parameterCount 6
		/parameter-1 {
			/key 1768186740
			/showInPalette -1
			/type (ustring)
			/value [ 10
				46696c6c20636f6c6f72
			]
		}
		/parameter-2 {
			/key 1718185068
			/showInPalette -1
			/type (boolean)
			/value 1
		}
		/parameter-3 {
			/key 1954115685
			/showInPalette -1
			/type (enumerated)
			/name [ 9
				52474220636f6c6f72
			]
			/value 2
		}
		/parameter-4 {
			/key 1919247406
			/showInPalette -1
			/type (real)
			/value 221.0
		}
		/parameter-5 {
			/key 1735550318
			/showInPalette -1
			/type (real)
			/value 0.0
		}
		/parameter-6 {
			/key 1651275109
			/showInPalette -1
			/type (real)
			/value 0.0
		}
	}
}

 

When ran with the ActionSuite.js:

 

#include "../ActionSuite.js";
var testActions = new ActionSet(new File("~/desktop/sample.aia"))
var actionsAsJSON = testActions.toJSON();

 

Becomes:

 

{
  "version": 3,
  "name": "Test Actions",
  "isOpen": 1,
  "actionCount": 3,
  "actions": [
    {
      "name": "Opacity 60 (selection)",
      "keyIndex": 0,
      "colorIndex": 7,
      "isOpen": 0,
      "eventCount": 1,
      "events": [
        {
          "useRulersIn1stQuadrant": 1,
          "internalName": "(ai_plugin_transparency)",
          "localizedName": "Transparency",
          "isOpen": 0,
          "isOn": 1,
          "hasDialog": 0,
          "parameterCount": 1,
          "parameters": [
            {
              "key": "opac",
              "showInPalette": 1,
              "type": "(unit real)",
              "value": 60.0000038147,
              "unit": 592474723
            }
          ]
        }
      ]
    },
    {
      "name": "Add Anchors",
      "keyIndex": 53,
      "colorIndex": 0,
      "isOpen": 0,
      "eventCount": 1,
      "events": [
        {
          "useRulersIn1stQuadrant": 0,
          "internalName": "(ai_plugin_AddAnchorPoint)",
          "localizedName": "Add Anchor Points",
          "isOpen": 0,
          "isOn": 1,
          "hasDialog": 0,
          "parameterCount": 0
        }
      ]
    },
    {
      "name": "Set Fill",
      "keyIndex": 0,
      "colorIndex": 0,
      "isOpen": 0,
      "eventCount": 1,
      "events": [
        {
          "useRulersIn1stQuadrant": 0,
          "internalName": "(ai_plugin_setColor)",
          "localizedName": "Set color",
          "isOpen": 1,
          "isOn": 1,
          "hasDialog": 0,
          "parameterCount": 6,
          "parameters": [
            {
              "key": "idct",
              "showInPalette": -1,
              "type": "(ustring)",
              "value": "Fill color"
            },
            {
              "key": "fill",
              "showInPalette": -1,
              "type": "(boolean)",
              "value": 1
            },
            {
              "key": "type",
              "showInPalette": -1,
              "type": "(enumerated)",
              "name": "RGB color",
              "value": 2
            },
            {
              "key": "red.",
              "showInPalette": -1,
              "type": "(real)",
              "value": 221
            },
            {
              "key": "gren",
              "showInPalette": -1,
              "type": "(real)",
              "value": 0
            },
            { "key": "blue", "showInPalette": -1, "type": "(real)", "value": 0 }
          ]
        }
      ]
    }
  ]
}

 

Completely decoded and human-readable. The goal here was to allow scripting to create and run dynamic actions on the fly, and to be able to further understand parameters of Actions that seemed too obscure to fit to our needs while giving it a more familiar interface modeled on the already existing API of scripting:

 

// We should be able to load actions easily from a given file:
var mySet = new ActionSet(new File(Folder.desktop + "/someActionSet.aia"));

// And then just run one instantly:
mySet.actions.run("export300dpiJPEG");

// If we want to load them and save this set for the user:
mySet.load(); // it should be this easy

 

And the ability to create them dynamically on the fly:

 

var myActionSet = new ActionSet("Sample");
var myAction = new Action("recolorit");
myAction.events.add(
  new ActionEvent({
    localizedName: "selectAll",
    internalName: "(adobe_selectAll)",
    hasDialog: 0,
  }),
  new ActionEvent({
    localizedName: "Set color",
    internalName: "(adobe_plugin_setColor)",
    parameters: [
      new ActionParam({ key: "Set color", value: "idct" }),
      new ActionParam({ key: "Fill color", value: "fill" }),
      new ActionParam({ key: "type", value: "RGB color" }),
      new ActionParam({ key: "red.", value: 255 }),
      new ActionParam({ key: "gren", value: 50 }),
      new ActionParam({ key: "blue", value: 0 }),
    ],
  })
);

myActionSet.actions.add(myAction);
myAction.run();

 

While this works in the current version of the script, it still is pretty obscure given some of the redundancy of writing out ActionParams like idct longhand, or needing to know the exact internalName value and required parameters of each in order to construct a valid Action. At the least, the parsing part of this could allow you to create an Action then load it's AIA and convert to JSON to see the parameters needed in order to more easily tweak things yourself, then re-encode it as AIA and save it somewhere:

 

var rawActionString = myActionSet.toAIA();

 

 The source code to this Action API is here. I'm sure it's not perfect since I didn't test literally every possible Action and at the moment, only the non-minified version works as expected since TerserJS seems to mangle key-values into raw unicode when the minified version is used. I'd like to eventually rewrite this in a more performant way -- right now, trying to parse an ActionSet with 10+ actions can freeze the app for 2ish seconds so it's likely not fit for commercial use and including the entire script in with your own to utilize fully, but at the least I thought others might be able to make use of the JSON part.

TOPICS
Scripting

Views

695

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Apr 23, 2023 Apr 23, 2023

Copy link to clipboard

Copied

Hi @Inventsable, I LOVE this! I haven't got time to check it out properly yet—but wanted to add an immediate thank you! for sharing it. I'll let you know how it goes when I get a chance. - Mark

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Apr 23, 2023 Apr 23, 2023

Copy link to clipboard

Copied

Awesome idea! thanks for researching, putting it together and sharing!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Apr 24, 2023 Apr 24, 2023

Copy link to clipboard

Copied

That’s nice. It will be informative.

 

Sorry if I am wrong, but it seems that the multibyte string is garbled. I am converting as follows.

function ustring(str) {
  var tempStr = str.replace(/[0-9A-Za-z!'()*._~-]/g, function(c) {
    return c.charCodeAt(0).toString(16) ;
  }) ;
  tempStr = encodeURIComponent(tempStr).replace(/%/g, '') ;
  var len = tempStr.length / 2 ;
  var res = {source: str, hex: tempStr, length: len} ;
  return res ;
}

function hexToString(hex) {
  var res = decodeURIComponent(hex.replace(/(.{2})/g, '%$1')) ;
  return res ;
} ;

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Apr 24, 2023 Apr 24, 2023

Copy link to clipboard

Copied

I can't replicate that myself but feel free to give a sample dataset. A comparison between the methods you've provided and the ones I'm using:

 

function ustring(str) {
  var tempStr = str.replace(/[0-9A-Za-z!'()*._~-]/g, function (c) {
    return c.charCodeAt(0).toString(16);
  });
  tempStr = encodeURIComponent(tempStr).replace(/%/g, "");
  var len = tempStr.length / 2;
  var res = { source: str, hex: tempStr, length: len };
  return res;
}

function hexToString(hex) {
  var res = decodeURIComponent(hex.replace(/(.{2})/g, "%$1"));
  return res;
}

function hexToAscii(input) {
  for (var i = 0, output = ""; i < input.toString().length; i += 2)
    output += String.fromCharCode(parseInt(input.toString().substr(i, 2), 16));
  return output;
}
function asciiToHex(input) {
  for (var i = 0, output = ""; i < input.toString().length; i++)
    output += input.toString().charCodeAt(i).toString(16);
  return output;
}

const hexTests = [
  "5b44656661756c745d",
  "4d6163696e746f73682048443a",
  "417274204f7074696d697a6564",
];
const resultsA = hexTests.map((i) => hexToString(i));
const resultsB = hexTests.map((i) => hexToAscii(i));

console.log(resultsA); // ['[Default]', 'Macintosh HD:', 'Art Optimized']
console.log(resultsB); // ['[Default]', 'Macintosh HD:', 'Art Optimized']

const asciiTests = ["[Default]", "Macintosh HD:", "Art Optimized"];
const resultsC = asciiTests.map((i) => ustring(i).hex);
const resultsD = asciiTests.map((i) => asciiToHex(i));

console.log(resultsC);
// [
//   "5B44656661756c745D",
//   "4d6163696e746f73682048443A",
//   "417274204f7074696d697a6564",
// ];
console.log(resultsD);
// [
//   "5b44656661756c745d",
//   "4d6163696e746f73682048443a",
//   "417274204f7074696d697a6564",
// ];

 

The only difference I see is that yours is returning more uppercase characters but otherwise seem equal, though I'm also not throwing in many locale specific unicode or raw data. I'm also unsure if you'd used the minified versions of the script which do garble ASCII as noted in the original post and seems a biproduct of using TerserJS to minify it.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Apr 24, 2023 Apr 24, 2023

Copy link to clipboard

Copied

It doesn’t make a difference if alphanumeric characters are used.

For example, "e5889de69c9fe8a8ade5ae9ae382a2e382afe382b7e383a7e383b3" is "初期設定アクション", which is Japanese for default action. When this is converted to a string, a difference occurs.

 

function hexToAscii(input) {
  for (var i = 0, output = ""; i < input.toString().length; i += 2)
    output += String.fromCharCode(parseInt(input.toString().substr(i, 2), 16));
  return output;
}

function hexToString(hex) {
  var res = decodeURIComponent(hex.replace(/(.{2})/g, "%$1"));
  return res;
}

var setName = 'e5889de69c9fe8a8ade5ae9ae382a2e382afe382b7e383a7e383b3';

var hexToAsciiResult = hexToAscii(setName);
alert(hexToAsciiResult);
// --> '初期設定アクション'

var hexToStringResult = hexToString(setName);
alert(hexToStringResult);
// --> '初期設定アクション'

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Apr 24, 2023 Apr 24, 2023

Copy link to clipboard

Copied

This looks awesome! Thanks for sharing!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Apr 25, 2023 Apr 25, 2023

Copy link to clipboard

Copied

LATEST

Thanks.  This will probably come in handy in the future. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines