Changing fonts in container cells

by Joseph Winchester

In a previous article I described how to add color on a cell by cell basis for the VisualAge Smalltalk container details part. This was done by creating a decorator class EmphasizedRenderable, instances of which were used to wrapper a cell's contents. As well as aggregating the decorated object the EmphasizedRenderable had instance variables to store background and foreground color. The EmphasizedRenderable specialized the extended widgets framework method ewDrawUsing: anEwRenderContext which is used to draw the an object inside a container details cell. Here the background and foreground color specified in the EmphasizedRenderable object were used to manipulate the colors of the render context and enable the cells' contents to be drawn in different colors.

One of the motivations behind creating the EmphasizedRenderable was to allow the programmer to be able to bring certain cells to the user's attention. Color is one technique that can be used for this, while another is to show a user specific cells in different font styles. Examples of this could be to show an overdue item in bold, or to show a column of numbers in italics. In this article I show how to extend the EmphasizedRenderable object to allow it to store formatting information for bold, italics and pitch size.

Principle of least astonishment

A good way to write Smalltalk code is by the principle of least astonishment. This states that you should never be surprised by what a method does, it should be obvious by its name alone. To change a String to be drawn with bold or italic emphasis the most obvious method names are #setBold and #setItalic. Likewise to set the pitch a suitable method name would be #setPitch: anInteger. The String object needs to wrapper itself and then redirect the protocol through to the wrapper.

 String>>#setBold
  ^EmphasizedRenderable new
   contents: self ;
   setBold

 String>>#setItalic
  ^EmphasizedRenderable new
   contents: self ;
   setItalic

 String>>#setPitch: anInteger
  ^EmphasizedRenderable new
   contents: self ;
   setPitch: anInteger

For unbound polymorphism, i.e. any object is able to represent itself with a specific font style, an implementation should be introduced on Object that converts itself to a string to which it redirects the message.

 Object>>#setBold
  ^self convertToDisplay setBold

 Object>>#setItalic
  ^self convertToDisplay setItalic

 Object>>#setPitch: anInteger
  ^self convertToDisplay setPitch: anInteger

This technique of using polymorphism and delegation together in Smalltalk is a powerful one because it defines a single protocol that only one object has to know how to implement, all others just need to find a path to this object type. In our case EmphasizedRenderable is the end of the line and the style information should be stored as state on the object.

Extra instance variables will need to be added to EmphasizedRenderable to store the new font style information. These are in addition to the colors that were used in the original EmphasizedRenderable.

 AbtObservableWrapper subclass: #EmphasizedRenderable
  instanceVariableNames: 'foregroundColor backgroundColor isBold isItalic pitch'
  classVariableNames: ''
  poolDictionaries: ''

Note that in VisualAge version 4 the instance variables for foregroundColor and backgroundColor are not required.

The get methods should set the state on the object. In addition there should be unary methods that query whether the object has font style information, i.e. #isBold, #itItalic and #hasPitch.

 EmphasizedRenderable>>#setBold
  isBold := true

 EmphasizedRenderable>>#isBold
  ^isBold == true

 EmphasizedRenderable>>#setItalic
  isItalic := false

 EmphasizedRenderable>>#hasItalic
  ^isItalic == true

 EmphasizedRenderable>>#setPitch: anInteger
  pitch := anInteger

 EmphasizedRenderable>>#hasPitch
  ^pitch notNil

Note that these six methods are not the standard getter and setter methods. Inexperienced Smalltalkers often always add a unary get method and a single keyword set method for each variable. More experienced Smalltalkers however hide the state as much as possible and write a public interface that reflects the behavior of the object rather than just surfacing its data. For example, a set method #isBold: aBoolean is not as clear as method #setBold and, if we desired, a method called #setNormal. A good object's public interface should reflect the intention of what behavior we want to surface to client objects rather than windows over the object's state.

Rendering the renderable

