c++winapitextout

How to draw wavy underlines with Win32 TextOut


I'm working on a piece of code that basically draws text on the screen by multiple calls to TextOut. It's a C++ Win32 application, I'm using Visual Studio 2012. How can I draw wavy underlines in the way Word does when marking spelling mistakes, etc.?

TEXTMETRIC has a member called tmUnderlined that works for normal underlined text, but not for wavy underlines. I also found that Microsoft's RichEdit control supports wavy underlines, but I can't use that either.

I could, of course, create a path using a simple sine wave, but before I do this I wanted to check if there is a more standard way to do this.


Solution

  • I implemented it by myself. Here's the code in case anybody is interested:

    BOOL SomeClass::drawWavyUnderline (WPPoint ptTextPos, int nWidth, COLORREF col)
      // Draws a wavy underline below the position a run of text at the given 
      // position and of the given width would occupy. This method consults the
      // height of the currently selected font in order to find the baseline where
      // the underline is drawn. 
      //    NOTE: The method will fail to find the correct position of the underline
      // if the current text alignment is not set to TA_LEFT!
      // @param ptTextPos (in): TextOut reference point.
      // @return: TRUE on success, FALSE on failure.
      {
      BOOL bResult = FALSE;
      Gdiplus::Graphics *pGfx = NULL;
      Gdiplus::Pen *pPen = NULL;
      Gdiplus::PointF *arrPts = NULL;
      // Determine the number of points required.
         static const float fNumPixelsPerSample = 1.2f;
         int nNumPts = (int)(nWidth / fNumPixelsPerSample);
         if (nNumPts <= 1)
            {
            goto outpoint;  // width too small or even negative!
            }
      // Retrieve information about the current GDI font.
         TEXTMETRIC tm;
         if (!::GetTextMetrics (/* HDC... */, &tm))
            {
            goto outpoint;  // failed to retrieve TEXTMETRIC!
            }
      // Create points array.
         arrPts = new Gdiplus::PointF [nNumPts];
         if (arrPts == NULL)
            {
            goto outpoint;  // out of mem!
            }
      // Fill points array.
         static const float fYOffset = 1.0f;
         static const float fAmp = 1.5f;
         Gdiplus::PointF *pScan = arrPts;
         for (int i = 0; i < nNumPts; i++, pScan++)
            {
            pScan->X = (Gdiplus::REAL)(ptTextPos.x + (float) i * nWidth / (nNumPts - 1));
            // The amplitude is computed as a function of the absolute position x rather
            // than the sample index i in order to make sure the waveform will start at
            // the correct point when two runs are drawn very near each-other.
            float fValue = (float)(fAmp * sin ((pScan->X / fNumPixelsPerSample)*(M_PI / 3.0)));
            pScan->Y = (Gdiplus::REAL)(ptTextPos.y + tm.tmAscent + tm.tmDescent*0.5f + fYOffset + fValue);
            }
      // Create GDI+ graphics object.
         HDC hdc = /* code to retrieve the HDC... */ ;
         if (hdc == NULL)
            {
            goto outpoint;  // missing HDC
            }
         pGfx = new Gdiplus::Graphics (hdc);
         if (pGfx == NULL)
            {
            goto outpoint;  // out of mem!
            }
      // Draw the lines.
         pPen = new Gdiplus::Pen (Gdiplus::Color (GetRValue (col), GetGValue (col), GetBValue (col)));
         if (pPen == NULL)
            {
            goto outpoint;  // out of mem!
            }
         pGfx->SetSmoothingMode (Gdiplus::SmoothingModeHighQuality);
         if (pGfx->DrawLines (pPen, arrPts, nNumPts) != Gdiplus::Ok)
            {
            goto outpoint;  // failed to draw the lines!
            }
      bResult = TRUE;
      outpoint:
      // Clean up.
         if (pPen != NULL)   delete pPen;
         if (pGfx != NULL)   delete pGfx;
         if (arrPts != NULL) delete[] arrPts;
      return bResult;
      }
    

    P.S.: If you don't like gotos, feel free to rewrite it and use try..catch instead!