Using the C preprocessor with the InDesign C++ SDK

Lately, I’ve been writing a fair bit of C++ code for InDesign C++ plug-ins, and I’ve started using some simple constructs that use the C preprocessor to make my code easier to follow.

This post focuses specifically on macro usage in the InDesign C++ SDK environment.

Pre-pre-amble: Plugin vs. Plug-In

Skip to the next section if you’re an experienced InDesign C++ developer.

If you’re new to in InDesign C++ plug-in development, you have to know there is a steep learning curve ahead of you.

First of all, make sure you understand the difference a plugin and a plug-in.

An InDesign plugin (no dash) is an enhancement built within the UXP environment.

An InDesign plug-in (with a dash) is an enhancement built on top of the C++-based InDesign SDK.

You need to start here:

https://developer.adobe.com/console/servicesandapis

There is an entry that says ‘InDesign Plugin’ which is for the UXP environment.

That’s not the one you want. For the InDesign C++ SDK, you need another entry that says just ‘InDesign’:

For my training course for would-be InDesign C++ developers, I wrote a book that I use as training course notes.

https://www.lulu.com/shop/kris-coppieters/adobe-indesign-cs3cs4-sdk-programming/paperback/product-165ej5dq.html


I wrote this more than a decade ago, and CS3 and CS4 are now fossils of a distant past, but as it so turns out, the underlying concepts of InDesign C++ programming have not changed, and the book is still highly relevant.

Before you can dive into the SDK you need to ‘grok’ a whole range of higher-level abstractions and terminology: boss classes, interfaces, implementations, Query vs Get,…

And remember, if you are interested in getting a head start with InDesign C++ development, make sure to talk to me: [email protected]

Pre-Amble

This blog post is about a few simple C macros that make my life easier.

So, I won’t get into the pros and cons of do-while(false) vs. early return vs. throw/catch, I won’t delve into D.R.Y (Don’t Repeat Yourself) and K.I.S.S (Keep It Simple, Stupid), or ‘how many lines in a function’. That’s stuff to be discussed over a beer.

The InDesign SDK

For the longest time, I’ve been mimicking the style of the SDK when writing C++ plug-ins.

I.e. I want to make sure the code I am writing ‘looks the part’, and follows a similar style and similar coding conventions.

do-while(false);

One of the somewhat controversial constructs in the InDesign SDK is the use of do-while(false); i.e. a loop that does not loop.

This construct is used to implement what I describe as ‘a pre-condition ladder’.

The idea is that every function starts with a list of pre-conditions and assertions that are expected to be true, and once all the pre-condition tests pass, you get to the ‘meat’ of the function and execute the function.

The preconditions are like the rungs of the ladder, and if something is not right, you break and ‘fall off the ladder’ towards the while (false). If all is well, you can ‘climb down the ladder’ and safely reach the ‘meat’ of the function.

Something like

int f(params) 
{
    int value = 0;
    do
    {
        if (! somePrecondition1())
        {
            break;
        }
        if (somethingUnexpectedBad())
        {
           LOG_ERROR("Something unexpected bad happened");
           break;
        }
        
        value = AllisWell(bla) + 123;
    }
    while (false);
    return value;
}

The function purposely only has a single return at the end, which makes it easier to break and inspect the return value with a debugger, so we don’t use ‘early returns’.

Using C Macros

I’ve recently built a few simple C macros that, at least for me, make my InDesign plug-in code less verbose and more readable.

Note that the macros I am presenting here are simplified versions of the ones that I am actually using.

The full-fledged versions that I have built and actually use have many more features, like compile-time balancing and error detection, but discussing those details would lead me too far into the woods.

The idea is that I want to help the reader of my code (that reader is often me, six months from now).

If the reader of my code sees the beginning of a do statement, I am leaving them guessing a bit as to what that do will be used for. A real loop? An infinite loop? A condition ladder construct?

A second idea is that in C/C++ the ‘end of a thing’ is the closing curly brace. It is heavily overloaded, and used for many different things. That can make code hard to figure out.

With these macros, the ‘end of some things’ is much more explicit – rather than a curly brace, there is an explicit ‘END’ to the ‘thing’.

The third idea is that in the InDesign C++ plug-in code I like to distinguish between different kinds of pre-conditions:
• ‘normal’ pre-conditions. Things you want to check for, and bail out of the function if not met, but which are normal and expected -> PRE_CONDITION
• ‘error’ pre-conditions. Things that ‘should not happen’, so you want to emit errors to the logs -> SANITY_CHECK
• things that make you go ‘huh?’ but are not necessarily errors -> EXPECTED

#ifndef __PREPROCESSOR_CONSTRUCTS__h_
#define __PREPROCESSOR_CONSTRUCTS__h_
//
// Preprocessor magic to convert code into string
//
#ifndef __STR
#ifdef __VAL
#undef __VAL
#endif
#define __STR(x) __VAL(x)
#define __VAL(x) #x
#endif
#define BEGIN_CONDITION_LADDER          do {
#define END_CONDITION_LADDER            } while (false)
#define CONDITION_LADDER_BREAK          break
#define BEGIN_INFINITE_LOOP             do {
#define END_INFINITE_LOOP               } while (true)
#define INFINITE_LOOP_BREAK             break
#define PRE_CONDITION(CONDITION, BREAK) if (! (CONDITION)) BREAK
#define SANITY_CHECK(CONDITION, BREAK)  if (! (CONDITION)) { ASSERT("! " __STR(CONDITION) ); BREAK; }
#define EXPECTED(CONDITION)             if (! (CONDITION)) ASSERT("! "  __STR(CONDITION))
#define BEGIN_FUNCTION                  do {
#define END_FUNCTION                    } while (false)
#define FUNCTION_BREAK                  break
#endif // __PREPROCESSOR_CONSTRUCTS__h_