Now we have the instance of EmphasizedRenderable we have to make sure that it uses the font style information when it is being drawn in a container details cell. The extended widgets framework method for drawing an object is ewDrawUsing: anEwRenderContext. In the previous article this method was specialized as below to allow an EmphasizedRenderable to draw itself in the specified colors.

 EmphasizedRenderable>>#ewDrawUsing: aRenderContext
  "Store the current foreground color of the renderable context,
  change it to be our background color and fill a rectangle.  Set the 
  color to be our foreground color, render our contents, and 
  finally restore the render context color back to its original value"
  | oldColor |
  oldColor := aRenderContext getForeground.
  backgroundColor notNil ifTrue: [ 
    aRenderContext
      foregroundColor: backgroundColor ;
      fillCurrentRectangle ;
      foregroundColor: oldColor ].
   foregroundColor notNil ifTrue: [
     aRenderContext foregroundColor: foregroundColor ].
  self contents ewDrawUsing: aRenderContext.
  aRenderContext foregroundColor: oldColor

This method works by drawsing a rectangle in the background color of the EmphasizedRenderable and then drawing the EmphasizedRenderable's contents in the specified foreground color. The color is stored at the start of the method so the render context can be reset at the end of the method. A similar technique will be used for font styles. We will store the original font, manipulate it, and then set it back again. The method should be changed as follows.

 EmphasizedRenderable>>#ewDrawUsing: aRenderContext
  "Store the font and current foreground color of the renderable context.
  Alter the font if required to be bold or italic.
  Change the foreground color to be our background color and fill 
  a rectangle.  Set the color to be our foreground color, render our contents, and 
  finally restore the render context color back to its original value"
  | oldColor oldFontStruct |
  oldFontStruct := aRenderContext fontStruct.
  oldColor := aRenderContext getForeground.
  self modifyFontOfRenderContext: aRenderContext.
  backgroundColor notNil ifTrue: [ 
   aRenderContext
    foregroundColor: backgroundColor ;
    fillCurrentRectangle ;
    foregroundColor: oldColor ].
  foregroundColor notNil ifTrue: [ 
   aRenderContext 
    foregroundColor: foregroundColor ].
  self contents ewDrawUsing: aRenderContext.
  aRenderContext foregroundColor: oldColor.
  aRenderContext setFont: oldFontStruct.

Before writing the linchpin method EmphasiedRenderable>>modifyFontOfRenderContext: aRenderContext a bit of information on how IBM Smalltalk fonts work is needed.

In VisualAge version 4 that does not have the need for color to be stored with the EmphasizedRenderable object the method only needs to concern itself with font information.

 EmphasizedRenderable>>#ewDrawUsing: aRenderContext
  "Store the current font, manipulate it, and set it back again.
  | oldFontStruct |
  self modifyFontOfRenderContext: aRenderContext.
  aRenderContext setFont: oldFontStruct.

IBM Smalltalk Fonts

There are two important classes to that make up fonts in IBM Smalltalk, CgFontStruct and CgFont. It is instances of CgFontStruct that hold descriptive information about a font such as the font name, height, pitch, and other dimensions. The instance of EwRenderContext that is the argument to ewDrawUsing: will return its font struct with the method fontStruct. CgFontStruct instances however are difficult objects to interpret and manipulate. For example inspect the result of

 CgDisplay default defaultFontStruct

Working out the name of the font or its pitch size is hard to do just by looking at theobject. What is required is for the font structure to be decomposed into a more meaningful description. Fortunately thiat can be done by sending the CgFontStruct the method name. For example, sending the method name to the result of the expression above will return the name of the system default font.

 '-microsoft-system-bold-r-normal-sans serif-16-120-96-96-p-70-iso8859-1'

Note that this is what my PC returns but this is machine specific. The default font varies depending on the machine setup and operating system.

