Insight Statistical Consulting Ltd
John Kirkpatrick MSc BSc (Hons) CStat CSci

How to decode a style definition

If you want to really understand how style definitions work, there's no subsitute for rolling your sleeves up and taking alook at one of the major styles provided by SAS Institute. In this tutorial, I'm going to take a look at the mother of all style definitions, style.default. (I use the word "mother" quite deliberately. As we'll see, it's literally as well as figuratively true.)

If all you want to do is see what style element controls the rendering of a particular part of your output, there's a much simpler solution. There's no need to go through the rigmarole described here.

Take, for example, this trivial example:

ODS RTF FILE="tut0001,rtf"; PROC REPORT DATA=SASHELP.Class; COLUMN Name Age Height Weight; DEFINE Name/DISPLAY; DEFINE Age/DISPLAY; DEFINE Height/DISPLAY; DEFINE Weight/DISPLAY; RUN; ODS RTF CLOSE;

What steps does SAS go through in deciding to print the data in Times Roman 10pt?

The first step is to find out which style has been used to render your output. In our case, it's the styles.RTF style.

I know that's a pretty obvious choice, but does anyone know where that is actually stated in the documentation? I'd love to know....

Open the styles.RTF style in the style browser, get your hands on a print out, or simply click here.

At first glance, this listing looks horrible. It’s long, complicated and not well laid out. However, there is a basic underlying structure that simply repeats itself. We can continually apply this structure to discover what the definition tells us. The entire listing has the following structure:

proc template; define style styles.default; <style element 1> <style element 2> . . . <style element n> end;

and each style element definition has the following form:

style <style name> <from <existing style name>>/ <comment> <style attribute 1> = <value 1> <style attribute 2> = <value 2> . . . <style attribute n> = <value n>;

The FROM clause of the style element definition defines the parent/child relationship, if any, for the style element. A child style element inherits all the existing style attributes from its parent (and, indirectly from its grandparent and great-grand parent etc). The remainder of the definition adds to or modifies the inherited attributes. This is the most useful place to start, and we can decode the genealogy defined by the different FROM clauses of the various style elements to construct the family tree for the default styles shown here. It shows for example, that the pages style is the child of the document style and the grandchild of the contents style.

The first section of the style definition is particularly difficult to get a handle on as it defines a whole series of abstract styles which are rarely, if ever, used directly, but which simplify the process of making global changes to the practically useful styles that are defined later on.

To help us see the practical effects of the style definitions, we’ll take a closer look at the data style and its family tree. The data style is the style that defines the look of all cells in a table produced by the TABULATE procedure, amongst many other things.

The data style is defined like this:

style Data from Cell "Default style for data cells in columns." / foreground = colors('datafg') background = colors('databg');

The cell style is simply a placeholder that, in itself, does nothing:

style Cell from Container "Abstract. Controls general cells.";

whereas the container style does a little bit more:

style Container "Abstract. Controls all container oriented elements." / font = Fonts('DocFont') foreground = colors('docfg') background = colors('docbg');

Notice that we have two definitions for the foreground and background style elements in this hierarchy. Which ones are actually used? The rule is very simple: the definitions in the child style override those in the parent style.

So, overall, the data style looks like this:

font = Fonts('DocFont') foreground = colors('datafg') background = colors('databg');

It’s obvious that these attributes will define the font, the foreground (i.e. text) colour and the background colour that will be used to print data that uses the data style, but we still don’t know what font and colours will be used. However, the final step is straightforward. We need to notice that Fonts is itself a style element, with the following definition:

style fonts "Fonts used in the default style" / 'TitleFont2' = ("Arial, Helvetica, Helv",4,Bold Italic) 'TitleFont' = ("Arial, Helvetica, Helv",5,Bold Italic) 'StrongFont' = ("Arial, Helvetica, Helv",4,Bold) 'EmphasisFont' = ("Arial, Helvetica, Helv",3,Italic) 'FixedEmphasisFont' = ("Courier",2,Italic) 'FixedStrongFont' = ("Courier",2,Bold) 'FixedHeadingFont' = ("Courier",2) 'BatchFixedFont' = ("SAS Monospace, Courier",2) 'FixedFont' = ("Courier",2) 'headingEmphasisFont' = ("Arial, Helvetica, Helv",4,Bold Italic) 'headingFont' = ("Arial, Helvetica, Helv",4,Bold) 'docFont' = ("Arial, Helvetica, Helv",3);