A sample function:

bool16 ActivePageItemHelper::GetStoryFrameList(
  const UIDRef& storyUIDRef,
        UIDList& pageItemList)
{
    bool16 success = kFalse;
    BEGIN_FUNCTION;
    IDataBase* dataBase = storyUIDRef.GetDataBase();
    SANITY_CHECK(dataBase, FUNCTION_BREAK);
    pageItemList = UIDList(dataBase);
    InterfacePtr<ITextModel> textModel(storyUIDRef, UseDefaultIID());
    SANITY_CHECK(textModel, FUNCTION_BREAK);
        
    InterfacePtr<IFrameList> frameList(textModel->QueryFrameList());
    PRE_CONDITION(frameList, FUNCTION_BREAK);
    success = kTrue;
    int32 curPageItemIdx = -1;
    for (int32 frameIdx = 0; frameIdx < frameList->GetFrameCount(); frameIdx++)
    {
        bool16 frameIdxSuccess = kFalse;
        BEGIN_CONDITION_LADDER;
            InterfacePtr<ITextFrameColumn> textFrameColumn(frameList->QueryNthFrame(frameIdx));
            SANITY_CHECK(textFrameColumn, CONDITION_LADDER_BREAK);
            InterfacePtr<IHierarchy> textFrameColumnHierarchy(textFrameColumn, UseDefaultIID());
            SANITY_CHECK(textFrameColumnHierarchy, CONDITION_LADDER_BREAK);
            InterfacePtr<IHierarchy> multiColumnItemHierarchy(textFrameColumnHierarchy->QueryParent());
            SANITY_CHECK(multiColumnItemHierarchy, CONDITION_LADDER_BREAK);
            InterfacePtr<IHierarchy> splineItemHierarchy(multiColumnItemHierarchy->QueryParent());
            SANITY_CHECK(splineItemHierarchy, CONDITION_LADDER_BREAK);
            InterfacePtr<IGraphicFrameData> splineItem(splineItemHierarchy, UseDefaultIID());
            SANITY_CHECK(splineItem, CONDITION_LADDER_BREAK);
            frameIdxSuccess = kTrue;
            //
            // Check if already in list
            //
            UID splineUID = ::GetUID(splineItem);
            PRE_CONDITION(curPageItemIdx < 0 || pageItemList[curPageItemIdx] != splineUID, CONDITION_LADDER_BREAK);
            pageItemList.Append(splineUID);
        END_CONDITION_LADDER;
        success = success && frameIdxSuccess;
    }
    END_FUNCTION;
    return success;
}

Points of interest:
– I am expecting textModel to be non-null, and if it is null, something is seriously wrong, hence I use SANITY_CHECK.
– I need frameList to be non-null, but if it is null, that’s not an error; it simply means that we cannot run the function in this particular context, hence I use PRE_CONDITION.
– Within the loop, I have little CONDITION_LADDER. Make sure all is well before executing pageItemList.Append(splineUID)

There are some interesting aspects to this.

Because these are macros, it is easy to (temporarily) add some extra code to macros like BEGIN_FUNCTION/END_FUNCTION.

You should add logging/tracing code in a debug version, or can add code that will break into the debugger when certain conditions are met.

If you have logger infrastructure, add the logging calls to the macros (e.g. SANITY_CHECK would logs errors, PRE_CONDITION does not log anything)

Adding logging and tracing to the BEGIN_FUNCTION/END_FUNCTION macros can be help in situations where you’re unable to use a debugger on a problem, e.g. when the issue happens on a customer’s server, and you cannot get access to the server.

Such code will ‘pooff!’ away in the release version, and only be added in the debug version.

Take it for what it is worth; it works for me. Using these macros adds another level of abstraction, which can be considered a ‘bad thing’, but overall, I find these helpful.

Mitigating JavaScript Promise Pitfalls for Occasional JavaScript Developers

Helping the Occasional JavaScript Developer with Tracked Promises

I am currently working with Promises in JavaScript and exploring ways to make them more digestible for an Occasional JavaScript Developer (OJD).

Because I routinely dive in and out of various development environments, I consider myself an OJD; after I’ve done PHP or Python or C++ or ExtendScript all day, switching to a JavaScript mindset doesn’t come easy.

Surprises, and not the good kind

Much of the code I write is used by other OJD (or OC++D or OPHPD…), and while coding, I strive to avoid surprises.

By avoiding surprises I mean: if you read some source code first, and then step it with a debugger, everything works as you’d expect. It should be outright boring.

I find that it is fairly hard to write JS code without surprises; you don’t need much for the code to suddenly veer off on an unexpected tangent.

I prefer my code to be gracious, spacious, and clear. A calm, open landscape rather than a dense, compressed knot of complexity waiting to unravel.

Limited Lifetime Scripts

In some JavaScript environments (e.g. UXP/UXPScript), scripts have a limited runtime/lifetime.

Any unsettled promises disappear and will never be handled if the script terminates before the JavaScript runtime had a chance to settle all promises.

Promises and async/await are great when you live and breathe JavaScript day in day out, but the OJD will often find themselves on treacherous terrain.

One issue with Promises is that it is very easy to forget to wait for the Promise to settle (or with async/await, forgetting an await), and then these Promises might silently disappear, never to be settled.

Using VSCode and Copilot and other stuff helps, but I still don’t like it.

Fire and Forget

