Managed Extensions: Using GDI+ to Render Sheared Text

Welcome to this week’s installment of .NET Tips & Techniques! Each week, award-winning Architect and Lead Programmer Tom Archer demonstrates how to perform a practical .NET programming task using either C# or Managed C++ Extensions.

Last month, I wrote a couple of GDI+ articles and quickly found out that the ability to draw text and implement imaging in Visual C++.NET applications is used much more widely than I originally realized. In fact, I’ve received quite a few requests for more articles on various techniques such as drawing shear text, creating the illusion of reflected text, and so on. Therefore, in this month’s first article, I illustrate how easily you can draw shear (or slanted) text using the GDI+ objects.

Steps to Rendering Shear Text

  1. Store the shear parameters.

    This is an issue only if the end-user specifies the values and then invokes the text-rendering process (for example, by clicking a button). This article’s accompanying demo works this way. In this case, store these values in a member variable when the user requests that the text be rendered, and then cause the invocation of the PictureBox control’s Paint method. Otherwise, if you simply use the form control values in the Paint method, sometimes you’ll use values that the user has typed in but hasn’t requested to be rendered. In the demo, this code looks like the following (where the btnDisplayText_Click method is an event handler that is called when the user clicks the Display Text button):

    public __gc class Form1 : public System::Windows::Forms::Form
    {
    
    ...
    
    protected:
      String* textToDisplay;
      Decimal fontSize;
      Decimal shearSize;
    
    ...
    
    private: System::Void btnDisplayText_Click(System::Object *
                                               sender,
                                               System::EventArgs *
                                               e)
    {
      // Set up internal display values
      textToDisplay = txtToDisplay->Text;
      fontSize      = spnFontSize->Value;
      shearSize     = spnShear->Value;
    
      // Invalidate the control
      picText->Invalidate();
    }
    
    
  2. Implement a Paint method for the PictureBox control.

    The text will be rendered on the PictureBox control, and implementing a Paint method ensures that the control is repainted when needed—such as when the user switches away from and then back to the application. The following is an example where the conditional statement simply ensures that the user has entered text to be displayed before attempting to use the object (textToDisplay) containing that value:

    private: System::Void picText_Paint(System::Object *  sender,
                                        System::Windows::Forms::
                                        PaintEventArgs *  e)
    {
      if (textToDisplay)
      {
      }
    }
    
  3. Note: The remainder of the steps involve code placed in the PictureBox object’s Paint method.

  4. Obtain the Graphics object for the picture control.

    You could acquire this object via the PictureBox object’s CreateGraphics method. However, because the GC (Garbage Collector) would collect that Graphics object when it goes out of scope, the object would volatile it. Therefore, you need the Graphics object that is passed to the Paint method (via the PaintEventArgs::Graphics member):

    Graphics* g = e->Graphics;
  5. Instantiate the Font object based on the user-supplied font size.

    This application uses a hard-coded font typeface of “Times New Roman,” but obviously that’s application specific. The following code creates a “Times New Roman” font with a style of “regular” (as opposed to bold) for the size the user specifies:

    System::Drawing::Font* font =
      new System::Drawing::Font("Times New Roman",
                                Convert::ToSingle(fontSize),
                                FontStyle::Regular);
    
  6. Obtain the size of the text to be rendered.

    As a previous article discussed, the Graphics::MeasureString method is a convenient method for measuring a string, given the presentation space in which it will be drawn and the font that will be used:

    SizeF textSize = g->MeasureString(textToDisplay, font);
  7. Clear the PictureBox Box control.

    Initialize the PictureBox control by using the PictureBox::Clear method and specifying the desired color. The following code uses the user-defined (via the Display Properties application) value for controls:

    g->Clear(SystemColors::Control);
  8. Calcualte where the text will be rendered on the PictureBox control.
  9. The following code is used to determine the x and y coordinates for centering the text:

    Single x = (picText->Width  - textSize.Width)  / 2;
    Single y = (picText->Height - textSize.Height) / 2;
    
  10. Prepend the translation to the transformation matrix of the PictureBox control.

    To scale text, you must scale the entire graphics object. Therefore, you need to first reposition the origin of the Graphics object to the calcualted x and y positions. You accomplished this via the Graphics::TranslateTransform method:

    g->TranslateTransform(x, y);
  11. Retrieve the newly generated transform.

    Once you’ve generated a “world transformation” for a given Graphics object, it can be retrieved via the Graphics::Transform property. This property returns a Matrix object that is used to shear the text:

    Matrix* transform = g->Transform;
  12. Shear the transform per the user’s specified amount.

    The Matrix object has a method called Shear that does what you set out to accomplish. It takes the horizontal and vertical shear factors. The demo specifies only the horizontal shear value (hence the passing of the literal 0 for the second parameter), but both can be specified easily. However, note that the transformation technically is only a true shear if one of these values (either the horizontal or vertical) is set to 0:

    transform->Shear(Convert::ToSingle(shearSize), 0);
  13. Set the Graphic object’s Transform to the newly manipulated one.

    Once the local Matrix object has been modified via the Shear method, you need to set the Graphic object’s transform to this value:

    g->Transform = transform;
  14. Render the text.

    Now that the PictureBox control’s presentation space is set up (via Graphics object), you finally can call the Graphics::DrawString method to render the text:

    g->DrawString(textToDisplay, font, Brushes::Black, 0, 0);

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read