One of the style attributes in this definition is docFont, with the value ("Arial, Helvetica, Helv",3) . This tells SAS to use either the Arial font, the Helvetica font or the Helv font using a relative font size of 3.

The actual font used will depend on the order in which the fonts are stored on the computer that runs the SAS job. SAS will use the first one in the list that it finds. Font sizes that are specified without a unit are relative sizes, and SAS will choose an actual size that it feels is appropriate, in a manner similar to a web browser interpreting HTML code. Relative font sizes are in the range 1 to 7. Alternatively, you can force SAS to use an exact size, by specifying a unit. Valid units are: cm, in, mm, pt (points: 1 pt is equal to 1/72 of an inch) and px (pixels, the size of which depend on the target device). You can also specify font attributes such as colour, weight and style. See the Online Documentation for more details.

We can use a similar process to decode the foreground and background colours, albeit with an additional step. The colors style is defined like this:

style colors "Abstract colors used in the default style" / 'headerfgemph ' = color_list ('fgA2') 'headerbgemph' = color_list('bgA2') 'headerfgstrong ' = color_list('fgA2') 'headerbgstrong' = color_list('bgA2') 'headerfg' = color_list('fgA2') 'headerbg' = color_list('bgA2') 'datafgemph' = color_list('fgA1') 'databgemph' = color_list('bgA3') 'datafgstrong' = color_list('fgA1') 'databgstrong' = color_list('bgA3') 'datafg' = color_list('fgA1') 'databg' = color_list('bgA3') 'batchfg' = color_list('fgA1') 'batchbg' = color_list('bgA3') 'tableborder' = color_list('fgA1') 'tablebg' = color_list('bgA1') 'notefg' = color_list('fgA') 'notebg' = color_list('bgA') 'bylinefg' = color_list('fgA2') 'bylinebg' = color_list('bgA2') 'captionfg' = color_list('fgA1') 'captionbg' = color_list('bgA') 'proctitlefg' = color_list('fgA') 'proctitlebg' = color_list('bgA') 'titlefg' = color_list('fgA') 'titlebg' = color_list('bgA') 'systitlefg' = color_list('fgA') 'systitlebg' = color_list('bgA') 'Conentryfg' = color_list('fgA') 'Confolderfg' = color_list('fgA') 'Contitlefg' = color_list('fgA') 'link2' = color_list('fgB2') 'link1' = color_list('fgB1') 'contentfg' = color_list('fgA2') 'contentbg' = color_list('bgA2') 'docfg' = color_list('fgA') 'docbg' = color_list('bgA');

(The highlighting is mine.)

The color_list style is similarly defined as:

style color_list "Colors used in the default style" / 'fgB2' = cx0066AA 'fgB1' = cx004488 'fgA4' = cxAAFFAA 'bgA4' = cx880000 'bgA3' = cxD3D3D3 'fgA2' = cx0033AA 'bgA2' = cxB0B0B0 'fgA1' = cx000000 'bgA1' = cxF0F0F0 'fgA' = cx002288 'bgA' = cxE0E0E0;

So we see that the foreground = datafg = fgA1 = cx000000 and background = databg = bgA3 = cxD3D3D3. cx000000 and cxD3D3D3 are hexadecimal RGB colour definitions. The first two digits define the red component, the middle two the green component and the final two the blue component. Each component is defined on a scale of 0 to FF in Hex (or 0 to 255 in base 10). cx000000 is black and cxD3D3D3 is a pale grey. So, the definition finally decodes as

font = ("Arial, Helvetica, Helv",3) foreground = cx000000 background = cxD3D3D3;

or “use Arial in a medium point size in black on a light grey background”.

That’s all well and good, but if you look back at the original table, you’ll see that the table does not look anything like this: for a start the fonts used are Times Roman, not Arial. What’s happened?

