Private Investigation into Style Overrides
April 29, 2024 | Tips | en
Paragraph- and Character-Based Attributes
In InDesign, text formatting attributes are addressed at two basic levels:
— The Character Formatting Level (ℂ
) covers text attributes that can vary within a single paragraph, like font settings (family, style, size), kerning, language, color…
— The Paragraph Formatting Level (ℙ
) controls settings that affect an entire paragraph like alignment, indents, hyphenation, numbering, paragraph shading and so on.
These distinct sets of options (over 300 in total) can be fine-tuned and applied, either manually to arbitrary text portions, or in a more logical way using styles. What is not obvious to the novice is how paragraph styles and character styles actually register, share and drive these ℙ
/ℂ
settings.
Paragraph and Character Styles
In the Scripting DOM, a Paragraph Style (PS) is a collection of fully determined key-value pairs emanating from the ℙ
and ℂ
spaces. For example, a paragraph style has a fixed left indent value in ℙ
as well as a fixed typeface, style and text color in ℂ
.
Note. – Due to inheritance via the basedOn
property, the settings of a PS are actually determined with respect to hierarchical dependencies. However, the value of every attribute is always positively known.
By contrast, a Character Style (CS) is a collection of ℂ
attributes (only), and most of them are not even specified. This structure strictly encodes differences, i.e. specific changes relative to a default, or inherited, formatting state. We'd better talk about it in terms of ℂ
refinements. As long as a CS does not decide on a setting, it makes no changes to the underlying attribute and therefore remains transparent in the formatting process.
InDesign provides two locked root styles:
— The PS “[No Paragraph Style]” contains a preset of ℙ+ℂ
attributes that define “the default look of text with no further formatting applied.” (Cf InDesign Plug-In Programming Guide, vol. 1, “Text Fundamentals”, Adobe, 2012, p. 289.)
— The CS “[None]” is just an abstract empty set; “it exists purely to provide a root for character styles.” (ibid.)
Note. – Do not confuse the hidden, locked “[No Paragraph Style]” item and the “[Basic Paragraph]” shown in the PS panel. Despite its reserved name, the latter can be fully edited.
About Style Overrides
Due to the very existence of styles, a text entity has two additional correlates that are not listed in the primitive ℙ
and ℂ
spaces, namely the underlying appliedParagraphStyle
and appliedCharacterStyle
.
Although any insertion point can express these two extra properties, the logical scope of a paragraph style is the Paragraph
object, while a character style is intended to fine-tune TextStyleRange
units, that is, any “continuous range of identical text formatting attributes.”
But the story can get complicated due to the occasional application of ℙ
and/or ℂ
attributes beyond the control of a style. Here is a basic scenario of such cascading events:
1. At the scale of the paragraph under consideration,
a) the applied PS defines the expected ℙ
attributes;
b) some ℙ
attributes may then be overridden (against their PS specification).
2. At the scale of the character range under consideration,
a) the applied PS defines the expected ℂ
attributes;
b) the applied CS defines the expected ℂ
refinements;
c) some ℂ
attributes may then be overridden (against PS and/or CS specs).
→ 1b) effects are called Paragraph-Level Overrides (ℙ+
in short);
→ 2c) effects are usually called Character Overrides (ℂ+
).
Note. — “Local overrides (…) occur when a formatting change is made without modifying a style; for example, selecting a word and making it bold.” (op. cit., p. 292.)
Paragraph-Level Overrides
The ℙ+
case is the easiest to understand because it is detected at the paragraph scale and involves neither ℂ
attributes nor assigned CS. In the UI, the Style Override Highlighter indicates such events by colored boxes bordering the paragraph(s) in question. For example, here is the result of manually altering the alignment of two paragraphs from their regular centered state:
The figure above illustrates an important fact in that it does not reveal any ℂ+
issue while mixed ℂ
attributes are visible with the same applied style “MyParaStyle”. The formatting of the first paragraph fully complies with all ℂ
attributes defined in the PS — since the “[None]” style is uniformly assigned to that line. Yet, in the second paragraph, a custom CS style “ItalStyle” is applied, with the effect of italicizing the text. This corresponds to refinement step 2b) and therefore does not result in any character overrides.
By definition, ℙ+
events only report discrepancies in paragraph formatting. They don't care about ℂ
attributes. With multiple CS applied throughout the same paragraph, still no ℂ+
to deplore:
In the figure, the bottom paragraph was restored in relation to its PS using a code like this:
// Clearing ℙ+ para.clearOverrides(OverrideType.PARAGRAPH_ONLY);
But the command ...clearOverrides(OverrideType.ALL)
would have had exactly the same effect since there is no ℂ+
in our example. For the moment, remember the following translations concerning the OverrideType
enumeration:
OverrideType.PARAGRAPH_ONLY
↔ ℙ+
OverrideType.CHARACTER_ONLY
↔ ℂ+
OverrideType.ALL
↔ ℙ+
and ℂ+
Note also that clearing ℙ+
inherently affects the entire paragraph. So you could just as easily run the command from a simple insertion point with the same outcome:
// Clearing ℙ+ (...from any paragraph subtext) para.insertionPoints[0].clearOverrides(OverrideType.PARAGRAPH_ONLY);
Let's now move on to the reciprocal question: How to detect that a paragraph is altered by some ℙ+
? (I mean, specifically altered at the PARAGRAPH_ONLY
level, no matter what happens in the ℂ
space.)
To my knowledge, you cannot get this information directly from the scripting DOM! Neither of the two modes of investigation available to us, myText.styleOverridden
and myText.textHasOverrides(...)
, allows us to discern ℙ+
issues from ℂ+
issues. One might think the opposite by examining the arguments offered by the method textHasOverrides()
but it turns out that those arguments only handle the PS/CS distinction (via the StyleType
enum), not the ℙ+
/ℂ+
distinction encoded in OverrideType
.
If we return to our scenario, this means that these features cannot discern
1b) cases, i.e. ℙ+
against PS
from
2c) cases, i.e. ℂ+
against PS and/or CS.
The Scripting DOM only tells us, at best, against which StyleType
(PS or CS) the override appears. So in the happy circumstance where you can strictly establish a CS violation, you know you are dealing with a ℂ+
because such an event does not occur in 1b). But in the case of a PS violation, you don't know if you are facing a 1b) or 2c) issue, that is, a ℙ+
or a ℂ+
override.
Note. — InDesign 12 introduced a second argument in textHasOverrides(...)
, the Boolean parameter charStyleAsOverride, which does not solve our problem in any way. Worse, it tends to obscure the ℙ+
/ℂ+
dichotomy. (Coming back to it a little further…)
The Case of Character Overrides
Remember the three sub-steps that weigh in on processing ℂ
attributes:
2a) desired settings from the applied PS;
2b) desired refinements from the applied CS;
2c) ℂ+
overrides (against PS and/or CS).
Here is a typical example of 2c) override as shown by the Highlighter:
Unlike the ItalStyle and RedStyle text ranges, which exhibit 2b) refinements as dictated by the corresponding character styles, the BoldOver part is marked as a ℂ+
(blue highlighting). However, note that the ‘+’ suffix is only displayed in the paragraph styles panel (“MyParaStyle+”.) This should not be interpreted as a ℙ+
issue though.
Indeed, InDesign is just sending us the following message: Regarding ℂ
attributes, this local bolding violates the PS specifications AND is not supported by the active CS — the underlying style being “[None]” in that particular area. Please note that this ℂ+
does not conflict with the assigned CS at this point, because the “[None]” style (i.e. the empty shell) demands nothing regarding the font, the typeface or any ℂ
attribute, neither positively nor negatively. Hence the local override occurs with respect to the PS only.
Still with me? Ok, let's look at a more striking example. Suppose we change the ItalStyle part to purple. What do you expect now from the respective style panels?
Surprised? Except for highlighting a new style range, there is no difference in the UI. InDesign keeps sending us the very same signal: Regarding ℂ
attributes, this purple assignment violates the PS specifications AND is not supported by the active CS. Please note that it does not conflict with the “ItalStyle” rules, which only define a +Italic
refinement and don't care about the text color!
This is a very important example to keep in mind. We are in a region where a character override (ℂ+
) coincides with the scope of a non-neutral CS, and yet the conflict does not concern this applied CS at all, but still the overhanging PS.
Now, a definitive experiment is to apply the purple color to the RedStyle part:
This time, the fillColor=Red
criterion is violated, so we have a double ℂ+
marked by two trailing ‘+’ symbols in the respective style panels: “MyParaStyle+” (PS scope) and “RedStyle+” (CS scope).
We can even go further. Let's manually change that purple override and apply a [Black] color instead, that is, the fillColor
attribute that happens to be the one “MyParaStyle” expects. Here is the result:
Note that “MyParaStyle” is then displayed without the ‘+’ suffix in the PS panel. The ℂ+
highlighting is maintained anyway, which may seem counterintuitive since the ℂ
attributes now fully match the PS specification!
What the InDesign panels tell us now is this: Although the text range fully complies with the PS settings (no conflict in that panel), it still contradicts the ℂ
refinements requested by “RedStyle”, i.e. the applied CS.
Interlude: Going Back to [None]
You might naively think that by forcefully applying the “[None]” style to a ℂ+
issue that only concerns a CS, the override will vanish and everything will return to normal. But if we do this test on the ItalStyle portion once ‘restored’ to Regular, we get a result for which nothing had prepared us:
The question is WHY? Why does InDesign keep reporting a ℂ+
in a region where all ℙ
and ℂ
settings perfectly match the applied PS (“MyParaStyle”) and can in no way be objected to by the “[None]” style?
I stood there for a long time wondering what was going on there… To top it off, if one selects the disputed part and run the following code,
const PS_TYPE = StyleType.PARAGRAPH_STYLE_TYPE; const CS_TYPE = StyleType.CHARACTER_STYLE_TYPE; sel = app.selection[0]; // The ℂ+ violation is selected alert( sel.textHasOverrides( PS_TYPE ) ); // true!!! alert( sel.textHasOverrides( CS_TYPE ) ); // true!!!
everything happens as if the scripting subsystem detected a double departure from the PS and CS settings! Since there is definitely no ℙ+
in my example, this confirms forever that no analogy can be drawn between StyleType.PARAGRAPH_STYLE_TYPE
and OverrideType.PARAGRAPH_ONLY
. As we said above, these two enumerators have absolutely no connection.
Hoping to get a hold of the cause of the override, you may want to check by yourself if the selection and the underlying style(s) actually coincide with respect to the appliedFont
, the fontStyle
or any other properties you might suspect. E. g.
sel = app.selection[0]; ps = sel.appliedParagraphStyle; alert( ps.name ); // MyParaStyle cs = sel.appliedCharacterStyle; alert( cs.name ); // [None] alert( sel.appliedFont===ps.appliedFont ); // true :-) alert( sel.fontStyle===ps.fontStyle ); // true :-) // etc
So, everything perfectly matches. Even deeper diff routines won't discover why InDesign sees a gap where there isn't one.
Here's our last resort: select the so-called character override, create a fresh CS from there, apply that very style — the ℂ+
highlighting disappears! — and examine how its settings differ from the root style. (Click on the animation to see the ending in cinemascope!)
How to interpret this result? By some means that is not apparent within DOM properties, InDesign just remembers that a local +Regular
command has been manually applied to the text range. The resulting ℂ
state is formally indistinguishable from that specified by the applied PS (“MyParaStyle”), but the application still sees it as a “pure local ℂ+
override” being added during step 2c). Whether this should be considered a bug, I don't know. In any case, if we export the document in IDML from its ℂ+
state, the unwanted highlighting disappears upon reopening and our sel.textHasOverrides(…)
tests now return false
. We can therefore speak of a sort of ghost print of a style override that no longer exists.
Getting rid of this dust is not difficult. After all, it would be enough to delete the newly created CS (after the step shown in the animation); just make sure you deactivate the Preserve Formatting option:
A more expeditious approach for developers is to run the command sel.clearOverrides(OverrideType.CHARACTER_ONLY)
on the ℂ+
. Ironically this command is not available in the interface when you select the problematic text,
but the code does work and eliminates the issue. Anyway the real problem is not cleaning up those ghost overrides, but detecting them in the first place! If you need an advanced style override processing script, you'll likely have to implement your own comparison or “style-diff” algorithm. Unfortunately, your efforts will still not be enough to detect fake ℂ+
that the Highlighter claims to identify.
Note. - And I'm not even talking about nested styles, nested GREP styles and similar side effects. (It would take another long article to cover this subject.)
Related Scripting Tools
It's high time to say more about the scripting features in contact with style overrides.
1. clearOverrides(OverrideType)
The first historic DOM method, Text.clearOverrides(...)
, was born with InDesign CS2 (4.0) and remains of prime importance today. It introduces the OverrideType
enum already mentioned with its three options (.CHARACTER_ONLY
, .PARAGRAPH_ONLY
, .ALL
) and can selectively remove ℂ+
and/or ℙ+
overrides from any Text
entity. It is also supported at the Story
level.
2. styleOverridden: Boolean
The Boolean property Text.styleOverridden
(also exposed to Story
objects) was added in InDesign CS3 (5.0). The documentation is slightly ambiguous about it: its exact purpose is to indicate whether the target text contains one or more overrides (regardless of their type) with respect to the applied paragraph style. In other words, this property reliably reflects the ‘+’ suffix visible in the PS panel; it addresses ℂ+
and/or ℙ+
events.
mySelection.styleOverridden
will wake up in almost all relevant cases, that is, situations represented by
“MyParaStyle+ ¦ MyCharStyle” or “MyParaStyle+ ¦ MyCharStyle+”
in the style panels. As we have seen, alternate cases of the form
“MyParaStyle ¦ MyCharStyle+”
are both bug harbingers (see previous section) and quite rare in practice. Remember: they correspond to a ℂ+
that contradicts the applied CS while restoring attributes in line with the PS.
As a general tip, only invoke styleOverriden
(or the methods described below) from a uniform TextStyleRange
. When investigating overrides, start by splitting your target into homogeneous parts — myText.textStyleRanges.etc
— so as to extract clear information that does not mix multiple formatting issues. Failing such rules, you would only get properties from the first style range identified in the text, which would completely distort your conclusions.
3a. textHasOverrides(StyleType): Boolean
As Uwe Laubender noted in this 2019 thread, Text.textHasOverrides(...)
was born with a single argument (the newly created StyleType
enumerator) in InDesign 11.x. This innovation seemed like excellent news, but rather than bringing the refinements that one might expect, it mainly introduced major confusion.
Text.textHasOverrides(StyleType)
claims to distinguish overrides in conflict with the applied PS (PARAGRAPH_STYLE_TYPE
) from those in conflict with the applied CS (CHARACTER_STYLE_TYPE
). At first glance, the PS option is redundant with Text.styleOverridden
, but it introduces a sneaky nuance: By default, the very detection of a character style — I mean, not “[None]” — will be reported as an override, regardless of whether the formatting attributes perfectly match the CS rules.
So, with this new tool in hands, you still don't know if you are dealing with ℂ+
or ℙ+
issues, but you take the risk of finding so-called overrides that just don't exist…
3b. textHasOverrides(StyleType,Boolean): Boolean
Finally, the current version of Text.textHasOverrides(...)
(which dates back to InDesign 12.x) provides an optional extra argument, charStyleAsOverride (Boolean
), which in some way repents of decisions previously taken. By explicitly setting this parameter to false
, you give up on “considering character styles as overrides”.
Logically, this second parameter only makes sense if the first is set to StyleType.PARAGRAPH_STYLE_TYPE
. I haven't found any situation where it affects the returned value when StyleType.CHARACTER_STYLE_TYPE
is used.
(In the following, I will abbreviate StyleType.PARAGRAPH_STYLE_TYPE
to <PS>
and StyleType.CHARACTER_STYLE_TYPE
to <CS>
.)
In practice, the test
(a) x.textHasOverrides(<PS>,false)
is almost always equivalent to
(b) x.styleOverridden
.
The only case I found where (a) answers true
while (b) answers false
is that of a ℂ+
in conflict with the applied CS but in agreement with the applied PS, i.e. “MyParaStyle ¦ MyCharStyle+” (our rare case.) The behavior of textHasOverrides(<PS>,false)
seems quite absurd in that context. In addition, we can't get anything out of that code as soon as a ℙ+
complicates the matter. (The problem has not changed in this regard, there is nothing to detect ℙ+
issues alone.)
Now, what is the purpose of x.textHasOverrides(<CS>)
? From my experiments, true
informs us that there is a ℂ+
at some point, not necessarily with respect to the applied CS (!) but let's not sulk, this still remains valuable information.
The map below summarizes the results of various tests carried out with these methods. The “signature” attached to any analyzed text part shows how the scripting features react to each situation. (Click the image to see the detailed codes.)