What compounds the issue is that I like to use a ‘Fire and Forget’ approach for certain methods, for example, logging to a file.

If the logging module I am using is asynchronous, it makes sense to me to just call the logging calls, and not await or then them.

My logging strings are time-stamped anyway, and I don’t really care when a log entry makes it into a log file. I want to fire off the logging call, and carry on with the ‘meat’ of the code, full speed ahead.

The issue is that if the script terminates too early, those pending logging calls never reach the log file.

There was a lot of head scratching during my initial experiments with UXPScript for InDesign and Photoshop, until I realized what was happening to my logging output.

Fixing the issue

There are multiple approaches to work around this (such as: keeping a list of all pending log calls and await them later with a combined Promise, using event emitters, relying on assistance from VSCode, Copilot and other tools…).

But nearly all of these approaches seem to be quite cumbersome for the OJD.

Tracked Promises

I developed an approach that seems to give me the lowest possible impact on the source code.

It’s not a panacea – it involves replacing the built-in Promise class with a subclass thereof, which can have negative consequences.

However, in the specific environments (UXP/UXPScript) where I am using them I feel my ‘hack’ makes it a little easier for an OJD to grok my code and maintain it without needing to fully understand async JS.

What I am doing is subclassing Promise with a new subclass (for the sake of argument, let’s call it TrackedPromise), and then assign TrackedPromise to global.Promise.

This causes the remainder of the script to use the TrackedPromise rather than the ‘regular’ Promise. The UXP/UXPScript engine will also automatically use the TrackedPromise for async/await.

The TrackedPromise is identical to Promise, except that it tracks construction and settling of Promises and it retains a dynamic collection of ‘unsettled promises’. Promises that settle are removed from the collection – only unsettled promises are tracked.

When my script is about to end (i.e., ‘fall off the edge’), I call a static method on the TrackedPromise to await any unsettled promises before proceeding.

That way, I can make sure Promises don’t disappear into the bit bucket and my ‘fire and forget’ logging calls all make it.

This also helps me finding any forgotten await or then; I can easily add debugging code to the TrackedPromise to point out where the issues are.

If you’re interested to see some code, have a look at the Github source code for Creative Developer Tools for UXP, link below. The tracked promises are part of the crdtuxp runtime.

Note: this code is work-in-progress. Also, use this idea at your own peril! While it works for me, there are no warranties, expressed or implied!

https://github.com/zwettemaan/CRDT_UXP/blob/7c081ca37c544f5c578005e3feb0552a05767155/CreativeDeveloperTools_UXP/crdtuxp.js#L4233

Potential Concerns

  • Overwriting global.Promise can lead to unintended consequences, especially in larger or more complex applications. Other libraries or parts of the code might not expect the modified behavior of TrackedPromise.
  • Introducing a custom Promise subclass adds a layer of abstraction. If this is unexpected, it can make debugging more ‘surprising’, rather than less.
  • Tracking all unsettled promises and waiting for them to settle at the end of the script can introduce a non-negligible performance overhead.
  • Future updates to the JavaScript engine could inadvertently introduce bugs or incompatibilities with this approach.

Re: Tidy InDesign Scripts Folder/Stub Scripts

Playing with InDesignBrot and PluginInstaller

If you want to see how those stub scripts work, here’s how you can do that.

You need to have installed PluginInstaller build #583 or higher (https://PluginInstaller.com) you can use the following link to install InDesignBrot_idjs into your Scripts Panel without unwanted clutter.

With PluginInstaller 0.2.4.583 or higher installed, click the following link:

InDesignBrot_idjs for InDesign sample script

PluginInstaller should open.

Click the Download button.

If you have multiple versions of InDesign installed, use the Install Target dropdown menu to pick the one you want to install into.


Click the Install button, and switch over to InDesign. The script should become available on your InDesign Scripts Panel.

Some rules of thumb:
– Doubling max steps will roughly double the time it takes to calculate the image.
– Doubling num pixels will roughly increase the time needed by a factor of four.

Start out with the default values and first gauge how long the script takes on your computer: your mileage may vary, and as far as I can tell my Mac with an M2 Max is pretty darn tootin’ fast.

I also found that on my Mac, InDesign now crashes when I set num pixels to 200, but 149×149 works fine (and takes about 30 mins to calculate).

Not sure about that, maybe the sheer amount of rectangles needed (40,000) is more than InDesign can handle. But I’ve calculated 200×200 renditions with earlier versions of InDesign, and those files still open just fine.

More Info About InDesignBrot

InDesignBrot source code is available on Github. I have a separate branch for a version based around Creative Developer Tools for UXP (CRDT_UXP). Note the README info further down this page.

https://github.com/zwettemaan/InDesignBrot/tree/CRDT_UXP

The main branch on Github is for an older, hybrid version where the same source code can be run in ExtendScript as well as UXPScript, for speed comparisons:

https://github.com/zwettemaan/InDesignBrot

InDesign UXPScript Speed

Or, “how a single comment line can make an InDesign UXPScript run more than five times slower”.

The Issue

I discovered a weird anomaly in InDesign UXP Scripting which can adversely affect the execution speed of a UXPScript.

I also tried it out with Photoshop. As far as I can tell, Photoshop UXPScript is not affected by this.

Simply adding a comment line like

// async whatever

into the ‘main’ .idjs file makes my script way, way slower.

A noticeable difference is a different redraw behavior while the script is executing.

I suspect the InDesign internal UXP Scripting module performs some crude preliminary textual scan of the script source code before launching the script, and InDesign behaves differently depending on whether it found certain keyword patterns or not.