The detailed breakdown of how to interpret this string can be found in the CommonGraphics chapter of the IBM Smalltalk programmer's guide ( ref 2 ). The information that we are interested in is weight ( currently bold ), slant ( currently r for Roman ), and pitch ( currently a point size of 120 ). To convert this to an italic font that was not bold we could change the substring 'bold' to 'normal' and 'r' to 'i'. Then this could be converted back to an instance of CgFontStruct and given to the render context. The technique that we will use is actually almost exactly this except that there are a few things that are made easy for us and a few that are made a little more tricky.

Logical Fonts

The desired result is to manipulate the string containing all the font information and substitute the various substrings for our desired font style changes. A novice Smalltalker would probably hack their way through some grungy collection protocol and manage to splice the various sections of the string apart and back together again with new innards. A more experienced Smalltalker might create an object that decomposed the string into its constituent parts, had public protocol to allow each part to be easily set, and could then surface up a reconstituted string. Fortunately there is already an object that does exactly this called CgLogicalFontDescription. This is instantiated with the class side method #name: aFontName and returns itself as a font name string with the unary method #name.

As an example, to get the name of the default system font and convert it to italics the following code can be evaluated.

 | defaultFontName |
 defaultFontName := CgDisplay default defaultFontStruct name.
 ( ( CgLogicalFontDescription name: defaultFontName ) slant: 'i' ) name.

Rather than have to use the methods slant: aString I prefer to carry forward the protocol decided on earlier of #setBold, #setItalic and #setPitch: anInteger. Not only is this polymorphic but unary methods are almost always clearer than keyword methods because they imply more directly the intent of the sender as well as being better encapsulated. CgLogicalFontDescription should be extended and have the following methods added

 CgLogicalFontDescription>>#setBold
  self weight: 'bold'
 CgLogicalFontDescription>>#setItalic
  self slant: 'i'

To set the pitch the breakdown of the font structure's name needs to be looked at in more details. The name for the default font is

 '-microsoft-system-bold-r-normal-sans serif-16-120-96-96-p-70-iso8859-1'

The 120 represents the point size of the font. This is measured in 1/10ths of 1/72ths of an inch. Almost always the point is surfaced to the user as just in 1/72ths of an inch, i.e. the 120 is a 12 point font. The 70 in the string represents the pixel size of the average width of a character in the font character set, and the 16 the font vertical pixel height. Both the pixel width and height will most likely be invalid once the point size has changed. To indicate this we will set them to the wildcard character '*'. Later this will be used by the system to determine the closest matching font to the one we desire.

 CgLogicalFontDescription>>#setPitch: anInteger
 "The point size is ten times the pitch and held as a string
 Each font description knows its pixel size and average width. 
 These will not necessarily be valid any more with a new pitch
 and should be set to wild characters to force a lookup of closest
 match in the CgDisplay"
 self 
  points: ( anInteger * 10 ) printString ;
  averageWidth: '*' ;
  pixels: '*'

Note that encapsulation really paid dividends with the #setPitch: anInteger method. Clients now only have to send one method to instances of CgLogicalFontDescription and are protected from knowing the internals of pixel height, width and point size.

Setting the Renderable font

The next step is to implement the method EmphasizedRenderable>>#modifyFontOfRenderContext: aRenderContext. This is the one that is called within the #ewDrawUsing: aRenderContext method described earlier. This method needs to do several steps : first to retrieve the logical font description from the EwRenderContext object, then to manipulate it using the bold, italics and pitch information, and lastly to set the font of the EwRenderContext to use this new manipulated font. EwRenderContext will work with an instance of CgFontStruct which can be retrieved from a font name using a CgDisplay instance. The method could be written as follows.

 EwRenderContext>>#modifyFontOfRenderContext: anEwRenderContext
  "Apply the font emphasis to the render context"
  | logicalFontDescription |
  logicalFontDescription := CgLogicalFontDescription
   name: anEwRenderContext fontStruct name.
  self isBold ifTrue: [ logicalFontDescription setBold ].
  self isItalic ifTrue: [ logicalFontDescription setItalic ].
  self hasPitch ifTrue: [ logicalFontDescription setPitch: pitch ].
  anEwRenderContext fontStruct: 
   ( CgDisplay default loadFont: logicalFontDescription )

