The digital graphics team often creates artwork for readers to better visualise and understand its reports. As readers consume ST content on different devices – mobile phone, tablet and desktop – the team has to tailor the size of its visuals to suit various reading experiences.

Visuals are adjusted for viewing on different screen sizes

This means that even minor changes can become complex and difficult to track, as designers have to make the edits asked for by journalists and editors, and apply these edits across all three formats.

To ensure that visual changes can be carried out effectively and accurately, the team created an Adobe Illustrator extension using a CEP.

The key features of the ST extension include:

  • Manual binding: Users can manually bind text frames to variables, making it easier to manage text updates.
  • Automated variable generation: By renaming text frames to match the values in a Google spreadsheet, users can automatically generate variables and edit the text frames with data fetched from the spreadsheet.

Choosing the right platform

Currently, Adobe Illustrator does not support the Unified Extensibility Platform. Therefore, a decision was made to develop Sheet2AI using Adobe’s CEP.

What is CEP?

It is a JavaScript-based architecture that enhances extensibility for Creative Cloud applications. CEP leverages Hypertext Markup Language (HTML), Cascading Style Sheets (CSS) and JavaScript, along with ExtendScript, to enable robust and flexible extension development.

Communication between host and CEP

Understanding CEP

Developing a CEP extension involves working with two primary layers – the CEP layer and the JSX/ExtendScript layer. These two layers will pass objects and variables to each other using callback functions.

  • CEP layer: This layer manages all JavaScript and user interface operations by utilising the Chromium Embedded Framework. Front-end operations are handled with standard web technologies (HTML, CSS and JavaScript), while Node.js powers the back end within the same runtime. This ensures both modernity and adaptability. However, despite its versatility, communication with the host application remains dependent on ExtendScript.
  • JSX/ExtendScript layer: This layer handles operations in the Creative Cloud app. For the extension to interact with Adobe Illustrator, commands and information are passed through the ExtendScript layer to the host application. ExtendScript is based on ECMAScript 3 or ES3, which is similar to JavaScript but with certain limitations and custom implementations by Adobe. Consequently, it does not support the latest ES6 features.

It is important to note that each version of the Creative Cloud application supports a different version of CEP. Developers must be mindful of the CEP version they are targeting, referring to resources such as the CEP Cookbook for guidance.

Getting started with Sheet2AI extension development

To create the Sheet2AI extension, the first step is to implement the base. This can be done by following the official Adobe guide. For comprehensive step-by-step instructions, refer to the Getting Started guide in GitHub.

Structure

The folder structure for this extension is organised according to the recommendations provided in the guide.

An important component to note is the CSInterface.js file, which can be downloaded from the CEP-Resources.

This library is crucial for enabling users to control the extension and communicate with the Adobe application. Developers can include the CSInterface.js library anywhere within the code base, as long as it is added as a <script> dependency in the index.html file.

Configuring the extension in manifest.xml

Configurations are set up according to the guide, with attention to the following XML elements and attributes:

  • ExtensionBundleID: A unique bundle ID assigned to the extension like com.my.test
  • XExtension ID: A unique ID you assign to your extension. It usually follows this syntax: ExtensionBundleID + .panel = com.my.test.panel (note that this ID appears twice in the manifest)
  • Host name and version: List of host application IDs and versions that your extension is built to support. To see the list, take a look at the Adobe CEP HTML Extension Cookbook
  • Because this extension is coded for Adobe Illustrator, the host ID will be set to ILST
  • MainPath: Path to your index.html. Make sure the path to this file is from the top-level directory of your code base
  • ScriptPath: Path to your index.jsx. Make sure the path to this file is from the top-level directory of your code base
  • Menu: Your extension name that will appear in the drop-down menu of the host app
  • Size: Default size of your extension
<?xml version='1.0' encoding='UTF-8'?>
<!-- 1) -->
<ExtensionManifest ExtensionBundleId="com.my.test" ExtensionBundleVersion="1.0.0" 
Version="7.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ExtensionList>
    <!-- 2) -->
    <Extension Id="com.my.test.panel" Version="1.0.0" />
  </ExtensionList>
  <ExecutionEnvironment>
    <HostList>
      <!-- 3) -->
      <Host Name="ILST" Version="25.3" />
    </HostList>
    <LocaleList>
      <Locale Code="All" />
    </LocaleList>
    <RequiredRuntimeList>
      <RequiredRuntime Name="CSXS" Version="7.0" />
    </RequiredRuntimeList>
  </ExecutionEnvironment>
  <DispatchInfoList>
    <!-- 2) -->
    <Extension Id="com.my.test.panel">
      <DispatchInfo>
        <Resources>
          <!-- 4) -->
          <MainPath>./client/index.html</MainPath>
          <!-- 5) -->
          <ScriptPath>./host/index.jsx</ScriptPath>
          <CEFCommandLine />
        </Resources>
        <Lifecycle>
          <AutoVisible>true</AutoVisible>
        </Lifecycle>
        <UI>
          <Type>Panel</Type>
          <!-- 6) -->
          <Menu>Panel Name</Menu>
          <Geometry>
            <Size>
              <!-- 7) -->
              <Height>540</Height>
              <Width>420</Width>
            </Size>
          </Geometry>
          <Icons />
        </UI>
      </DispatchInfo>
    </Extension>
  </DispatchInfoList>
