26-Aug-2021. It’s still very early days: I am currently building the Tightener core module in C++, trying to use TDD (Test Driven Development).
I’ve got the basic message passing working, and am about to start on the IPC (InterProcess Communication) to allow nodes to discover and communicate with one another.
Once that is done, I’ll start work on a Node.js language support wrapper node, and an InDesign DOM node.
Only at that point will I be able to deliver a proof-of-concept. We’re nowhere near that point yet. Stay tuned.
UI often comes last
When it comes to automating the Adobe Creative Cloud apps, the first concern I have is: how to interact with the document.
What does the DOM (Document Object Model) look like?
For the work I do, the UI is but an optional wrapper around the core functionality, and many of the tools I develop don’t interact with a user – they’re faceless components running as part of a workflow. They’re often throw-away software.
So, more often than not, the last thing I’ll think about is ‘how to create a UI?’ (User Interface) for the automation components I am building.
Even if a UI is warranted, what good is a UI if there is no functionality to be wrapped?
Many times, the solutions I create will have the most limited UI possible (command line, run from a debugger,…).
There is no point in building a UI for something that will only run once.
UXP: thin on the DOM side, heavy on the UI side
Adobe’s UXP (User Extensibility Platform) is heavy on the user interface end and light or non-existent on the DOM end.
For the kind of work I do, UXP is currently irrelevant.
ExtendScript: on life support
ExtendScript offers us a usable way to access the document DOMs, but ExtendScript is dying.
The available tools are suffering from neglect and one by one, they become unusable.
And then there are the breaking changes.
All too often, I have to urgently fix problems caused by breaking changes by Adobe.
Between a rock and a hard place
I think we will soon be in a situation where the old does not work any more, and the new does not work yet.
That’s called ‘being between a rock and a hard place’.
I’ve been thinking about this, and I think there is a way out for third party developers.
The goal is to provide us with what I think we need (DOM access), and reduce our exposure to breaking changes by Adobe.
This project should help limit the damage caused by breaking changes and give us a little bit more control over timely fixes.
Separation of concerns
There are a number of concerns that I keep in mind.
If you want to use, say, Python to automate an Adobe Creative Cloud app, you’re out of luck.
That does not need to be so.
DOM access and the programming language used are two separate concerns and they should also be separated into independent parts of the automation support infrastructure.
Another issue I often face is with C++ plug-ins and external dependencies. Many apps offer a plug-in architecture.
A recurring issue with those are external dependencies.
InDesign, for example, is written in C++, and has dependencies on a host of external code bodies: Boost, OpenSSL, zlib, icu…
Each time Adobe updates InDesign, my plug-ins need to be recompiled using the correct InDesign SDK for the new version of InDesign.
But when InDesign changes its own internal dependency from, say, Boost 1.65 to Boost 1.72, any plug-ins that rely on Boost need to match that dependency change.
Updating a plugin involves not just compiling against an updated InDesign SDK but also updating these additional dependencies to carefully match any depencency changes in InDesign, and that is a major time-sink.
To make updating plug-ins easier it I now think it’s better to opt for multiple smaller plugins rather than one large plug-in.
When an external dependency changes, this would only affect the plug-ins that actually need it, and the other plug-ins could be updated with much less effort.
Another approach that often works for me is to try hard to not have any external dependencies, and re-implement the needed functionality.
Rather than pulling in a massive external framework and then only use 1% of what it can do, I’d rather opt for re-implementing the functionality, and go dependency-free.
Another recurring concern is connectivity. We often need to automate ‘across’ a network wire.
My Current Thinking
Below is my current thinking for the architecture of the project.
The Tightener API
Tightener is the low-level ‘glue’ between the many possible components.
It is like Adobe Bridge (the protocol, not the application), but open source.
Tightener borrows ideas from Adobe Bridge and AppleEvents.
It allows message passing between the various components.
Each component that has the Tightener code embedded is a ‘node’.
Tightener is written in plain C++ 11, and has no external dependencies. It can be compiled into standalone apps and into plug-ins.
On a single workstation, we would have a number of active Tightener nodes.
There would be a single central Tightener node – the switchboard which ties all other nodes together.
Other nodes on the workstation communicate with the central node by way of IPC (InterProcess Communication)/named pipes.
Tightener would have a method of discovery and registration of satellite nodes with the switchboard node.
There would be a satellite node for each of the supported apps, by way of plug-ins with Tightener compiled-in.
There would be an InDesign node, an Illustrator node, a QuarkXPress node,… and just like Adobe Bridge, we would be able to pass messages between those nodes by way of the switchboard node.
These app nodes, which are mostly embedded in plug-ins, would then interact with the app to provide a DOM to the outside world.
If there is a need for external communication, we would have a Tightener satellite node that implements external communication.
Tightener itself would not need to ‘know’ how to communicate over a network.
For language support, there would be a Tightener node for each language we want to support – e.g. a Tightener node in Python, a Tightener node in Node.js,…
Scripts run in whatever language we desire, and use ‘their’ Tightener node to communicate with the app nodes.
Separate from that, I would start a second open source project. It would be a Tightener-based InDesign DOM project.
I suspect I’ll be able to graft this onto the infrastructure used for ExtendScript – the InDesign DOM is not tied to just ExtendScript, it is also used for IDML, AppleScript and VBScript.
I suspect it might be feasible to write another layer for Tightener, and have Tightener living in the same realm as ExtendScript, IDML and AppleScript.
It would offer an alternate to the ExtendScript DOM, and it would also be a re-implementation of the features of APID ToolAssistant (aka Active Page Items), an InDesign plug-in that I wrote long time ago, in the InDesign CS days.
Node.js Tightener Wrapper
For language support, I’d start with a Node.js Tightener node.
This would also form the basis of a node for external communications – we can implement such node using Node.js (or whatever language we choose).
Illustrator and others apps
Once that works, I’d look into getting the same for Illustrator, taking inspiration from the features I had in my AIntrospector plug-in.
Other apps can follow – as the project will be open source, other people could pick up building Tightener-based plug-ins for other apps (e.g. QuarkXPress, Adobe Premiere, Scribus, ImageMagick,…).
Active Page Items
Active Page Items is old and crusty and weird, but even after all these years it offers automation features that InDesign ought to provide, but does not.
For example: creating adornments on page items, or events that fire when individual page items are modified, compiling scripts into encrypted gobbledygook, and handle licensing and demo versions of compiled scripts to help monetize scripts.
Active Page Items has a number of problems that make maintaining it cumbersome.
Each time Adobe releases a new InDesign version, getting everything to compile and link is not fun.
One problem is that it is a monolithic plug-in, like a Swiss Army knife. If one thing breaks, everything is broken.
Another problem is that it relies on third party libraries like OpenSSL, urdl, boost, zlib.
As various dependencies change over time, getting it all to work together is not straightforward.
Tightener API implemented in C++ first
The first implementation of the Tightener API would be written in C++ so it can be compiled ‘into’ various plug-ins as well as in stand-alone applications.
If for some reason C++ cannot be linked into a target environment, the Tightener API can be re-implemented in any other sufficiently powerful computer language.
The Tightener C++ API can be compiled into various plug-ins.
Each of these plug-ins would provide access to a small part of the host app’s DOM.
Rather than have a single plug-in do ‘the whole DOM’, I feel it would be better to have many plug-ins, each doing ‘a bit of the DOM’.
That way, the task of creating these plug-ins can be divvied out and shared amongst multiple contributors.
Some plug-ins could tie into the existing scripting architecture (e.g. InDesign’s) so it should be easy (relatively speaking) to mirror what is available in InDesign ExtendScript.
- Provide a dependable way for scripters to access one or more DOMs inside of the Creative Cloud (and other) apps.
- Reduce our exposure to breaking changes made by Adobe.
- Open Source: this needs to be done fairly quickly, and trying to do it as a one-man band will most probably fail. By making it open source I might be able to rile up enough manpower to get this done before Adobe kills us all.
- Separation of concerns: avoid working on stuff that can be done another way, outside of Tightener. Essentially, all Tightener would do is provide an IPC socket into an app-to-be-automated.
- Tightener would provide just ONE low-level API, i.e. just an IPC socket. No other protocols.
- Additional protocols and languages are considered out of scope, but can easily be provided by creating wrappers around Tightener. These wrappers talk to Tightener via IPC, and provide additional ways to interact with the app-to-be-automated
- – WebSocket wrapper
– HTTPS wrapper
– Python wrapper
– Xojo wrapper
- The Tightener API defines an asynchronous message protocol, somewhat similar to how AppleEvents work on Mac OS X.
- Multiple Tightener APIs need to interact with one another – e.g. there could be a single ‘main’ Tightener API running on a computer as a standalone application. This core monitor would then interact with a collection of Tightener APIs running in multiple plug-ins in the host apps being automated.
The scripter’s scripts would interact with the ‘main’ Tightener API which would then act as a go-between to the in-plugin Tightener APIs.
- The Tightener API C++ implementation should be reliant as little as possible on third party libraries. This to avoid version conflicts. E.g. when Adobe decides to change the Boost version number or the ICU version number used in InDesign, that should not affect a plug-in which uses the Tightener code.
Asynchronous Message Passing
Through the Tightener API, you can send messages (‘Commands’) with a data payload to some targets.
Targets can be anything in the collected DOMs: an app, a document, a box on a page, a character style, some construct specific to the app being automated, some feature in some plug-in,…
The message will be processed, and some time later, a reply will be returned.
Targets can be defined by using descriptors.
Descriptors can be either specific or non-specific.
Specific: ‘page item with ID 123 in document ‘/Users/kris/Documents/bla.indd’
Non-specific: queries into one or more apps and/or document – e.g. ‘all frames with a blue background in all of the currently open documents’. They could even span multiple apps, e.g. ‘quit all open Adobe apps’.