I don't like this method for a number of reasons. First is that it does not read well as it has too much diverting code to do with casting between the various font structures in it and, more importantly, it is not using good behavior encapsulation. To get the font description we ask the render context it for its font structure and then do the conversion inline. The mechanics of how to convert between the two should not be surfaced to the method above which has one purpose - to modify the font of the render context, and not to expose the nuances of casting between various font classes. Likewise we should be able to give the render context a font description in the last line without having to expose distracting font conversion code. The changed method could be coded as follows.

 EwRenderContext>>#modifyFontOfRenderContext: anEwRenderContext
  "Apply the font emphasis to the render context"
  | logicalFontDescription |
  logicalFontDescription := anEwRenderContext logicalFontDescription
  self isBold ifTrue: [ logicalFontDescription setBold ].
  self isItalic ifTrue: [ logicalFontDescription setItalic ].
  self hasPitch ifTrue: [ logicalFontDescription setPitch: pitch ].
  anEwRenderContext setFont: logicalFontDescription.

This is much clearer. We now have to extend EwRenderContext to be able to give us the font description.

 EwRenderContext>>#logicalFontDescription
  ^CgLogicalFontDescription name: self fontStruct name.

This method however looks to me as though it still breaks encapsulation principles. Each time a programmer has to convert a font structure to a font description they have to have the knowledge of how to do the conversion. It is not the responsibility of a render context to know how to do this - it is the font structure object itself. The code would be better written as follows.

 EwRenderContext>>#logicalFontDescription
  ^self fontStruct asLogicalFontDesription
 CgFontStruct>>#asLogicalFontDescription
  ^CgLogicalFontDescription name: self name

The next method to write is EwRenderContext >>#setFont: aFontDescription. EwRenderContext has a graphics context that is the actual object whose font finally needs to be manipulated. Following the earlier desire to write behavior on the objects it belongs we should defer the work off to the correct object, namely the graphics context.

 EwRenderContext>>setFont: aFontObject
  ^self gc: setFont: aFontObject
 CgGC>>#setFont: aFontObject
  ^aFontObject setForGC: self.

The last method is turning around and asking the argument to do the manipulation of the font for the graphics context. This technique of double dispatch is a powerful way to program because it offers the ability to softly type the method argument. aFontObject can be any object we desire as long as it can respond to #setForGC: aCgGC. For example, if we wanted to set the render context's font to a font directly or a font structure, or even a string or symbol representing a logical font description name, all that is required is to implement the #setForGC: method. In the previous article I used double dispatch for setting the foreground color of the render context and allowing both a color string and an color palette integer to be the argument to a single method. The rule I use when implementing double dispatch is to ask the question

"Who should have the smarts to change an object, the receiver or the argument ?"

In the case of CgCG>>#setFont: aFontObject it is the font object that is going to work on the CgGC and it should therefore be responsible for doing the work

Font description To Font object

To code the method #setForGC: on the font description we need to convert between the font description and an actual CgFont instance. The CgFont is the object that can be used to actually manipulate the font of the graphics context at it actually represents a loaded operating system font.

 CgLogicalFontDescription>>#setForGC: aCgGC
  aCgGC setFont: self asCgFont

Note that up until now we have done a good job at deferring doing any work. We are just passing the buck between objects until we have homed in on the real problem to solve, converting a font description to a font object.

To convert between a font description and a font the CgFont has to actually be loaded into a display. This can be done with the following code.

 CgLogicalFontDescription>>#asCgFont
  ^CgDisplay default loadFont: self name.

While this method works there are two problems with it. First is that loading a font takes time and we will be doing it repeatedly each font description. This means that for 20 container cells that all have the same font description information 20 CgFonts will be loaded and created. The second problem is that each CgFont not only takes time to load but actually occupies operating system memory. This memory must be explicitly freed up when the font is no longer required by sending the font the method #unloadFont. Both of these problems would be solved by creating a cache that associated a font name with a CgFont instance and was able to return the cached instance of else load in a new font if required.