The textual scan does not seem to care where the patterns occur: e.g. in comments, or in strings or in actual source code.

The issue does not occur for anything that appears in a submodules (require). I am guessing the preliminary textual scan only inspects the ‘top level’ .idjs script.

Because this textual scan does not account for patterns occurring in comments, I can simply add a dummy comment line with the right pattern and trigger the behavior, and make my script become much slower.

It took me a fair amount of time to figure this out, because the same behavior also occurs when you run the script from the Adobe UXP Developer Tools.

Because there were two unrelated causes for the same symptom, I had to resort to tricks to avoid the ‘Heisenberg effect’.

Initially, each time I tried to observe/debug it, the issue was always ‘there’. And it sometimes did and sometimes did not happen when I ran my script from the Scripts Panel. I tell you, there was much growling and gnashing of teeth.

Demo

I have a benchmarking script, called InDesignBrot, which I keep handy and occasionally use for speed-testing InDesign. I have both ExtendScript and UXPScript variants of the script.

While trying to figure out what was going on, and to help make the issue stand out, I’ve re-worked the UXPScript variant of the InDesignBrot script so it only using Promises. It does not use the async or await keywords at all.

If you run this script from the InDesign Scripts panel, it will calculate a rough visualization of the Mandelbrot set in InDesign, using an NxN grid of square frames.

You can then tweak the parameters on the pasteboard and re-run the script.

On my M2 Max MacBook Pro, the script executes in about 0.5 seconds for a 19×19 grid.

While the script is running, the screen will not update, and the script does not redraw the page until it has completed the calculation.

Then I add a single comment line with the word async followed by a space and another word, like

// async whatever

anywhere in the InDesignBrot.idjs script.

This innocuous change makes the redraw behavior change, and I can now see individual frames being filled, despite InDesign being set to

app.scriptPreferences.enableRedraw = false;

In the end, the same script will take around 3 seconds or more to execute.

The InDesignBrot script can be reconfigured by way of a text frame on the pasteboard. If I change the num pixels to 29, the times become 1 second vs 20 seconds.

If you’re interested in trying this out for yourself, I’ve made a specific branch in the InDesignBrot Github repo. This branch has been trimmed down to remove stuff that’s not relevant to the discussion.

https://github.com/zwettemaan/InDesignBrot/tree/Odd_async_demo

Pull the repo or download the repo .zip and move the InDesignBrot folder onto the Scripts Panel.

Then double-click InDesignBrot.idjs to run the script.

You can tweak the settings on the InDesign pasteboard and experiment by re-running the script as many times as desired.

Tidy InDesign Scripts Folder

When installing scripts for InDesign, I don’t like to see this:

The issue is that the ‘main’ script is actually InDesignBrot.idjs, but it is buried in chaos, and the user would most probably not know what to do. The InDesign Scripts Panel also shows stuff my scripts need, but which has no relevance to the user of my scripts.

Instead, I want to see something like this (some more utilities thrown in for the sake of argument):

Each individual utility script has a subfolder below the User folder, and within those subfolders, the user sees only clickable scripts. No data files, no templates – just a clickable script.

This functionality is now part of the upcoming release of PluginInstaller.

The new release of PluginInstaller allows you, as the developer, to package scripts and their ‘satellite’ files into a single .tpkg file. It works for both .jsx (ExtendScript) and .idjs/.psjs (UXPScript) scripts.

At the user’s end, the user will use PluginInstaller to install the .tpkg file. Rather than creating a whole raft of files in the Scripts Panel folder, PluginInstaller will create just a ‘link’ to any clickable script files.

My first attempt for this feature was to use symlinks, but that approach has some serious drawbacks. If the actual script files are deleted, the symlinks become broken, and things turn to custard when the user double-clicks the broken entry on the Scripts Panel.

I’ve solved this by using ‘stub’ scripts instead of symlinks. The PluginInstaller will generate a small in-between stub script. When double-clicked, all this stub script will do is transfer control to the actual script, stored somewhere else, in a safe place.

If and when the actual script file is accidentally deleted, the stub script will bring up an error dialog with a clear explanation to the user (essentially: “This link is broken, please use PluginInstaller to re-install the missing script”).

When the user uses PluginInstaller to uninstall a script, the stub script will automatically be removed from the Scripts Panel.

Much cleaner!

Grokking Promises in JavaScript

I find Promise in JavaScript complicated to work with.

I understand how they work, and I know how to use them, but they are not ‘smooth’ for me.

Contrast that to concepts like, say, ‘calling a function’ or ‘scope’.

Those are also complex things, but while I am merrily coding away, I rely on a simple mental model, and I can blindly use those concepts without needing to think deeply about them and the complexities involved.

While coding, whenever I need to use a Promise, I invariably find myself coming to a grinding halt, getting out of ‘the zone’, and having to spend thinking time to make sure I grok my own code.

I think I now figured out why Promise rubs me the wrong way, and how to remedy that by using a mental model I am calling coderstate.

Coderstate is not something I use to code; instead it is a ‘mental state’ in my coder brain to track what I can and cannot do at some point in the code.

I currently distinguish between the following coderstates: global, module, async, function, procedure, promisor, executor, resolver.

Mentally keeping track of the coderstate helps me know when or why to use return and how to ‘pass on’ data.

Introduction

JavaScript is a language built with asynchronous operations at its heart, primarily handled through Promises and the newer async/await syntax.

While async/await simplifies asynchronous code, understanding the fundamental mechanism of Promises is crucial for any JavaScript developer looking to write efficient, performant, and scalable applications.

Coderstate

