ScriptUI Fonts Facts
June 02, 2012 | Tips | en
Skinning a user interface with ScriptUI may be tricky as soon as you want to apply custom fonts to specific fields or controls. Besides the ScriptUI object model does not offer intuitive solutions to that purpose, we also have to cope with a few cross-platform issues…
The Basics
Every ScriptUI element (such as Window
, Button
, Group
, Checkbox
, StaticText
, Listbox
…) exposes a graphics property that in turn exposes a font property. The graphics property is documented as a ScriptUIGraphics
object which “can be used to customize the element's appearance, in response to the onDraw() event.” There is much to say on this statement—in particular the onDraw method neither is an event, nor an actual event handler—but for now I want to focus on the font side. Given a ScriptUIGraphics
object, its font property is described as a ScriptUIFont
object which represents the “default font to use for displaying text.”
The first thing to notice is that the ScriptUIFont
object is in no way an interface to actually interact with fonts. Rather, it is a very basic object that simply stores a set of read-only properties:
• family: (String) The font family name.
• name: (String) The complete font name, “consisting of the family and style, if specified.” In fact, according to my tests, the two above properties contain the same string!
• size: (Number) The font size, in points. Floating-point numbers are supported but the size may be rounded to the nearest integer on rendering.
• style: (String) The font style, if specified (or the empty string). One of: "Regular", "Bold", "Italic", "BoldItalic".
• substitute: (String) “The name of a substitution font, a fallback font to substitute for this font if the requested font family or style is not available.” Although, I never found that property set to any non-empty value.
About ScriptUI.newFont()
Interestingly, the ScriptUIFont
class does not even offer a regular constructor. The official way to instantiate such object is to use the generic ScriptUI.newFont()
method, which normally expects three arguments: the font name, the style, and/or the size. If you supply a single argument, it is assumed to be the font name, then the style and size properties are set to default values. If you provide two arguments, the second parameter is regarded as the size! Finally, you must use the (name, style, size) order to specify a full structure, where style is a String
or a predefined alias (integer) found in ScriptUI.FontStyle
. That static enumerator just gathers a set of constant key-int pairs—{REGULAR: 0, BOLD: 1, ITALIC: 2, BOLDITALIC: 3}
—anyway there is no point in using that structure since ScriptUI.newFont(...)
supports, case-insensitively, the related strings: "Regular", "Bold", "Italic", "BoldItalic".
Note that you can invoke ScriptUI.newFont()
anywhere in your code, even with no ScriptUI element visible or created. The ScriptUIFont
object provides a user-friendly toString()
method that brings together the intrinsic properties of the object, formatted as follows: <NAME>[-<STYLE>]:<SIZE>
.
Here are some examples:
// The 'official' newFont(...) syntax // ================== // --- // Due to alerts ScriptUIFont.toString() // is implicitly invoked below: // --- alert( ScriptUI.newFont("Arial") ); // => "Arial:10.0" --so, 10 is the default size in my OS alert( ScriptUI.newFont("Arial", 16) ); // => "Arial:16.0" alert( ScriptUI.newFont("Arial", "BOLD", 14) ); // => Arial-Bold:14.0 alert( ScriptUI.newFont("Arial", 1, 14) ); // => Arial-Bold:14.0 --same result as above, 1 means Bold alert( ScriptUI.newFont("Arial", 1) ); // => Arial:1.0 --hey! this time 1 is the size!
The nice thing is that you can also supply a full formatted string as a unique argument to ScriptUI.newFont()
:
// Cool shortcut: var ft = ScriptUI.newFont( "Arial-BoldItalic:24" ); alert( ft.name ); // => "Arial" alert( ft.style ); // => "BoldItalic" alert( ft.size ); // => 24
Fonts Availability
As noted by Peter Kahrel in ScriptUI For Dummies: “For the font name you must use the font's PostScript name, which is not necessarily the same as the menu name used in InDesign's or PhotoShop's interface. If setting a font throws an error, chances are that that font's PostScript name is not the same as its menu name. […] To find a font's PostScript name, run this one-line script in the ESTK with the Console visible: app.fonts.item("Gill Sans").postscriptName;
”
In this regard, Mac OS and Windows deal very differently with missing fonts. The main issue is that Mac OS mutely allows an invalid font name to be loaded into the ScriptUIFont
object (without throwing any error), whereas ScriptUI / Win instantly rejects the request:
// ================== // Tested on Windows // ================== var ft = ScriptUI.newFont( "MyMissingFont-Regular:24" ); // Execution Error: // Error Number: 510 -- Invalid font name 'MyMissingFont'
By contrast:
// ================== // Tested on Mac OS // ================== var ft = ScriptUI.newFont( "MyMissingFont-Regular:24" ); // OK alert( ft.name ); // => "MyMissingFont" --all sounds OK!
So, when your code is running on a Win platform, you can easily detect ScriptUIFont
errors through try...catch
and provide gracefully degraded alternatives. On the contrary, checking whether the right typeface is actually applied to a Mac OS widget may be knotty, since the system noiselessly switches to a default font if a problem arises.
Note. — In fact, it is not correct that ScriptUI / Mac always ignores font errors. There are critical issues with corrupted or temporary unavailable fonts. In Mac OS Lion, for example, you can easily reach a OS Error: [-982] by playing with the graphics.font property of a ProgressBar
control. Also, some Mac OS X platforms may throw a “Bad argument” error when one attempts to set a non existing or corrupted font wich the system is supposed to own, e.g. Monaco. This issue has been mainly reported on MacPro or iMac machines with OS X 10.5.x or 10.6.x installed. In such circumstances, a try...catch
block is still needed to prevent obscure issues.
Regarding default typefaces, ScriptUI.applicationFonts
collects a (tiny) set of predefined ScriptUIFont
objects that fit the system preferences. The available keys usually are 'dialog', 'palette', and 'window'—which all appear to point out to the same structure, e.g. Tahoma:12.0 (Win) or Lucida Grande:14.0 (Mac). Each of those generic aliases can be supplied in every syntax that expects a ScriptUIFont
name. In such case, the actual name is internally set:
alert( ScriptUI.newFont("dialog:12").name ); // => "Lucida Grande", or "Tahoma", etc. // depending on the OS
Using ScriptUI Fonts
Despite what we have just learned, it is not so common we explicitly instantiate a ScriptUIFont
object in a script, since ScriptUI also allows to set the ScriptUIGraphics.font
property using the corresponding formatted string:
var u, w = new Window('dialog'), myStatic = w.add('statictext', u, "Hello World!"); myStatic.graphics.font = "Arial-Bold:24"; // Rather than: = ScriptUI.newFont("Arial-Bold:24"); w.show();
The above shortcut is extremely handful, although not documented! Apart from its brevity, it prevents the script engine from unnecessarily allocating space to a ScriptUIFont
object that you will never use by itself. Indeed, $.summary()
does not report any reference to a ScriptUIFont
instance.
What if you need to change the font of a widget at execution time? Just affect a new formatted string to myWidget.graphics.font
:
var u, w = new Window('dialog'), myStatic = w.add('statictext', u, "Hello World!"), b = w.add('button', u, "Change font"); myStatic.graphics.font = "Arial-Bold:24"; b.onClick = function(){ myStatic.graphics.font = "Verdana:16"; }; w.show();
Note. — Changing the font of a widget automatically causes a redraw (or calls myWidget.onDraw()
, if defined) but does not invoke the layout()
method of the window's layout object. If necessary, it's your responsibility to call w.layout.layout(1)
in order to update respective widgets bounds.
The following snippet illustrates how we can easily implement a simple editor—based on a multiline EditText
—that allows to dynamically increase/decrease the text size. Here again, all is done through string manipulation so that no explicit ScriptUIFont
is involved:
var u, w = new Window('dialog'), myEdit = w.add('edittext', u, "Sample", {multiline:true}); w.add('button', u, "Increase size", {step:1}); w.add('button', u, "Decrease size", {step:-1}); myEdit.preferredSize = [300,200]; myEdit.graphics.font = "dialog:14"; w.addEventListener('click', function(/*MouseEvent*/ev) { var ps = ev.target.properties, step = ps && ps.step, ft, sz; if( step ) { ft = myEdit.graphics.font; sz = Math.max(1,ft.size + step); // Change font size // --- myEdit.graphics.font = ft.toString(). replace(/:[\d.]+$/, ':'+sz); } ps = ft = null; }); w.show();
Ultimately, there are only two situations where you have to handle explicit ScriptUIFont
instances:
1) When you invoke myWidget.graphics.drawString(text, pen, x, y, font)
with a font argument which is not the current font applied to myWidget.graphics
.
2) When you invoke myWidget.graphics.measureString(text, font, boundingWidth)
with a font argument which is not the current font applied to myWidget.graphics
.
Indeed, those methods (exposed by any ScriptUIGraphics
object) both require a true ScriptUIFont
argument, if supplied. I can hardly imagine practical cases when you need to invoke drawString()
with different fonts within a single widget. Anyway, just remember this is possible.
How to Select a Valid Monospaced Font
Since ScriptUI / Mac usually does not protest against missing fonts, a widget can mistakenly claim it uses e.g. a Menlo typeface while it is actually rendering in Lucida Grande (assumed that Menlo is unavailable). Substitute fonts can become very annoying if your UI requires a fixed-width text at some location. Luckily, monospaced fonts are subject to a condition you can address through ScriptUIGraphics.measureString()
: all characters share the same width! Therefore, comparing the measured width of two very distinct characters, such as 'M' and the vertical line (U+007C), allows to control whether the loaded font is OK.
This trick allows to validate and automatically select, from a list of common candidates, the first fixed-width font available in any Mac OS platform:
var findMonoFont = function F() // ------------------------------------ { F.candidates || (F.candidates = ["Courier New","Monaco","Vera Mono","Andale Mono","Menlo"]); var a = F.candidates, i = a.length, r = '', w = new Window('dialog'), gx = w.graphics; while( i-- ) { gx.font = (r=a[i])+':24'; if( gx.measureString('|')[0]===gx.measureString('M')[0] ) { break; } } a = w = gx = null; return i >=0 ? r : ''; };
Of course the above code will not work on Win platforms, but it would be useless since you can address font availability through try...catch
:
// . . . try{ gx.font = "MissingFont:12" } catch(_){/*the font is missing*/} // . . .
Have fun!
Comments
Interesting article, Marc. Thanks very much.
Peter
I have to extract the fonts used in an .indd file and save the list of fonts into an excel sheet. Please let me know how can this be done.
Hi Awntika,
Thanks for your comment. However this is really another topic to deal with Application/Document fonts. Please, take a look at this link:
http://forums.adobe.com/message/249...
@+
Marc