Copy link to clipboard
Copied
Hello community,
I am working on a small script that has to do with toggling OpenType style sets. I've figured out how to actually turn on a specific stylistic set using this syntax:
textVariable.textRange.characterAttributes.stylisticSets = STYLYSTIC_SET_NUMBER;
The problem is that, by default, all fonts that support stylistic sets have 20 such sets defined in the UI, with most of them grayed out if not actually available. However, when I try to loop through the available Stylistic Set options of a selected text using a for loop, even the grayed out ones are picked up.
Do you know if there is a way to validate the actual available style sets so that I can count them properly?
Copy link to clipboard
Copied
@Eduard Buta That's a good question... I couldn't work out how to find that out. As you discovered, if you set a stylistic set that the font doesn't have, it will set just fine, no error. You could potentially analyse the text metrics after the change to see if that particular text changed and that would prove that the stylistic set did exist in the font—but I can't imagine that would help in almost any real case. Sorry, I'm not much help.
- Mark
P.S. While I'm here, I'll just check that you understand an unusual feature of setting and getting `characterAttributes.stylisticSets`: that they take a "binary coded decimal", not a normal decimal. I've written a couple of functions to set and get stylistic sets. If you already know that, then you can ignore from here on. 🙂
You can use this to SET:
/**
* Sets which stylistic sets are active on selected text.
* Works with Indesign and Illustrator.
*/
(function () {
// example usage: let's say we want to turn on stylistic sets 1, 2 and 5.
setStylisticSets(app.activeDocument.selection, [1, 2, 5, 6]); // note: one-based indexing here
})();
/**
* Set active Opentype stylistic sets.
* @author m1b
* @version 2022-12-24
* @param {TextFrame|TextRange} text - the text.
* @param {Array<Number>} stylisticSetIndices - indices of the stylistic sets to activate.
*/
function setStylisticSets(text, stylisticSetIndices) {
if (
text.constructor.name == 'Array'
&& text.length > 0
)
text = text[0];
if (text.hasOwnProperty('textRange'))
text = text.textRange;
else if (text.hasOwnProperty('texts'))
text = text.texts[0];
if (app.name.search(/indesign/i) == -1)
// convert to zero-based indexing
for (var i = 0; i < stylisticSetIndices.length; i++)
stylisticSetIndices[i]--;
if (text.hasOwnProperty('characterAttributes'))
text.characterAttributes.stylisticSets = getBinaryCodedDecimalForArray(stylisticSetIndices);
else if (text.hasOwnProperty('otfStylisticSets'))
text.otfStylisticSets = getBinaryCodedDecimalForArray(stylisticSetIndices);
else {
alert('Please select some text and try again.');
return;
}
};
/**
* Returns a binary coded decimal number (BCD)
* representing binary bits.
*
* Use case: in Indesign and Illustrator,
* `characterAttributes.stylisticSets`
* are stored as a BCD.
*
* Example 1:
* - for Opentype stylistic sets 1 and 5:
* - `numbers` array = [0,4]
* - returns 17 (10001 in binary).
*
* Example 2:
* - for opentype stylistic sets 3, 4 and 5:
* - `numbers` array = [2,3,4]
* - returns 28 (11100 in binary).
*
* @author m1b
* @version 2023-07-12
* @param {Array<Number>} numbers - the stylistic sets indices, eg. [2,4,5].
* @returns {Number} - the decimal number representing the numbers.
*/
function getBinaryCodedDecimalForArray(numbers) {
var arr = numbers.slice().sort(),
bin = [],
len = arr[arr.length - 1],
i = len + 1,
val = arr.pop();
while (i--) {
if (val == i) {
bin[len - i] = 1;
val = arr.pop();
}
else {
bin[len - i] = 0;
}
}
// join into binary number and parse binary as decimal
return parseInt(bin.join(''), 2);
};
And use this to GET:
/**
* Reads which stylistic sets are active given selected text.
*/
(function () {
var flags = getActiveStylisticSetsForIllustrator(app.activeDocument.selection);
if (flags) {
// just for demonstrating:
for (var i = flags.length - 1; i >= 0; i--)
if (flags[i] == true)
$.writeln('Stylistic Set ' + (flags.length - i) + ' is active.');
}
})();
/**
* Returns an array of booleans representing
* active opentype stylistic sets.
* Note: will only return results of stylistic
* sets applied to the first character of `text`.
* @author m1b
* @version 2023-10-18
* @param {TextRange} text - the text to test.
* @returns {Array<Boolean>}
*/
function getActiveStylisticSetsForIllustrator(text) {
var setsNumber;
if (text.constructor.name == 'Array')
text = text[0];
if (text.constructor.name == 'TextFrame')
text = text.textRange;
if (text.hasOwnProperty('stylisticSets'))
setsNumber = text.stylisticSets;
else if (text.hasOwnProperty('characterAttributes'))
setsNumber = text.characterAttributes.stylisticSets;
if (setsNumber != undefined)
return getNumberAsBits(setsNumber);
};
/**
* Convert a decimal number into an array of
* boolean values for each binary place.
* For example:
* for n: 2 -> [true, false].
* for n: 37 -> [true, false, false, true, false, true].
* @author m1b
* @version 2023-10-18
* @param {Number} n - a decimal number.
* @returns {Array<Boolean>} - n as binary number represented by array of booleans.
*/
function getNumberAsBits(n) {
var bits = [],
b = (n).toString(2);
for (var i = 0; i < b.length; i++)
bits[i] = b[i] == '1';
return bits
};
/**
* Returns a binary coded decimal number (BCD)
* representing binary bits.
*
* Use case: in Indesign and Illustrator,
* `characterAttributes.stylisticSets`
* are stored as a BCD.
*
* Example 1:
* - for Opentype stylistic sets 1 and 5:
* - `numbers` array = [0,4]
* - returns 17 (10001 in binary).
*
* Example 2:
* - for opentype stylistic sets 3, 4 and 5:
* - `numbers` array = [2,3,4]
* - returns 28 (11100 in binary).
*
* @author m1b
* @version 2023-07-12
* @param {Array<Number>} numbers - the stylistic sets indices, eg. [2,4,5].
* @returns {Number} - the decimal number representing the numbers.
*/
function getBinaryCodedDecimalForArray(numbers) {
var arr = numbers.slice().sort(),
bin = [],
len = arr[arr.length - 1],
i = len + 1,
val = arr.pop();
while (i--) {
if (val == i) {
bin[len - i] = 1;
val = arr.pop();
}
else {
bin[len - i] = 0;
}
}
// join into binary number and parse binary as decimal
return parseInt(bin.join(''), 2);
};
Edit 2024-08-20: removed unused function.
Copy link to clipboard
Copied
Thank you for your detailed response, @m1b
You said the following: "You could potentially analyse the text metrics after the change to see if that particular text changed and that would prove that the stylistic set did exist in the font". I've already thought about this approach, and I think that's what I'm going to do, since it would most likely work in my use case.
I also wanted to let you know that I definitely did not know about binary coded decimal. So far I've got my code to a certain point, but I'm sure this will help me polish it further. Thank you very much!
Copy link to clipboard
Copied
That will be good if you still have a way forward. Yeah you can think of the stylistic sets number as 20 "switches" in binary, where 1 means turned on. So 10000000000000000010 means that sets 2 and 20 are turned on, and is I think 524,290 in decimal. Anyway good luck with your project!
- Mark
Copy link to clipboard
Copied
Hey, @m1b . I found a bug in my existing logic, and I was wondering if you know how could I detect the actual height of the letterforms in a text object via scripting? The object.height is unreliable for my use case, since it picks up the text's leading spaces.
Copy link to clipboard
Copied
Hi @Eduard Buta, I've written a function to do that. It's a bit involved! Read a discussion and get the script here.
- Mark
Copy link to clipboard
Copied
Thank you very much. This helped me get to the bottom of my problem. Much appreciated!
Copy link to clipboard
Copied
Two files in the Illustrator SDK look relevant to your interests, courtesy of a third-party repo that posted it (Adobe’s own developer page isn’t working in my Firefox):
The CharFeaturesSuite has functions for querying and setting OT features on a range of text in a text frame. (Look for the phrase “OpenType feature”.) I expect JSX’s CharacterAttributes.stylisticSets uses that under the hood.
AIFontSuite’s functions provides a lot more font information than JSX’s painfully limited TextFont class. (Look for “glyph set”.)
See if there’s anything there that whets your appetite. You’ll need a C/C++ developer to make use of it if you don’t know C yourself; there’s a few around these forums, typically under the SDK tag.
Copy link to clipboard
Copied
Thank you for the resources, @hhas01
Albeit, these might be a bit overkill for my simple need at the moment. I'm also pretty much struggling with ExtendScript as it is, so I doubt I'd be able to understand these. I managed to do what I wanted with what the API already offers and a bit of creative thinking on the side. Your answer is much appreciated either way!