To demystify Promise, and help me work with them, I introduced a concept that I call coderstate.

Coderstates map to distinct behavioral zones within Promise handling in JavaScript, each with specific roles and expectations.

It is an attempt to give me an easy ‘mental model’ that I can rely on when working on code with Promise.

First some pseudocode with annotated areas to exemplify the coderstate.

As I read through code like the pseudocode below, I now mentally keep track of the coderstate. It helps me figure out ‘where I am at’, and how to handle data at that point in the code.

Two important aspects are:
– should or shouldn’t I use return?
– how should I ‘pass on’ the data I have?

Look for comments // ** coderstate: ...:

// ** coderstate: global or module

let apiEndpoint = "https://api.example.com";

function appendX(s) {

// ** coderstate: function

    return s + "X";
}

function logIt(s) {

// ** coderstate: procedure

    console.log(s);
}

async function manageUserData(userId) {

// ** coderstate: async

// In coderstate async, I can use "await" keywords
    let userData;
    try {
        userData = await getUserData(userId);
        console.log("User Data Processed:", userData);
    } catch (error) {
        console.error("Error handling user data:", error);
    }

    return userData;
}

function getUserData(userId) {

// ** coderstate: promisor

// A promisor is akin to an async function, 
// but does not have the async keyword in 
// the declaration. It still aims to return
// a Promise and works like an async function 
// for all intents and purposes

    return new Promise((resolve, reject) => {

// ** coderstate: executor

// Nested inside this promisor, 
// we find this executor coderstate 

        fetchData(userId, resolve, reject);
    });
}

function fetchData(userId, resolve, reject) {

// ** coderstate: executor

// This section is also part of the executor:
// the ultimate goal is to call the 
// resolve or reject functions, 
// either now, or some time in the future

    console.log("Fetching data for user ID:", userId);

    fetch(`${apiEndpoint}/user/${userId}`)
        .then(response => response.json())
        .then(

            (data) => {

// ** coderstate: resolver

// Here we're inside the function that is 
// called when the Promise resolves

// We can return plain values or we can return 
// a chained Promise. We can also call and return 
// the values of the outer reject or resolve functions

                if (data.error) {            
                    return reject(
                      "Failed to fetch data: " + 
                      data.error);
                } else {
                    return resolve(processData(data));
                }
            },

            (reason) => {

// ** coderstate: resolver

// Here we're inside the function that is called 
// when the promise rejects, which is a form of 
// resolution too
            }

        })
        .catch(
            (reason) => {

// ** coderstate: resolver

                return reject(
                    "Network error: " + 
                    error);
            }
        );
}

function processData(data) {

// ** coderstate: function 

    console.log("Processing data...");

    return data;
}

manageUserData(12345);

coderstate: global or module

Top-level code in a script or module.

Can declare variables or functions and initiate asynchronous operations.

coderstate: function

Inside a regular function; using return is expected to return some value. Returns an implicit undefined if there is no return.

coderstate: procedure

Inside a regular function; no return is expected – i.e. the caller will ignore the return value.

coderstate: async

Inside an async function.

Can use await to pause function execution until a Promise resolves, simplifying the handling of asynchronous operations.

Whatever we return will become a Promise once it is received by the caller.

coderstate: promisor

This state signifies a standard, non-async function that aims to return a Promise.

These promisor functions can be called by asynchronous code – to the caller they look like async function.

It’s all about how you can look at some code – promisors are not a ‘programming thing’, more like an ‘understanding/expecting thing’.

Promisors behave pretty much the same as async functions and can be called with await.

If the return value of a Promisor is not a Promise, the await-ing code will automatically wrap it with a resolved Promise.

We cannot use await inside the promisor because the function is not explicitly declared as async – we need to chain promises with then.

function fetchUserData(userId) {

  // ** coderstate: promisor

  return new Promise((resolve, reject) => {

    // ** coderstate: executor

    if (userId < 0) {
        // Here we use 'return' to abort the execution
        // of the executor function.
        // Without 'return', we would also execute the
        // resolve call further down
        return reject(new Error("Invalid user ID"));
    }
    resolve("User data for " + userId);

  });
}

coderstate: executor

An executor is a function passed in to the constructor of a new Promise object.

It accepts a resolve and a reject parameter, both of which are functions.

An executor is a coderstate that has access to either the resolve or the reject functions and whose job it is to eventually call resolve or reject.

When calling a nested function from an executor, where we also gets provide the resolve and/or reject parameters, I will also consider the scope of this nested function to also be in the executor coderstate. See fetchData in the snippet below for an example.

const promise = new Promise(
     (resolve, reject) => {

// ** coderstate: executor

        fetchData(userId, resolve, reject);
     }
);

function fetchData(userId, resolve, reject) {

// ** coderstate: executor

// fetchData is considered part of the executor 
// coderstate because it can directly call resolve or 
// call reject.

    fetch('https://api.example.com/data')
        .then(response => resolve(response.json()))
        .catch(error => {

// ** coderstate: resolver

            return reject(new Error("Network error: " + error.message));
        });

});

Be careful with return: a return statement can be used inside an executor, but it is not used to return any useful data to a caller.

return can only be used to force an early exit from the executor function.

From within an executor, any result data is ‘passed on’ by way of parameters when calling resolve or reject, not by way of return.

I have a more extensive code sample further down to clarify this.

In a good executor, we need to make sure all code paths eventually end in calling either resolve or calling reject.

Note that a common pattern is to use return reject(...) or return resolve(...).

This can be slightly confusing. It is important to understand that in coderstate executor, data is passed on by way of the parameter values of these function calls.