Fortunately such a cache exists in the class AbtBasicView. The method can be changed as follows.

 CgLogicalFontDescription>>#asCgFont
  "Return the font cached in the class variable in AbtBasicView.
  This is to minimize fonts created and ensure that fonts are freed
  up correctly when the application closes"
   ^( AbtBasicView fontStructNamed: self name ) font

Implementing the Container details code

In the previous article I created a subclass of the container details column. This was necessary to get a callback at the correct point when the cell's contents have been converted from a domain object to a string to allow the EmphasizedRenderable to be created. This was done by making a connection from the event cellEmphasisRequested on JrwColoredContainerDetailsColumn to a script. In the example given before a collection of JrwPerson class was listed in the container and the requirement was that their name was set to blue if they were male and red if they were female. For an example using font style we could choose that their name should be drawn in bold if they are female and 10 point italics if they are male. This would be done by connecting the cellEmphasisRequested event of the column showing name to a script containing a single keyword method on the view. An instance of EwCellValueCallbackData is the object that is used as the argument to the method. We will send the argument object the same methods that were decided on earlier to manipulate style, namely #setBold. #setItalic and #setPitch:.

 JrwAppBldrViewSubclass>>#fullNameCellValueRequested: anEwCellValueCallbackData
  anEwCellValueCallbackData item isMale
   ifFalse: [ anEwCellValueCallbackData setBold ]
   ifTrue: [ anEwCellValueCalllbackData setItalic ; pitch: 10 ]

The EwCellValueCallbackData object is holding the cell's contents as its value. This should be retrieved and have the style method forwarded to it to instantiate the EmphasizedRenderable object, and then replaced as the new value

 EwCellValueCallbackData>>#setBold
  self value: self value setBold
 EwCellValueCallbackData>>#setItalic
  self value: self value setItalic
 EwCellValueCallbackData>>#setPitch: anInteger
  self value: self value setPitch: anInteger

This completes the work required to allow the programmer to specify font style on a cell by cell basis.

Issues

In Version 4 of VisualAge IBM built the ability to set color on a cell by cell basis by adding instance variable get and set methods directly to the EwCellValueCallback object itself, obviating the need for using EmphasizedRenderable to manipulate color. The downloadable code has versions that use EmphasizedRenderable to color in version 3 and the IBM solution for version 4, with font style for both.

Note that the subclass of AbtContainerDetailsColumn with the event cellEmphasisRequested was only required because the callback cellValueRequested occurs before conversion has been done by the converter object in the view. If the cell is only containing a string object the IBM supplied part AbtContainerDetailsColumn can be used in place of JrwColoredContainerDetailsColumn and the cellValueRequested can be used safetly.

When a font pitch is change the cell's height does not change. This works well when one has to reduce the size of a cell's data but if the data is going to be drawn in a larger font one should be aware that it might have the top clipped off by the neighboring cell.

One thing I had tried to do in this article is to stress the importance of responsibility driven design. Good object oriented programming techniques should be practices every time you write Smalltalk and the dividends will pay sooner or later. Objects should be designed around a behavior based public interface and delegation should be used as much as possible to make the work get done in the object where it belongs. If an object cannot be found that should have responsibility for a piece of work then create a new one. Method names will become clearer and the Smalltalk should almost become self documenting.

I hope you have found this article useful and that it shows the power of the renderable drawing protocol in the ExtendedWidgets framework, as well as given some useful information as to how fonts work in IBM Smalltalk. I have only focussed on manipulating strings but a renderable object can be anything at all as long as it conforms to the correct protocol. In a future article I hope to show how icons can be used as renderable objects to be able to display picture as well as text in a container cell. I wellcome all feedback

Enjoy the article? Subscribe to Eye on Objects!

Joe Winchester has been working with VisualAge for Smalltalk since 1994. He is currently working for IBM as a member of the VisualAge development team."

Home Page