Colours in Word 2007, Part 1
Posted by Tony Jollans (Microsoft MVP for Word) on 21st August 2007
Introduction
Not specifically a new feature, changes to colours have come about with the introduction of a new Themes feature common to Word, Excel, and PowerPoint, and working with them in VBA requires a bit more knowledge than in the past. In this, the first of two posts based on a longer piece I am writing, I examine Color properties, particularly, but not exclusively, of the Font object; the second part will continue this examination as well as looking at ColorFormat objects for those objects that have them.
Colours in the User Interface
Click on the Font Color icon (
) on the Formatting Toolbar in Word 2003 (or 2002 or 2000) and you will see this:

Click on the Font Color icon in Word 2007, now in the Font group on the Home tab of the Ribbon, and you will see this:

Not only has the pretty picture changed. The whole underlying colour model is different. With a couple of exceptions (highlighting, track changes, and, arguably, embedded MS Graph objects), all colours in Word can now be, and are by default, based on scheme colours belonging to a Theme. Choose a colour from the main block in the 2007 Color Dialog shown above and a reference to the colour, rather than the colour itself, will be stored. This is true even if you are working in Compatibility Mode and if you eventually save a document in Word 97-2003 format then, and only then, and only in the saved copy, will the colours be downgraded to RGB values reflecting the theme at that moment; this degradation happens without warning and is not picked up by the compatibility checker.
As you will note from the tooltips, if not from the dialogue itself, darker and lighter shades of all the scheme colours can be chosen. Although it isn’t really directly relevant in the UI, it is as well to understand that the percentages shown are really percentages of available lightness (or darkness) rather than percentages lighter or darker than the actual base scheme colour.
Themes are very much part of Word (and Excel and PowerPoint) 2007 and are a powerful tool for creating what Microsoft call professional-looking documents. They affect the User interface in all sorts of ways but, for the most part, once you understand them, there are no huge surprises; the surprises come when you address them in VBA.
How colours are presented via the Object Model
There are three ways colours are presented to VBA in Word: via ColorFormat objects, via Color properties, and, less significantly, via old ColorIndex properties. Whilst ColorFormat objects would seem to be the way forward, not all colourable elements have them and some, fonts in particular, have had the new colours squeezed into the existing Color properties. Unfortunately this can break existing code and, as there is nothing new in the Object Model to help, the only way to work with them is at the byte and bit level.
Color Properties: the low-down
All colours in Word are stored as 32-bit values, made available to VBA via the object model as Long data types, which can be considered as four separate bytes. Exactly what is stored can vary but there are two types of value:
-
Absolute colours, stored as 24-bit RGB values, with the most significant byte (or, more specifically, the most significant bit) set to zero, equating to positive numbers (or zero) when interpreted as Long values. Each of the primary components of the colour uses one byte: red, green, and blue in order from least significant to most significant. There are Word enumeration constants, for example wdColorBlue or wdColorPink, available for all of the 40 colours in the (pre-2007) basic palette with several extra shades of grey thrown in for good measure.

A representation of an RGB colour (White in this case) as stored by WordThere is a special RGB value that can be returned to VBA, but cannot be set on a font through VBA, and which is never held by Word with any special meaning. This is the colour with the code equating to decimal number 9999999, red 127, green 150, blue 152, a nondescript sort of grey, which indicates that elements being queried do not all have the same colour.
-
Special, referential or relative, values indicating that colours stored, or determined, somewhere else are to be used. These may be in several different formats but they all have the most significant bit set to one, equating to negative numbers when interpreted as Long values. The special values are:
-
In all versions of Word, a value of 0xFF000000 (decimal -16777216) means to use an Automatic (normally contrasting) colour. There is a Word enumeration constant, wdColorAutomatic, set to this value to facilitate working with it.

A representation of Word’s storage of the code for Automatic Colour -
In ActiveX controls in documents, and in VBA, for UserForm controls, special values for system colours (for some reason lost in the mists of time these are also called OLE Colors) ranging from 0x80000000 to 0x80000018. There are VBA enumeration constants, for example vbTitleBarText or vbButtonFace, available for all of these.

A representation of Word’s storage of the code for a system colour
(this is the Window Background)
-
In Word 2007 — at last, the interesting bit — in Word 2007, new values representing scheme colours.
The most significant byte of these can be considered in two halves – sometimes, rather horribly, called nybbles. The more significant half is set to 0b1101 (0xD or decimal 208) and the less significant half to a different value (from 0b0100 (0x4) to 0b1111 (0xF)) for each of the scheme colours.
The second most significant byte is always set to zero
The remaining two bytes hold two numbers from 255 (0xFF), meaning unchanged, to 0 (0x0), meaning 100%, representing percentages darker (more significant byte) and lighter (less significant byte). The darker and lighter percentages cannot both be specified – the percentage darker is ignored if the percentage lighter is other than 0xFF (lightness unchanged).