The return statement merely forces an early exit from the executor code flow and the caller will not use any of the returned data.

Contrast this with coderstate resolver where using the return statement is crucial to pass on the data.

coderstate: resolver

This state is when we’re executing the resolve or reject call from a Promise.

Data is passed in as parameters to the resolver function.

Data can be ‘passed on’ by way of the return statement.

This is important: in a coderstate resolver, the return statement is instrumental in passing on data, whereas in coderstate executor, the return statement plays no role in passing on any data.

From coderstate resolver, we can chain on additional Promise. We can either return the final ‘plain’ value or we return a chained Promise.

More Complicated Example

In this example, pay attention to when return is needed or not.

In this example we have a fast flurry of multiple coderstates, and knowing which is which can help us understand when we need return and when we can omit it.

function appendX(s,m) {
    
// ** coderstate: promisor

    return new Promise(
        (resolve, reject) => {

// ** coderstate: executor

            if (m == 1) {

// Note: Data is passed on via resolve(). No need for "return"
// We still could use "return" to force early return from code 
// and avoid trailing code execution, but any data returned is ignored

                resolve(s + ":m=" + m);
            }
            else {

                setTimeout(
                    () => { 

// ** coderstate: procedure

// No return value is expected here, so I see this as a procedure
// Note: Data is 'passed on' via resolve(). No need for "return"

                        resolve(s + ":m=" + m),
                    },
                    1000);
            }
        }
    );
}

function nested(s, m) {

// ** coderstate: promisor

// We don't see an explicit new Promise() here, but because appendX
// is either a promisor or async, this function also becomes a promisor.

    return appendX(s, m).then(
        (result) => {
// ** coderstate: resolver
// Here, the "return" statement is required to pass on the data     
            return result + "Chained";
        }
    )    
}

await nested("xx",1);

Comparing Promises with async/await

While async/await is syntactically easier and cleaner, using Promises directly gives developers finer control over asynchronous sequences, particularly when handling multiple concurrent operations or complex error handling scenarios.

Conclusion

Understanding and utilizing the different “coderstates” of Promise-related code in JavaScript can make it easier to follow the logic of async JavaScript code.

Injecting UXPScript Wrapper

Further to my previous post, it turns out the trick I discovered might be useful.

I am still on the warpath, in full ‘discovery’ mode, so it’s perfectly possible that the approach outlined below is moot and there are easier ways to do the same, but I am recording this for future reference nevertheless…

Part of what I do in CRDT_UXP is to handle a lot of stuff in a daemon process (e.g. there is a centralized logger). Sending stuff from UXPScript to the logs is async.

The issue is that the code in CRDT_UXP creates a lot of Promise-s without waiting for them to resolve.

I don’t want to slow down the code and wait for confirmation of, say, a crdtuxp.logNote() call.

So I will call
crdtuxp.logNote(...)
rather than
await crdtuxp.logNote(...)

Waiting would create an unnecessary game of ping-pong between the UXPScript code and the daemon process.

The problem arises when the UXPScript terminates: the wrapper code in InDesign that calls my script does not wait for any still-pending promises to resolve.

At the time of termination, any ‘loose’ unresolved promises are simply discarded, and my crdtuxp.logNote calls don’t even make it to the daemon.

So, with the trick in my previous post, I could:

1) cast away the async prefix to the wrapper, so the ‘wrapping function’ becomes a bog-standard JavaScript function.

To avoid confusing the calling code, I make this function return a Promise so the calling code needs be none the wiser and can treat the function as if it were an async function.

2) Before opening a new scope and create a new function I can replace the default Promise class with a proxy replacement which adds tracking code for any as-of-yet unresolved promises.

Because I am doing this ‘outside’ the scope of the wrapper function, I can ‘inject’ a new Promise class.

3) At the end of my script, I add

return Promise.all(Promise.pendingPromises());

which is a promise to end all promises.

The call Promise.pendingPromises() is a static method on my proxy Promise class which returns an array of all ‘not-yet-resolved’ promises.

As it turns out, the calling code in InDesign accepts this return value and (I assume) has an await on it.

This way, I can make sure the script fulfils any ‘dangling’ promises before terminating – so all my logging calls make it into the log files before the script terminates.

WARNING: the following code is unfinished ‘work-in-progress’ – don’t copy-paste this into your own project sight unseen!!

For reference, my .idjs script now looks as follows.

// Close off the async wrapper created by the calling context
});

... stuff ...

const inDesign = require("indesign");
const app = inDesign.app;

// Save the original Promise class
const SystemPromise = global.Promise;

let PROMISES_PENDING = {};
let LAST_PROMISE_UNIQUE_ID = 0;

// Define the new Promise class
class Promise {
    constructor(executor) {

        this._state = 'pending';
        this._value = undefined;
        this._id = ++LAST_PROMISE_UNIQUE_ID;

        // Create a new instance of the original Promise
        this._promise = new SystemPromise((resolve, reject) => {
            executor(
                value => {
                    this._state = 'resolved';
                    this._value = value;
                    if (this._id in PROMISES_PENDING) {
                    	delete PROMISES_PENDING[this._id];
                    }
                    resolve(value);
                },
                reason => {
                    this._state = 'rejected';
                    this._value = reason;
                    if (this._id in PROMISES_PENDING) {
                    	delete PROMISES_PENDING[this._id];
                    }
                    reject(reason);
                });
        });
        PROMISES_PENDING[this._id] = this;
    }

    // Proxy static methods
	static pendingPromises() {
		let retVal = [];
		for (let id in PROMISES_PENDING) {
			retVal.push(PROMISES_PENDING[id]);
		}
		return retVal;
	}
	