</ExtensionManifest>

Client folder

For the front-end, Adobe’s design system, Spectrum Web Components (SWC), is utilised. This library is imported by adding a script dependency.

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1" />
   <title>Panel Title</title>
<!-- add SWC script dependency here -->
   <script
     id="spectrum-script"
     src="https://jspm.dev/@spectrum-web-components/bundle/elements.js"
       type="module" async>
  </script>
 </head>

 <body>
   <sp-theme theme="spectrum" color="light" scale="medium">
     <sp-dialog size="m">
       <h2 slot="heading">Google Sheets to AI</h2>      
       <sp-button variant="primary" id="btn-generate">Generate Variables</sp-button>
     	...
     </sp-dialog>
 </sp-theme>
 </div>
<!-- add script dependencies here, including CEP's CSInterface.js library -->
   <script src="lib/js/JSX.js"></script>
   <script src="lib/js/CSInterface.js"></script>
   <script src="app.js"></script>
 </body>
</html>

After including the library, developers can start using SWC for building the user interface.

JavaScript (app.js)

An instance of CSInterface should be created here to allow access to methods and properties useful for building the extension. The method evalScript() is used for communication between client-side JavaScript and ExtendScript code. This instance is also where JSON data is fetched from a Google spreadsheet.

One important lesson learnt during development was the proper use of events. CEP extensions and ExtendScript have events that can be intercepted. ExtendScript has numerous events unique to each app, while CEP has a few shared among applications, such as opening, saving, and changing the active document. Some Adobe apps support all events, others only a few, and some none at all. These details are documented in the CEP Cookbook.

Understanding and utilising these events are crucial, especially since one of the requirements for this extension is to save and load previously inputted details.

Host (ExtendScript) folder

Helper functions, such as exampleFunction(), are created in ExtendScript and can be called from the client-side JavaScript using csInterface.evalScript("exampleFunction()").

function createVariables() {
  var doc = app.activeDocument

  try {
    if (doc.textFrames.length <= 0) throw new textFramesNotFoundException 
    var textRefs = doc.textFrames

    for (var i = 0; i < textRefs.length; i++) {
      var textRefName = textRefs[i].name
      
      try {
        // regex checker 
        var regex = /[^A-Za-z0-9\.\-\_:]+/

        if (!textRefName || regex.test(textRefName)) throw new regexException

        // check if text frame is binded to a variable
        if (!textRefs[i].contentVariable) {
          try {
            if (!doc.variables.getByName(textRefName)) throw new variableNotFoundException 
    
            // if variable found, bind object to variable
            var textVar = doc.variables.getByName(textRefName)
            textRefs[i].contentVariable = textVar
          } catch (variableNotFoundException) {
            // if variable of object's name not found, create variable + bind that object to the variable
            var textVar = doc.variables.add()
    
            textVar.kind = VariableKind.TEXTUAL
            textVar.name = textRefName
            textRefs[i].contentVariable = textVar
          }
        }
      } catch (regexException) {
        alert('Text frame name not suitable for variable\nNames cannot contain spaces or special characters, except for period (.), hyphen (-), underscore (_), and colon (:).', 'ERROR', true)
        return
      }
    }

    alert('Successfully generated variables.')

  } catch (textFramesNotFoundException) {
    alert('No text frame found\nCreate a text frame object.', 'ERROR', true)
  }
}

Some useful resources for learning and reference include the ExtendScript GitHub Wiki and the Adobe Illustrator Scripting Documentation, the latter being particularly helpful when developing this extension.

Debugging

Set-up

The set-up can be done by following the CEP debugging guide, which provides detailed instructions for setting up the debugging environment.

Before debugging, a .debug file needs to be created at the top level of the panel’s folder.

Folder structure for client-side debugging

Remote debugging can then be initiated by opening http://localhost:port on a Chromium-based browser. Since CEP is based on Chromium, it is necessary to use Chrome or Chromium; other browsers will not work for this purpose.

Debug mode

Activating debug mode is especially essential for developing and testing extensions. Without this step, unsigned extensions will not function. The following commands enable debug mode:

For Mac

  • Open the terminal and type: defaults write com.adobe.CSXS.8 PlayerDebugMode 1

For Windows

  • Open regedit.
  • Navigate to HKEY_CURRENT_USER/Software/Adobe/CSXS.8.
  • Add a new entry named PlayerDebugMode of type string with the value 1.

Launching the extension

To launch the extension in Adobe Illustrator, open Adobe Illustrator > Window > Extensions > YourPanelName.

Important considerations

Developing with CEP can be challenging due to limited documentation and resources. This often leads to uncertainties about whether an issue stems from the code or the platform itself. While Stack Overflow is commonly used by developers, it may not provide extensive answers about CEP. Instead, consider these valuable resources:

Handling errors effectively is crucial. Implementing try-catch blocks can help manage unexpected behaviours, provide clear alerts that indicate what went wrong, and suggest the steps needed to resolve the issues.