A representation of Word 2007’s storage of the code for a Theme Colour (Accent 1)There is a new Enumeration in Word, wdThemeColorIndex, with constants for each of the scheme colours, with values as per the less significant half of the first byte. This enumeration also contains constants for the values 0 to 3, called Dark1, Light1, Dark2, and Light2, which produce the same colours as Text1, Background1, Text2, and Background2 respectively. You will see more of these in Part 2.
Do note, however, that, unlike the enumerations for the other formats of the property, the Color property cannot be set directly to the Theme constants; whichever one you chose would give a very dark red, barely distinguishable from black.
-
Setting Color Properties: some recorded code
To make things a little clearer, try recording some code. Open a document – any document – in Word 2007, select come text, and then use your own favourite way to record a macro. Open the Font Color Dialog (clicking the icon on the Home tab of the Ribbon is probably the easiest way) and click on the “Blue” icon under “Standard Colors”. Next reopen the Dialog and click on the “Blue, Accent 1″ icon under “Theme Colors”. You may notice, incidentally, that standard blue and theme accent blue are different blues, despite the tips. Open the Dialog one last time and click on the “Blue, Accent 1, Lighter 40%” icon, again under “Theme Colors”. Stop recording and switch to the VBA Editor to see your code; it should look something like this:
Sub Colours1()
‘
‘ Colours1 Macro
‘
‘
Selection.Font.Color = 12611584
Selection.Font.Color = -738131969
Selection.Font.Color = -738132071
End Sub
Your first thought is probably that that doesn’t make anything clearer; the decimal numbers generated by the macro recorder serve only to muddy the waters. Well, yes, they do a little but this is just a step on the way. Check out the hex values of those decimal numbers and you’ll see that the code could also be written something like this:
Sub Colours1()
‘
‘ Colours1 Macro
‘
‘
Selection.Font.Color = CLng("&H00C07000")
Selection.Font.Color = CLng("&HD400FFFF")
Selection.Font.Color = CLng("&HD400FF99")
End Sub
Referring back if you need to, you can now see that the first line sets the Color to an RGB value (0x0, 0x70, 0xC0) which is indeed “Blue” (RGB(0, 112, 192)). Also referring back you can see that the second one sets the Color to new scheme colour 4 (which is Accent1) with darkness and lightness both unchanged (0xFF). The third one is the same except that the lightness this time is 0x99. Without giving you a maths lesson, I hope you can see that 0x99/0xFF is the same as 0x9/0xF, decimal 9/15, which is 6/10, or 60%. The way this works is that 40% lighter is held as 60% (=100 - 40).
If you experiment a little with this you’ll soon find you can use any scheme colour and percentage darker or lighter quite easily. For example, this sets the Color to 5% darker than Accent3 (code 6), an option that is not available through the UI.
Sub Colours1()
Selection.Font.Color = CLng("&HD600F3FF")
End Sub
Making a Function of it
So far you have only seen hard-coded hex values, which isn’t the most practical way of working. It would be much better if you wrote some code once, which did all the work and then just re-used it as needed. To write a function like this you need to build up the hex code from its constituent parts, substituting the variable elements according to parameters passed to it.
There are two variables: the scheme colour and the adjustment. From what you have seen so far there are two adjustments, a darkness and a lightness; they are, however, mutually exclusive and, so, a single parameter can easily be used. Without going into any details, if you look at the TintAndShade method of the ColorFormat object you will see that it takes a number between -1 and +1 with negative values representing darknesses and positive values lightnesses. +0.6, for example, means 60% lighter, and -0.25, say, 25% darker. Using the same in this new function doesn’t seem unreasonable.
The following code is just one way to write the desired function. In my own environment I have a user-defined type for the parameters and the constants defined at module level but the end result is the same.
Function SchemeColorProperty(SchemeColor As WdThemeColorIndex, _
TintAndShade As Double) _
As String
‘ Author: Tony Jollans
‘ Date: August 2007
‘ Purpose: Convert a Word 2007 Scheme Color Index and TintAndShade _
value to a hexadecimal string in the format required _
to set a Color Property
Const LightnessUnchanged = "FF"
Const DarknessUnchanged = "FF"
Const HexadecimalPrefix = "&H"
Const SchemeColorIdentifier = "D"
Const UnusedZeroByte = "00"
Dim LightnessOrDarkness As String
If TintAndShade >= 0 Then
LightnessOrDarkness _
= DarknessUnchanged & _
Right$("0" & Hex$((1 - TintAndShade) * &HFF), 2)
Else
LightnessOrDarkness _
= Right$("0" & Hex$((1 + TintAndShade) * &HFF), 2) & _
LightnessUnchanged
End If
SchemeColorProperty = HexadecimalPrefix & _
SchemeColorIdentifier & _
Hex$(SchemeColor) & _
UnusedZeroByte & _
LightnessOrDarkness
End Function
There are a couple of points worthy of note. Firstly that the SchemeColor argument is declared as type WdThemeColorIndex; this means that you will be prompted with a dropdown containing the possible values when you code a call to the function.
Secondly, the calculation with the TintAndShade value. It is first subtracted from 1 and then multiplied by 255 to be in the right single-byte format. Although it is expressed in percentage terms in the UI, it is held, as you have already seen, in 255ths. Lastly you will see that it is forced to always be two characters long; some calculations may produce a single hex-digit, which must have a leading zero added to correctly fit in the string being built.
To use the above function, all you need is a single line of code, for example:
Sub Example1()
Selection.Font.Color _
= CLng(SchemeColorProperty(wdThemeColorAccent1, 0.4))
End Sub
Querying Color Properties
Being able to set Color properties is, of course, only part of the picture. Having set them, you really need a means of querying them as well. I do not propose going into great detail about this outline code which uses the information presented so far to determine the type of Color property found; it issues a brief message for each ‘old’ type and when it finds a ‘new’ value simply passes it to another routine, of which more in a moment, for further examination.
Sub Colours2()
Dim HexString As String
HexString = Right$(String$(7, "0") & Hex$(Selection.Font.Color), 8)
Select Case Left$(HexString, 2)
Case "00"
If HexString = “00C8C67F” Then
MsgBox "The colour of the Selection is not all the same"
Else
MsgBox "The colour is an RGB value:" & vbCr & _
"Red: " & _
CLng("&H" & Mid$(HexString, 7, 2)) & vbCr & _
"Green: " & _
CLng("&H" & Mid$(HexString, 5, 2)) & vbCr & _
"Blue: " & _
CLng("&H" & Mid$(HexString, 3, 2))
End If
Case "FF"
MsgBox "The colour is set to the default of ‘Automatic’"
Case "80"
MsgBox "The colour is an OLE Color"
Case "D4" To "DF"
Call QuerySchemeColor(HexString)
Case Else
MsgBox "The colour is of an unknown type." & vbCr & _
"The code is: 0x" & HexString
End Select
End Sub
Sub QuerySchemeColor(HexString As String)
‘ Here’s where the new and interesting stuff begins
End Sub
This code is not perfect; amongst other things, it has the font Color property hard-coded, but it is merely exemplary. The Case “D4″ to “DF” does perhaps require a little explanation. It relies on the fact that the ASCII values for the upper case letters are greater than the ASCII values for the numbers and, as well as the desired values, will also be satisfied by some characters with ASCII codes in between the numbers and the letters (colon, question mark, etc.) but as these do not occur in the hex strings being checked, it is safe to use in this way in this particular circumstance.
Having already seen how the codes are made up, filling in the code in the QuerySchemeColor routine is relatively easy! The first thing to do is to split the hex string into its four component bytes, after which the zero byte can be ignored. The next step is to interpret the codes.
The scheme colour code, you’ll remember, is the less significant half of the byte and, for presentation really wants converting to English (or your own language). There will be more on the ThemeColorIndex values in Part 2 but, for the moment, the translation of code to text is presented witout further comment.
The lightness and darkness, you’ll also remember, are in 255ths; dividing by 255 and multiplying by 100 gives them in 100ths; lastly subtracting from 100 gives the result as a percentage as normally expressed in the UI. A final tweak adds a comma, if appropriate, and the appropriate word for the shading.
Sub QuerySchemeColor(HexString As String)
Dim SchemeColorByte As String
Dim ZeroByte As String
Dim DarknessByte As String
Dim LightnessByte As String
Dim SchemeColor As Long
Dim Darkness As Long
Dim Lightness As Long
Dim SchemeColorName As String
Dim TintingAndShading As String
SchemeColorByte = Mid$(HexString, 1, 2)
ZeroByte = Mid$(HexString, 3, 2)
DarknessByte = Mid$(HexString, 5, 2)
LightnessByte = Mid$(HexString, 7, 2)
SchemeColor = "&H" & Right$(SchemeColorByte, 1)
Select Case SchemeColor
Case wdThemeColorMainDark1: SchemeColorName = "Dark 1"
Case wdThemeColorMainLight1: SchemeColorName = "Light 1"
Case wdThemeColorMainDark2: SchemeColorName = "Dark 2"
Case wdThemeColorMainLight2: SchemeColorName = "Light 2"
Case wdThemeColorAccent1: SchemeColorName = "Accent 1"
Case wdThemeColorAccent2: SchemeColorName = "Accent 2"
Case wdThemeColorAccent3: SchemeColorName = "Accent 3"
Case wdThemeColorAccent4: SchemeColorName = "Accent 4"
Case wdThemeColorAccent5: SchemeColorName = "Accent 5"
Case wdThemeColorAccent6: SchemeColorName = "Accent 6"
Case wdThemeColorHyperlink: SchemeColorName = "Hyperlink"
Case wdThemeColorHyperlinkFollowed:
SchemeColorName = "Followed Hyperlink"
Case wdThemeColorBackground1: SchemeColorName = "Background 1"
Case wdThemeColorText1: SchemeColorName = "Text 1"
Case wdThemeColorBackground2: SchemeColorName = "Background 2"
Case wdThemeColorText2: SchemeColorName = "Text 2"
Case Else:
SchemeColorName = "Unknown " & SchemeColor
End Select
Lightness = 100 - ("&H" & LightnessByte) / &HFF * 100
Darkness = 100 - ("&H" & DarknessByte) / &HFF * 100
If Lightness = 0 Then
If Darkness = 0 Then
TintingAndShading = “”
Else
TintingAndShading = ", Darker " & Darkness & “%”
End If
Else
TintingAndShading = ", Lighter " & Lightness & “%”
End If
MsgBox "The colour is: " & SchemeColorName & TintingAndShading
End Sub
If you run the Colours2 routine it will call the QuerySchemeColor routine and tell you, in normal UI-speak, what colour the font of your Selection is set to to. Before you do run it, make sure that you have some text selected in your document, and that that text has a theme colour applied to it. You can verify, if you wish, that the output is what you expect for the colour you have chosen.
This post is already far too long, so interested readers are left to adapt this routine to be a function used for whatever purpose they desire, and to add any error checking they deem appropriate.
The logical next step after querying a new Color property is to convert it to an RGB value. This requires knowing more about the new enumerations, the Word one you have already seen and, also, similar Office ones, which you will see next time.
ColorIndex Property Values: yesterday …
ColorIndex properties are really a hangover from a long-gone version of Word but, although wounded, they won’t lie down. They are numbers, from 1 to 16, referring to the limited range of colours that were once all that Word could use. They are not in any way related to the actual colours they indicate and have been arbitrarily assigned; a ColorIndex value of 6, for example, refers to ‘pure’ red, one of 7 to ‘pure’ yellow.
Word still uses ColorIndex values for Highlighting, and for some of the Change Tracking colour settings, features that are still restricted to just 16 colours. Word also maintains ColorIndex values for many other colourable items. If you set the ColorIndex for the font of some text, say, to a value of 6, the text will duly turn red and the Color of the font will be set to the RGB value for red (255, 0, 0). Conversely if you set the Color of the text to the RGB value for red, then its ColorIndex value will be set to 6. Where it gets more interesting is when you set the RGB value of the text to one which does not correspond to a specific ColorIndex value. I don’t know the algorithm used but Word converts the RGB value, somehow, reasonably well, to the ColorIndex value for the nearest colour.
As with Color properties, there are some special ColorIndex values. Zero is used for no special ColorIndex when, for example, the colour of text is set to Automatic, which isn’t a specific colour and doesn’t have a specific index. Also there are special values meaning that the elements being queried do not all have the same ColorIndex. For font colours this value is the same decimal 9999999 as returned for Color but for other elements a value of 65535 (0xFFFF) is returned.
… and today
No changes have been made to ColorIndex properties in Word 2007. It is still the case that ColorIndex values are kept in line with Color RGB values, but no allowance is made for the fact that Color values are not necessarily RGB values any more. When a Color property is set to a new scheme colour, the ColorIndex property is set to the colour, or closest colour, corresponding to the RGB value that would be represented by the low-order 24 bits of the Color property value.
For example, setting the font colour to Accent1 with no adjustment creates, as you have seen, a Color property value of 0xD400FFFF. The low-order 24 bits of this value are 0x00FFFF; if it were an RGB color rather than a scheme colour, this would be yellow and the ColorIndex is thus set to 7, wdYellow. If, instead of Accent1, you had chosen Accent2, or any other scheme colour, the ColorIndex would still have been set to wdYellow; in fact, more often than not it is set to wdYellow, and when it isn’t yellow it is either bright green (for dark variations of scheme colours) or red (for light variations of scheme colours).
Although I suspect that little use is made of ColorIndex properties anyway, this lack of change essentially consigns them to history.
Finally
There is much more still to come, particularly full details of the new enumerations, and the wonderful world of ColorFormat objects.
This post has been extracted and adapted from a longer, currently unfinished, article that will be maintained on my new website at www.wordarticles.com.
Tony Jollans, August 2007
Posted in VBA, Word | 1 Comment »