    static resolve(value) {
        return SystemPromise.resolve(value);
    }

    static reject(reason) {
        return SystemPromise.reject(reason);
    }

    static all(promises) {
        return SystemPromise.all(promises);
    }

    static race(promises) {
        return SystemPromise.race(promises);
    }

    // Proxy instance methods
    then(onFulfilled, onRejected) {
        return this._promise.then(onFulfilled, onRejected);
    }

    catch(onRejected) {
        return this._promise.catch(onRejected);
    }

    finally(onFinally) {
        return this._promise.finally(onFinally);
    }
    
    isPending() {
        return this.state === 'pending';
    }

    isResolved() {
        return this.state === 'resolved';
    }

    isRejected() {
        return this.state === 'rejected';
    }

    getValue() {
        return this.value;
    }    
}

... stuff ...

// Open a new, non-async wrapper to pass back to the calling context. 
// The closing '})' is not in this file - it is instead provided by 
// the calling context.

(function (exports, require, module, __filename, __dirname) {

... the meat of the code ...

// End with a promise which will finalize all pending promises
// so the caller does not exit until all is done

return Promise.all(Promise.pendingPromises());

// No }) here - the calling wrapper provides that!

UXPScript Tidbit

(note 2024-07-24: I’ve written a follow-up post, with a practical use of this technique:
https://coppieters.nz/?p=945)

I discovered this (probably useless) tidbit. When creating a UXPScript for InDesign, in its simplest form, you create an .idjs text file, and put a script into it.

You can then debug your script using Adobe’s Adobe UXP Developer Tools which are part of the Creative Cloud apps.

I noticed in the debugger, that when I ran my script, the calling context would wrap my script between two added lines:

(async function (exports, require, module, __filename, __dirname) {
.... the contents of my .idjs file...
});

which I assume is done by simple string manipulation, and then this string is parsed by the JavaScript engine, and then executed.

That led me to try a code injection in my .idjs script. I added a single line, at the top of my script.

This added line does not make sense on its own, but it ‘slots in’ when accounting for the wrapper.

After adding my ‘sneaky line’, my .idjs file becomes:

});(function (exports, require, module, __filename, __dirname) {
... the contents of my .idjs file ...

Hence, when I run this script, the effective code (i.e. my code combined with the wrapper provided by the calling UXPScript context) becomes:

(async function (exports, require, module, __filename, __dirname) {
});(function (exports, require, module, __filename, __dirname) {
... the contents of my .idjs file ...
});

My script does not contain any await statements, so it continues to work.

With this injection, I’ve ‘cast away’ the async nature of the wrapper, by replacing the async wrapper with a normal function wrapper.

I doubt that this is useful, and I don’t yet ‘grok’ the implications yet, but I suspect this might come in handy later, for example to inject stuff into the global variable space.

Things like:

(async function (exports, require, module, __filename, __dirname) {
});
let globalVar = 123;
(async function (exports, require, module, __filename, __dirname) {
… the contents of my .idjs file …
});

which creates a variable in the Script scope.

The reason I scratching around here is that I am currently exploring the ins and outs of UXPScript for InDesign, figuring out how to best integrate UXPScript into the Creative Developer Tools/Tightener technology stack and figure out how I can reduce friction and make the environment easier to use for simple ad-hoc scripts, which are the bread and butter of many workflows.

At present, I am planning to implement a ScriptUI-inspired UI environment for UXPScript, opening the door for simple UI, without needing to go full UXP. More later…

Flexible, configurable InDesign scripts with minimal effort

Preamble

When building InDesign scripts for creative users, the core functionality of the script often requires only a fraction of the development effort.

Most of the budget is needed for creating a user interface, such as a dialog or a panel, to allow the user to configure the script functionality.

I’ve recently started using a new pragmatic approach with an absolutely minimal user interface, often greatly reducing the necessary budget.

This approach also offers significant additional benefits beyond reducing development effort and cost.

The idea is simple: add a non-printing text frame somewhere in the document, typically on the pasteboard near the first page. This text frame contains configuration info for the script, using an enhanced version of the old Windows INI format.

This format, while more primitive than JSON or XML, is robust and human-readable, avoiding the pitfalls of complex delimiters and balancing issues. INI’s simplicity makes it resistant to corruption yet easy to use.

In an ideal world with unlimited budgets, one would alway create a supporting User Interface. In the Real World, using INI on the pasteboard is often Good Enough.

I am in the process of adding a supporting API into Creative Developer Tools; I’ll soon have the same API available both in CRDT_ES and CRDT_UXP. There are functions for parsing INI data, and post-processing values (e.g. when a boolean is expected, accepting input values like true, 1, yes, t, Y, Yes… as synonyms for true).

More info about CRDT here:
https://www.rorohiko.com/wordpress/creative-developer-tools/

INI – a quick refresher

INI data is line-oriented and consists of a mix of section lines and name/value lines:

A section line contains a string between square brackets, e.g., [Section1].

A name/value line contains a string followed by an equal sign and another string, e.g., firstName = John.

Example:

[Section1]
firstName = John
lastName = Doe
kids = Jill, Jack, Jeremy

Spaces around the equal sign are ignored. If a value string needs leading or trailing spaces, enclose it in double quotes:

nameWithExtraSpace = "    John Doe   "

Quotes around strings are ignored, allowing embedded quotes without escaping.

This is much easier to explain to end-users than the JavaScript/JSON escape \ mechanism.

Example:

nextLine = "Jeff said: "how are you, Doris?" while walking to the counter"

Advantages of Using INI in a Text Frame for Configuration

The advantages of stashing the config info in a text frame on the pasteboard are:

User-editable and Embedded

The config data is part of the document, making it user-editable and user-inspectable.

Unlike side-car files, it cannot get detached from the document.

Using a text frame is also easier than embedded metadata like XMP, which requires a UI for editing.

Expresses Document Dependency on Automation

The presence of a config text frame indicates that the document is associated with some automation. Including comments in the INI frame can provide additional information about the script.

Human-readable and Comment-able

The data is human-readable and can include explanatory text. Anything that doesn’t look like INI data is ignored.

Instills Confidence for the End-User

The robust format minimizes accidental data corruption, giving users confidence when tweaking the config.

There are no hidden settings that can cause unexpected behavior.

Easy to Transport

The config data can be easily copied between documents or templates by copying the text frame.

No Need for a UI

For simple scripts, there is no need to create a CEP panel or ScriptUI interface. The INI config frame can be made self-explanatory.

Enhancements

Case-Insensitive and Space-Insensitive

The format is mostly case-insensitive and ignores most whitespace, except within value strings.

Characters in name strings are limited to a-z, A-Z, 0-9; any other character within a name is ignored.

Example: the following two files are equivalent:

[Section1]
firstName = John
lastName = Doe
kids = Paul, Joseph, Joanne
[ section 1 ]

first name = John

last name = Doe

K I D_S = Paul, Joseph, Joanne

Lenient Quoting

Different types of quotes are accepted without distinction, making the format robust even when using smart, unbalanced quotes. Curly quotes are just fine.

No Need to Teach Escaping \

Because the quote characters are not ‘special’ they can be embedded into value strings without need for the complexities that come with the JavaScript escape character.

Leading and Trailing Spaces in values

Leading and trailing spaces in value strings are ignored unless the value string is enclosed in quotes.

Examples:

name1 = John Doe
name2 =     John  Doe

the spaces within the values for name1 and name2 are taken into account. name1 is "John Doe" (1 space between first and last name) and name2 is "John Doe" (2 spaces).

If leading or trailing spaces are needed in a value string it can be enclosed in quotes.

name1 = “ John Doe ”

Arrays

By default, INI has no support for arrays. It remains up to the script developer to work around this.

One simple approach is to accept comma-separated strings in the value and use the split() function in JavaScript to split the value. This is often Good Enough for arrays of strings.

If more complex objects are needed, the enhanced INI has some rudimentary support for arrays of objects, by allowing the user to repeat a section name and each time the section name is repeated, it constitutes a new object entry in the array.

Re-using a section name is interpreted as ‘next row’.

For example:

[xyz]
a=1
b=9

[xyz]
a=2
b=76

[xyz]
a=556
b=4

Each time the [xyz] re-occurs, a row counter is incremented, and the row counter automatically suffixed to the names.

Initially, the row counter is 1 (first row). The first row is a special case, and for the first row, there is no suffix.

From the second row onwards, the suffix is _2, _3,…

The data above is equivalent to:

[xyz]
a=1
b=9
a_2=2
b_2=76
a_3=556
b_3=4

This can then be post-processed with the CRDT API into the following object:

[
    {
        "a": 1,
        "b": 9
    },
    {
        "a": 2,
        "b": 76
    },
    {
        "a": 556,
        "b": 4
    }   
]

Creative Developer Tools

The Creative Developer Tools runtime contains methods to parse and post-process enhanced INI data.

I won’t document them here – the source of truth is the documentation of CRDT_ES and CRDT_UXP. At the moment, I am still adding new methods to the API.

Networking Event – Creative Developers Summit, Washington DC

An event you need to note down in your calendar:

  • Creative Developers Summit (July 10 + 11, 2024, Washington DC, co-located with the CreativeProWeek)

Creative Developers Summit

Since 2010, I’ve helped organizing our independent Creative Developer Summit, co-located with CreativeProWeek.

The 2024 Creative Developer Summit happens on July 10 and 11, at the Crystal Gateway Marriott hotel in Washington, D.C.

Make sure to read this blog post of mine:

This independent developer get-together is organized by developers for developers. Come and join your tribe!

Speakers and Topics:

Date: July 11:

Justin Taylor (Hyper Brew): Bolt UXP: Build Adobe UXP Plugins Faster
Hamza Habeeb (Adobe): Adobe Express Add-ons: Spark Creativity with Powerful Gems
Harbs (Santa Cruz Software): First-hand Experience Building Express Add-ons
Kris Coppieters (Rorohiko): Creative Developer Tools: Realizing the Tightener Technology Stack, Three Years On
Caleb Clauset (Typefi): Typefi’s Dev Tools
Colin Flashman (colecandoo): Esko Without a Budget
Keith Gilbert (Gilbert Consulting): From the Trenches
James Lockman (Adobe): A First Look at the Adobe Firefly and Creative Cloud APIs
Erin Finnegan (Adobe): Adobe Creative Cloud Developer Champion Program
Adobe Reception: For Community Experts and Developers

Date: July 10

Workshop: Integrating Firefly with Creative Cloud APIs
Instructor: James Lockman and Adobe Team

* In person registration:

To attend the summit in-person, purchase a 1- or 2-day CreativeProWeek ticket.

https://creativeproweek.com/dc-2024/creative-developers-summit/

And don’t miss the networking opportunities at the hotel bar!

Fees cover food, drinks, conference room access, and Wi-Fi. Message or email me (kris at rorohiko.com) for a special developer discount code to get 50% off.

* Remote attendance is free. Register here:

https://bit.ly/creativedev2024