The answer is that the ODS RTF destination uses the STYLES.RTF style by default, not the STYLES.DEFAULT style. However, the STYLES.RTF style is a piece of cake compared to the STYLES.DEFAULT style. Here it is:

proc template; define style Styles.Rtf; parent = styles.printer; style titleAndNoteContainer from titleAndNoteContainer / outputwidth = _undef_; replace cell from container / linkcolor = colors('link2'); style table from table / cellpadding = 3pt; style batch from batch / rules = none frame = void cellpadding = 0pt cellspacing = 0pt; replace Body from Document "Controls the Body file." / bottommargin = 0.25in topmargin = 0.25in rightmargin = 0.25in leftmargin = 0.25in; end; run;

The PARENT statement means we also need to look at the definition of styles.printer as well:

proc template; define style Styles.Printer; parent = styles.default; replace fonts / 'TitleFont2' = ("Times Roman",12pt,Bold Italic) 'TitleFont' = ("Times Roman",13pt,Bold Italic) 'StrongFont' = ("Times Roman",10pt,Bold) 'EmphasisFont' = ("Times Roman",10pt,Italic) 'FixedEmphasisFont' = ("Courier",9pt,Italic) 'FixedStrongFont' = ("Courier",9pt,Bold) 'FixedHeadingFont' = ("Courier",9pt,Bold) 'BatchFixedFont' = ("SAS Monospace, Courier",6.7pt) 'FixedFont' = ("Courier",9pt) 'headingEmphasisFont' = ("Times Roman",11pt,Bold Italic) 'headingFont' = ("Times Roman",11pt,Bold) 'docFont' = ("Times Roman",10pt); style Table from Output / rules = ALL cellpadding = 4pt cellspacing = 0.25pt borderwidth = 0.75pt; replace color_list "Colors used in the default style" / 'link' = blue 'bgH' = grayBB 'fg' = black 'bg' = white; replace Body from Document "Undef margins so we get the margins from the printer or SYS option" / bottommargin = _undef_ topmargin = _undef_ rightmargin = _undef_ leftmargin = _undef_ pagebreakhtml = html('PageBreakLine'); replace colors "Abstract colors used in the default style" / 'headerfgemph' = color_list('fg') 'headerbgemph' = color_list('bgH') 'headerfgstrong' = color_list('fg') 'headerbgstrong' = color_list('bgH') 'headerfg' = color_list('fg') 'headerbg' = color_list('bgH') 'datafgemph' = color_list('fg') 'databgemph' = color_list('bg') 'datafgstrong' = color_list('fg') 'databgstrong' = color_list('bg') 'datafg' = color_list('fg') 'databg' = color_list('bg') 'batchbg' = color_list('bg') 'batchfg' = color_list('fg') 'tableborder' = color_list('fg') 'tablebg' = color_list('bg') 'notefg' = color_list('fg') 'notebg' = color_list('bg') 'bylinefg' = color_list('fg') 'bylinebg' = color_list('bg') 'captionfg' = color_list('fg') 'captionbg' = color_list('bg') 'proctitlefg' = color_list('fg') 'proctitlebg' = color_list('bg') 'titlefg' = color_list('fg') 'titlebg' = color_list('bg') 'systitlefg' = color_list('fg') 'systitlebg' = color_list('bg') 'Conentryfg' = color_list('fg') 'Confolderfg' = color_list('fg') 'Contitlefg' = color_list('fg') 'link2' = color_list('link') 'link1' = color_list('link') 'contentfg' = color_list('fg') 'contentbg' = color_list('bg') 'docfg' = color_list('fg') 'docbg' = color_list('bg'); end; run;

From these two definitions, we see that the definition of docFont in styles.default is redefined to be ("Times Roman",10pt) in styles.printer, which is then inherited by styles.rtf. Similarly foreground and background are redefined as color_list(‘bg’) and color_list(‘fg’), which as before, decode as white and black respectively.

This gives us, at last, the format that we actually see in the table:

Font = ("Times Roman",10pt) Foreground = black Background = white