JSXGetURL 0.0.9 Update

Download link further below…

What is it?

JSXGetURL is meant to work with any Adobe Creative Cloud application that has ExtendScript support – InDesign, InDesign Server, InCopy, Illustrator, Photoshop…

JSXGetURL is implemented as an ExtendScript DLL/framework, written in C/C++.

This DLL allows you to access URLs (including https: or ftp:) straight from ExtendScript.

There is nothing to install – all you need to do is //@include a .jsx file which then loads the DLL.

Sample code:


//@include "JSXGetURL/JSXGetURLLoader.jsx"
var getURL = JSXGetURL();

getURL.addRequestHeader("Accept: */*");
var s = getURL.get("https://www.rorohiko.com") + "";

alert(s.substr(0,1000));

var headers = getURL.getResponseHeaders();
alert(headers);

// Some random FTP file for testing

var f = new File("~/Desktop/sha512.sum");
var s = getURL.get("ftp://cygwin.com/pub/gcc/sha512.sum", f.fsName);

// Some zip file to test binary file download

var f = new File("~/Desktop/FrameReporter.1.1.8.zip");
getURL.get("https://www.rorohiko.com/downloads/FrameReporter.1.1.8.zip", f.fsName);

JSXGetURL is a wrapper around libcurl which in turn is using open source code from OpenSSL, Boost, zlib.

The last few weeks, I put a fair bit of work in updating JSXGetURL. Version 0.0.9 is now available:

https://www.rorohiko.com/downloads/rr612412/JSXGetURL.0.0.9.zip

Changes

  • Added some simple functions to access request and response headers.
  • M1 support on Mac
  • Upgraded a bunch of dependencies. JSXGetURL now uses
  • zlib 1.2.13
  • Boost 1.81.0
  • OpenSSL 3.0.7
  • Curl 7.81.0
  • Visual Studio 2019 on Windows
  • Xcode 14.2

Licensing

The .zip file contains a fully functional, time-bombed version of JSXGetURL – it will expire on 30-June-2023.

I am currently still working hard on the Tightener project, and by then I hope to have the licensing module in Tightener functional enough to handle a licensing scheme for JSXGetURL. I’ve tried to run JSXGetURL as a ‘sponsored/donationware’ project for a few years, but that has not worked. From June onwards, there will be some subscription fee to be paid for continuous use.

InDesign GREP styles gotcha…

I was mucking around with InDesign GREP styles for auto-formatting dollar values, and had a bit of trouble getting it to work.

If you’re not interested in wading through the technical information below – you’re just poking around to find some example of how to format prices in InDesign: scroll down. The working solution is at the end of this post.

After a lot of hemming and hawing, I figured out what it was that made it behave in a way I did not expect.

Lookbehind does not allow for variable-length patterns

I was formatting prices, and had the following patterns set up:

While experimenting, I had given all the character styles involved a different colored stroke, so the characters would ‘glow’ in different colors depending on the character style applied to them.

This makes things a lot easier when used it together with the Preview option on the Paragraph Style dialog.

The raw text looks like this:

After applying the paragraph style, the result looked like this:

which means it did not format the cent values as expected.

