Deviceindependent Drawing
Device-independent drawing is largely conducted by the cExample-HelloView class. Here is its declaration:
class cExampleHelloView : public cBase {
public:
// construct/destruct static cExampleHelloView* NewL();
~cExampleHelloView();
// Settings void SetTextL(const TDesC& aText); void SetFullRedraw(TBool aFullRedraw);
// Draw void DrawInRect(const MGraphicsDeviceMap& aMap, CGraphicsContext& aGc, const TRect& aDeviceRect, CFont* aFont) const;
private:
void ConstructL(); private:
HBufC* iText; TBool iFullRedraw;
Firstly, note that it's derived from CBase, not CCoeControl. No CCoeControl-derived class can be device-independent, because controls are heavily tied to the screen. The drawing code is located in the DrawInRect () function, which takes a device map, a graphics context, and a rectangle within which to draw. We'll now look at the drawing code in detail.
DrawInRect() can be divided into two sections:
■ drawing a box around the text.
Drawing a box
It's easiest to start by looking at the part that draws the box:
void CExampleHelloView::DrawInRect(const MGraphicsDeviceMap&
aMap,CGraphicsContext& aGc, const TRect& aDeviceRect, CFont* aFont) const
//Draw text
//Allocates a device-independent size for the box to be drawn around the text.
TSize boxInTwips(1440,288); // 1" x 1/5" surrounding box //Converts twips to pixels, using iZoomFactor, to get a box of
1" x 1/5" TSize boxInPixels;
boxInPixels.iWidth=aMap.HorizontalTwipsToPixels(boxInTwips.iWidth);
boxInPixels.iHeight=aMap.VerticalTwipsToPixels(boxInTwips.iHeight); // draws the box
TRect box( //this creates a TRect using boxInPixels
TPoint(
aDeviceRect.Center().iX -
boxInPixels.iWidth/2, aDeviceRect.Center().iY -
boxInPixels.iHeight/2
) , boxInPixels); aGc.SetBrushStyle(CGraphicsContext::ENullBrush); aGc.SetClippingRect(aDeviceRect); aGc.SetPenColor(KRgbDarkGray); aGc.DrawRect(box); box.Grow(1,1); aGc.SetPenColor(KRgbBlack);
aGc.DrawRect(box); //this makes the box 2 pixels thick
The purpose of this code is to draw a box of a device-independent size. The box is slightly fancy - it is two pixels wide, dark gray on the 'inside', and black on the 'outside'.
All graphics context-drawing functions are specified in pixels. So, before I can draw the rectangle, I have to convert the size I wanted, specified in twips, to a size in pixels. I do this using the twips-to-pixels functions of the graphics device map. Remember that a Map ultimately uses the screen device, which is how it can get the information about the number of pixels to a twip.
Then I have a problem: I can't be sure that this surround rectangle is actually contained entirely within aDeviceRect, and I should not draw outside aDeviceRect. There is no guarantee, either on screen or another device, that drawing will be clipped to aDeviceRect. But because I need that guarantee here, I set up a clipping rectangle explicitly. Important
Even device-independent drawing code must take device realities into account.
In Chapter 9, we saw that we had to take rounding errors into account when sizing the grid for the Solo Ship's fleet view. Here, I am taking device realities into account in a different way: although I calculate the size of the surrounding rectangle beginning with twips units, I do the expansion explicitly in pixels. Whatever display I draw this rectangle to, I want it to consist of a two-pixel border - two lines spaced apart by a certain number of twips would overlap at small zoom states, and be spaced apart at large zoom states.
Getting a font
I want to draw the message in 12-point Swiss bold text. '12-point Swiss bold' is a device-independent way of specifying a font. What I need to do is get a device-dependent font that meets this specification, taking into account the zoom state.
|
Class |
Description |
|
TFontSpec |
A device-independent font specification, supporting a name, height and style. The style includes italic/normal, bold/normal, and superscript/subscript/normal attributes. Other font-related attributes, such as color, underline, and strikethrough, are implemented algorithmically by drawing functions. |
- Figure 15.4
We could allocate (and release) the font along with the rest of the size- and target-independent code in DrawInRect(~) . However, Draw- InRect(~) is not a leave function, and should not be, as it is called from the hello control Draw (~) function, yet font allocation could fail and might leave.
It is possible to get around the above problem using a trap harness, and thereby allocate the font in DrawInRect (~). We don't do this as it is not a good practice and it is also bad practice to allocate resources while drawing, so it would make the code unsuitable for large-scale use. Therefore, I have found another place to allocate and release fonts. This is in CExampleHelloControl::SetZoomAndDeviceDependentFontL(~) . This is an appropriate location for the font allocation as it is called on class construction and as a result of zooming in and out, and the size-dependent font is expected to change when the user zooms in or out (and at no other time).
Here's the code:
void CExampleHelloControl::SetZoomAndDeviceDependentFontL(TInt aZoomFactor) {
//Set zoom factor
//Allocate a device dependent font for drawing iZoomFactor.ReleaseFont(iFont);
iFont=NULL;
_LIT(fontName, "SwissA");
TFontSpec fontSpec(fontName, 240); // font size is 12 point fontSpec.iFontStyle=TFontStyle(EPostureUpright, EStrokeWeightBold,
EPrintPosNormal);
User::LeavelfError(iZoomFactor.GetNearestFontInTwips(iFont, fontSpec));
The key function here is GetNearestFontinTwips(), a member function of MGraphicsDeviceMap. You pass a TFontspec to this function, and you get back a pointer to a device-dependent font (a CFont*).
The mapping from TFontspec to CFont* is ultimately handled by a graphics device (though the font specification may be zoomed by a zoom factor). Once you have a CFont*, you can only use it on the device that allocated it - more precisely, you can only use it for drawing through a graphics context to the device that allocated it.
This function usually finds a match, but in the unlikely case that it doesn't, it returns an error code. I propagate any error by calling user::LeaveifError()
Notice the need to release the font after use; when you no longer need a CFont*, you must ask the device to release it.
If you forget to release a font, the effect will be the same as a memory leak: your program will get panicked on exit from emulator debug builds.
There is no error if ReleaseFont() is called when the font has not been allocated yet, the function just returns. Therefore, it is safe to release the font at the start of the function. The last font to be allocated before the application is closed is released in the hello control class destructor.
The code iFont=NULL exists because of the possibility of the function leaving before the font is reallocated. In this case, the destructor would attempt to release the font again if iFont wasn't set to null, causing the application to crash.
The font specification uses the TFontspec class, defined by the GDI, in gdi.h. Here's its declaration:
class TFontSpec {
public:
IMPORT_C TFontSpec();
IMPORT_C TFontSpec(const TDesC& aTypefaceName, Tint aHeight); IMPORT_C TBool operator==(const TFontSpec& aFontSpec) const; IMPORT_C void InternalizeL(RReadStream& aStream); IMPORT_C void ExternalizeL(RWriteStream& aStream) const; public:
TTypeface iTypeface; Tint iHeight; TFontStyle iFontStyle;
A font specification consists of a typeface, a height, and a font style. The TTypeface class is also defined by the GDI. It has several attributes, of which the most important is the name.
When you use GetNearestFontinTwips (), the height is expected to be in twips, though some devices support a GetNearestFontin-Pixels() function that allows the height to be specified in pixels.
The font style covers posture (upright or italic), stroke weight (bold or normal), and print position (normal, subscript, or superscript). Note
Other font attributes, such as underline and strikethrough, aren't font attributes at all - they're drawing effects, and you can apply them using the
CGraphicsContext functions.
TFontspec has a handy constructor that I used above for specifying a font in a single statement. It also has a default constructor and assignment operator. Finally, TFontspec has ExternalizeL() and internal-izeL () functions. These are important: a rich text object, for instance, must be able to externalize TFontspecs when storing, and reinternalize them when restoring. TFontspec objects can be stored with a rich text object (i.e. a document of some sort). They provide necessary information about how to display the document on a screen or printer. The TFontspec class comes into use whenever a rich text object is stored in or restored from a file. TFontspec is actually useful to any application that can display text in more than one size or to more than one target, including our example.
Zooming and fonts
Before we move on we will look at some problems associated with zooming text at very low zoom factors. The first is a fairly straightforward issue, but the second is slightly more subtle.
The first case occurs when the zoom factor is set too low for the smallest available size of a font, as is illustrated in Figure 15.5 for the lowest zoom state of our example application, running on a P800 phone. The only sensible solution is to restrict the range of the zoom factor, dependent on the range of font sizes that are available on a particular phone.
Figure 15.5 Important
The fonts on most target phones will be supplied in a smaller number of heights than you will find on the emulator. This can lead to different behaviors on the emulator and the real hardware, as is illustrated in our example in its lowest zoom state. This is another example of the need to take device realities into account - and it emphasizes the importance of thorough testing of your application on the target phone.
The second case will cause problems even when the phone supplies fonts with a wide range of sizes. If this situation is not dealt with properly, then the width of text at low zoom factors does not scale in proportion to the height. As can be seen in the following diagram, this can cause the text to be too wide for the surrounding box:
- Figure 15.6
When getting a CFont from a TFontspec you are converting from a twips size to a pixel size and, as you would expect, the height and width of a font will scale proportionately. But a character cannot be displayed in a width smaller than that of a pixel so, if the width scales to less than this, the excess will accumulate, character by character, along the length of the text.
One sensible solution at such low zoom factors is to calculate the width of the whole string in twips, convert the result to pixels and simply draw a horizontal line of the appropriate length.
You might want to support these low zoom factors in order to display the shape of words, sentences and paragraphs even when the font is too small to read, so eventually you can just make out where the capital letters are and, with even lower zoom states, just where the paragraphs are. This requirement applies to a print preview, for example. The Symbian OS rich text view, unlike our example application, can properly handle and display fonts even smaller than a pixel, using techniques such as that described above. Not all smartphone views will have to support this, so for many phones it is not actually an issue.
Drawing the text
We are now back to the hello view class's DrawInRect () function, in which we will see how our allocated font is used.
void CExampleHelloView:: DrawInRect(const MGraphicsDeviceMap& aMap,
CGraphicsContext& aGc, const TRect& aDeviceRect, CFont* aFont) {
//Draw some text if (iFullRedraw) {
aGc.SetBrushStyle(CGraphicsContext::ESolidBrush) ;
aGc.SetBrushColor(KRgbWhite); }
else aGc.SetBrushStyle(CGraphicsContext::ENullBrush); aGc.SetPenStyle(CGraphicsContext::ESolidPen); aGc.SetPenColor(KRgbBlack) ;
aGc.UseFont(aFont);
TInt baseline=aDeviceRect.Height()/2 + font->AscentInPixels()/2;
aGc.DrawText(*iText, aDeviceRect, baseline,
CGraphicsContext::ECenter);
aGc.DiscardFont();
//Draw a surrounding box
This uses the version of DrawText() that specifies a rectangle and guarantees that drawing will be clipped within that rectangle.
However, the DrawText () function will only white out the background before drawing, if iFullRedraw is set. When it is set, the brush setting is solid and white, which will cause the background to be whited; when it is not set, there is effectively no brush and the text is drawn straight over whatever was there before. This is the reason for the messy display in the upper rectangle in the screen shot of our application. In a real program, the whiting out of the background would not be conditional.
Post a comment