As it turns out, positive lookahead (?=... allows variable length patterns, but positive lookbehind (?<=... does not.

So these patterns, which have lookbehind, did not work:

(?<=\d+)\.(?=\d{2})
(?<=\d+\.)\d{2}

Both patterns look behind for one or more decimal digits.

\d means a decimal digit;
\d+ means one or more digits
(?<=) means: look behind the character we’re currently working on
\. means: a period
(?=) means: look ahead from the character we’re currently working on
\d{2} means exactly two decimal digits

The first pattern means: look for a period, and then look behind (i.e. to the left of) that period and verify you can see one or more digits. Then look ahead of the period and verify you can see exactly two decimal digits.

But this pattern, a lookahead, does work:

\$(?=\d+\.\d{2})

Removing the + from the positive lookbehind patterns makes it all work:

My working solution:

I’ve put the styles into style groups ‘GREPStyles’ (to keep things organized):

The character styles are:

DollarSign: [None] + superscript
DollarValue: [None]
DecimalPoint: [None] + size 0.1pt + color: [None]
CentValue: [None] + superscript

The paragraph GREP style is set to:

Apply DollarSign to:
\$(?=\d+\.\d{2})

Apply DollarValue to:
(?<=\$)\d+(?=\.\d{2})

Apply DecimalPoint to:
(?<=\d)\.(?=\d{2})

Apply CentValue to:
(?<=\d\.)\d{2}

This is not perfect, but it’ll do for me.

You could easily extend this to also handle thousands separators – I leave that as an exercise.

Postscriptum:

David Blatner gave me a great tip which can be used to achieve a more precise matching and less ‘iffy’ results: the \K pattern.

This pattern allows us to do ‘lookbehind without lookbehind’ and does not have the same issues as lookbehind.

More info here:
https://www.regular-expressions.info/keep.html

Tightener 0.0.6 Public Alpha

The second public alpha of Tightener is available.

It consists of a single .zip file which works on Mac, Linux and Windows.

This version adds new integrations of Tightener into any Adobe® Creative Cloud applications that support ExtendScript.

It adds the Tightener Daemon CEP panel which helps Tightener run its event loop.

The Tightener Daemon CEP panel is available as a ZXP file which can be installed by way of Anastasiy’s Extension Manager.

To download, visit:

https://github.com/zwettemaan/TightenerDocs/tree/main/Releases/Alpha

For documentation, visit:

https://github.com/zwettemaan/TightenerDocs/wiki/Tightener-Docs

To see a few of the planned features, visit:

https://github.com/zwettemaan/TightenerDocs/issues

Tightener 0.0.5 Public Alpha

The first public alpha of Tightener is available.

It consists of a single .zip file which works on Mac, Linux and Windows.

To download, visit:

https://github.com/zwettemaan/TightenerDocs/tree/main/Releases/Alpha

For documentation, visit:

https://github.com/zwettemaan/TightenerDocs/wiki/Tightener-Docs

To see a few of the planned features, visit:

https://github.com/zwettemaan/TightenerDocs/issues

For a YouTube video demo, visit:

Tightener Status Update

Quick status update on Tightener: the project is still moving; I am slowly gearing up for a first alpha release.

A big chunk of work has been making the TQL glue language support ‘cooperative multitasking’.

Unlike JavaScript, where scripts are not running concurrently, but instead ‘pass the baton’ using constructs like Promises or async/await, TQL provides cooperative multitasking baked into the language, so multiple TQL scripts can run concurrently.

Next step will be to add a ‘yield()’ function to ExtendScript. The idea is that your ExtendScript would call ‘yield()’ frequently, and TQL scripts would then run a bit on each yield.

The use case I am working towards is around InDesign Server. One would use Tightener/TQL to coordinate things.

A web server could interact with the InDesign Server, and launch a TQL script containing an embedded ExtendScript on the InDesign Server.

The ExtendScript would be the ‘meat and potatoes’ script which does pagination, rendering, exporting,… whatever we typically do on InDesign Server.

The TQL script would continue to run concurrently with the ExtendScript and it would be able to easily send live feedback info to the web application (e.g. to show progress bars, update job progress, report failure or errors, interact with the user via the web browser,…)

This would make ExtendScript less of a ‘blocker’ and allow more lively behavior of the InDesign server.

Stay tuned…

JSXGetURL Sponsorships

Note: 23-Jan-2023: the sponsorship approach did not work, so I’ll be implementing a subscription fee approach by mid-2023.

Download version 0.0.9 (fully functional, time bombed at 30-Jun-2023):

https://www.rorohiko.com/downloads/rr612412/JSXGetURL.0.0.9.zip

Also see

https://coppieters.nz/?p=577

Note: If you are using version 0.0.7 or below, it will have stopped working by now because it was time bombed on December 31 of last year. You will have to update to version 0.0.9 (see link below).

JSXGetURL has been updated – I now have an M1 version available, but only for Github sponsors.

JSXGetURL adds basic http/https/ftp functionality to all Adobe apps that support ExtendScript – this includes InDesign, InDesign Server, Photoshop, Illustrator…

More info: https://coppieters.nz/?p=577

There is nothing to install, no admin privileges needed. You just need to add a folder with a few .jsx files to your ExtendScript project, and you can simply write ExtendScript like:

#include "JSXGetURL/JSXGetURLLoader.jsx"

var getURL = JSXGetURL();

var s = getURL.get("https://www.rorohiko.com");

alert(s.substr(0,1000));

and this works in InDesign Server, ExtendScript Toolkit, Bridge, InDesign, InCopy, Illustrator or whatever else supports ExtendScript, and it works both on Mac and Windows. And it’s very fast because it’s all compiled C/C++ code.

Note 23-Jan-2023: The sponsorship approach I tried in 2022 did not work, so I decided to change the strategy. The plan is that from June 2023 onwards, JSXGetURL will be subject to a subscription fee.

The link below gives you access to a fully functional version 0.0.9. It is time-bombed to stop working at 30-Jun-2023.

If all goes well, the subsequent updated version will be available for a subscription fee.

https://www.rorohiko.com/downloads/rr612412/JSXGetURL.0.0.9.zip

Tightener – Automating Creative Apps

I’ve been working hard on Tightener these last few months, and finally managed to build enough infrastructure to show a meaningful proof-of-concept demo.

The idea for Tightener occurred to me when setting up a web server that interacts with InDesign Server in the backend.

That integration is cumbersome and not straightforward, and developing and testing code is hard.

Doing this requires:
– setting up some method to synchronize data transfer between the various servers. Things like Samba or WebDAV or shared cloud volume. Synchronization problems abound.
– setting up some method to pass data from the web server to the scripts that need to run on the InDesign Server
– setting up some way to test and debug and deploy scripts on the live server
– maintaining multiple pools of code – web server code on one end, InDesign ExtendScript code on the other.
– setting up some method to monitor and track the jobs that are processed by the InDesign Server. Is the job complete? Has it crashed? Where to send the log files?

With Tightener that will all becomes a whole lot easier.

– Tightener is to offer bi-directional data transfer, synchronization and message passing. The web server can receive events from the InDesign server as the jobs progress
– Unified code base and simplified deployment: if so desired, InDesign scripts can be embedded into the web server code repository. Tightener can auto-deploy the scripts to the InDesign server as needed.
– Simplified testing and debug workflow. Interactively run the script against a local InDesign Desktop and a remote InDesign Server ‘as you go’.

There is still a lot of work to be done, but I’ve managed to get past a few sizeable hurdles.

I’ve got Tightener embedded in an initial InDesign plug-in. This extends InDesign with a new language called TQL (in addition to ExtendScript, VBScript and AppleScript).

TQL can access the InDesign DOM (I still have a lot more to cover here, but I’ve figured out how to do it, and finalizing the DOM access is just a matter of ‘more of the same’).

Tightener also handles IPC (InterProcess Communications) so multiple Tightener nodes on a workstation can interact with one another – e.g. a command line script runner can run TQL scripts in InDesign.

Here’s a YouTube playlist with more info:

https://www.youtube.com/playlist?list=PLuW3sE8aHqZa_9BBRIDKAUnLH65JZ-PRC

Also a link to the Tightener documentation project (rudimentary at the moment:

https://github.com/zwettemaan/TightenerDocs/wiki/What-is-Tightener%3F

Adobe Automation – work in progress

A few months ago, I started work on new infrastructure to automate Adobe Creative apps (and other apps).

A quick update: work is still steadily progressing.

At the the moment, I have built a framework in pure C++ 11, called ‘Tightener’.

There are no external dependencies – all you need to compile it is a C++ 11 compiler and a standard C++ library.

Tightener has an embedded language, called TQL (Tightener Query Language), pronounced as ‘tickle’.

TQL is similar to JavaScript, but simplified. It has the basics: expressions, functions, scopes, cooperative multitasking….

Tightener provides support for ‘Tightener Nodes’ which are instances of Tightener that are embedded into some environment (a command-line console, a language interpreter, an application, a network stack…).

Tightener nodes running on the same computer can communicate with one another using pipes/shared memory.

I envision many nodes per computer. There is one ‘main’ node and number of satellite nodes embedded into various environments, providing services (e.g. Python, InDesign, Illustrator, node.js…)

Using the messaging mechanism, one Tightener node can ask another Tightener node to run a TQL script.

TQL provides support for some XPath-like queries into entity-attribute Object Models – think of it as ‘XPath for JSON’.

Most embedded Tightener nodes will be able to handle queries into some object model (e.g. a Tightener node embedded in InDesign will handle queries into the InDesign DOM), and hence allow other Tightener nodes to make queries on their behalf.

I am currently integrating Tightener into an InDesign plug-in, so the InDesign DOM will become an integral part of the InDesign Tightener node.

Once that is done, any other Tightener node on the same computer can send TQL scripts to the InDesign node to query or execute.

Next I’ll integrate Tightener into Python, so I can start showing proof-of-concept demos: open a Jupyter Notebook, and interactively construct an InDesign automation script.

TQL is not meant for full-fledged programming (though it is powerful enough to do that).

TQL’s main goal is to reduce the number of round trips between the controlling Tightener node and controlled Tightener node: there is latency between the controller and the controlled. If we can execute some coded logic on the controlled node, that helps reduce the number of round trips we need.

(In short: this is the same thing as why ExtendScript is much faster than AppleScript. AppleScript has some latency each time we ‘cross the fence’ between the controlling AppleScript and the controlled program. ExtendScript lives ‘inside the fence’ and grafts directly only the controlled program).

A remote Tightener node is more ‘like AppleScript’.

An embedded Tightener node is more ‘like ExtendScript’.

So we want any remote Tightener nodes to delegate some of their work to an embedded Tightener node whenever possible. Hence TQL.

TQL is currently fleshed out enough to run all kinds of scripts, but there is still a lot of unimplemented things on my ‘to do’ list – e.g. the ‘switch’ statement.

Tightener nodes are, by design, not network-aware.

Networking functionality has to be achieved by building separate ‘gateway nodes’ which are essentially one or the other network comms stack with a copy of Tightener embedded.

This approach is to avoid introducing unwanted dependencies into Tightener: I don’t want to have to upgrade Tightener each time a new version of OpenSSL is released.

Next steps:
– Finalize Tightener integration into InDesign
– Embed Tightener into Python
– Create a Tightener gateway node by combining Tightener with OpenSSL

I’ve started a public Github repo where I’ll put the Tightener documentation as I write things up.

https://github.com/zwettemaan/TightenerDocs/wiki

Snippets

There is too much to remember, and I find myself searching for the same information over and over.

I’ve got a bunch of loose tidbits in Evernote, but in practice, Evernote is a bit of a sink: file a lot, rarely retrieve.

I am going to start keep track up useful snippets of code.

Every time I do a search and find some samples, I’ll make a re-usable snippet out of it – sample code for copy-pasting or testing

I’ll try to keep them organized, mainly for my own use, and for anyone else who can use it.

I’ll create a separate page for these: https://coppieters.nz/?page_id=451

Adobe Creative Cloud Automation

Project Status

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.

Programming Language

We should separate the DOM from the language used. ExtendScript forces one to use a JavaScript variant to access an app’s DOM.

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.

External Dependencies

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.

Connectivity

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.

It would open the door for scripters to use whatever environment they please – JavaScript, TypeScript, Python, PHP, Xojo, Swift, C++, some future language…

InDesign DOM

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.

DOM plug-ins

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.

Core ideas

  • 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
    – JavaScript/NodeJS 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’.