c++ 如何用Win32 TextOut绘制波浪下划线

mspsb9vt  于 2023-01-22  发布在  其他
关注(0)|答案(3)|浏览(91)

我正在编写一段代码,基本上是通过多次调用TextOut在屏幕上绘制文本。这是一个C++ Win32应用程序,我使用的是Visual Studio 2012。如何像Word在标记拼写错误等时那样绘制波浪形下划线?
TEXTMETRIC有一个名为tmUnderlined的成员,它适用于普通下划线文本,但不适用于波浪下划线。我还发现微软的RichEdit控件支持波浪下划线,但我也不能使用它。
当然,我可以使用简单的正弦波创建路径,但在此之前,我想检查一下是否有更标准的方法。

dnph8jn4

dnph8jn41#

我自己实现的。如果有人感兴趣的话,这里是代码:

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;
  }

附言:如果你不喜欢gotos,你可以重写它,用try.. catch代替!

ghhaqwfi

ghhaqwfi2#

这为我省去了很多麻烦,谢谢,所以这里是我的解决方案,没有goto,还有一点点优化:

#include <gdiplus.h>
#include <corecrt_math_defines.h>
#include <vector>

bool drawWavyUnderline(HDC hDC, const POINT& 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|TA_TOP.
// @param ptTextPos (in): TextOut reference point.
// @return: TRUE on success, FALSE on failure.
{
    bool bResult = false;
    // Determine the number of points required.
    const float fNumPixelsPerSample = 1.2f;
    const int nPts = (int)(nWidth / fNumPixelsPerSample);

    if (nPts > 1) {
        try {
            // Retrieve information about the current GDI font.
            TEXTMETRIC tm;

            if (::GetTextMetrics(hDC, &tm)) {
                // Fill points array.
                const float fAmp = 1.5f;
                const float fYOffset = ptTextPos.y + tm.tmAscent + tm.tmDescent * 0.5f + 1.0f;
                float xOffset = 0.0;
                std::vector< Gdiplus::PointF> arrPts;

                arrPts.resize(nPts);

                for (auto& pt : arrPts) {
                    pt.X = (Gdiplus::REAL)(ptTextPos.x + xOffset++ * nWidth / (nPts - 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.
                    const float fValue = (float)(fAmp * sin((pt.X / fNumPixelsPerSample) * (M_PI / 3.0)));
                    pt.Y = (Gdiplus::REAL)(fYOffset + fValue);
                }

                // Draw the wavy line:
                Gdiplus::Graphics   gfx(hDC);
                Gdiplus::Pen        pen(Gdiplus::Color(GetRValue(col), GetGValue(col), GetBValue(col)));

                gfx.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
                bResult = gfx.DrawLines(&pen, arrPts.data(), nPts) == Gdiplus::Ok;
            }
        }
        catch (...) {
        }
    }

    return bResult;
}
g0czyy6m

g0czyy6m3#

下面是我的C#版本:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApp1
{
    public static class WavyLineRendering
    {
        public static void Draw(Graphics graphics, Font font, Point ptTextPos, int nWidth, Color 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!

            float fNumPixelsPerSample = 1.2f;
            int nNumPts = (int)(nWidth / fNumPixelsPerSample);
            if (nNumPts <= 1)
                return;

            // Retrieve information about the current GDI font.
            var tm = GetTextMetricsWrapper(graphics, font);

            // Create points array.
            var arrPts = new PointF[nNumPts];
            
            // Fill points array.
            float fYOffset = 1.0f;
            float fAmp = 1.5f;
            for (int i = 0; i < nNumPts; i++)
            {
                arrPts[i].X = (float)(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 * Math.Sin((arrPts[i].X / fNumPixelsPerSample) * (Math.PI / 3.0)));
                arrPts[i].Y = (float)(ptTextPos.Y + tm.tmAscent + tm.tmDescent * 0.5f + fYOffset + fValue);
            }
            // Draw the lines.
            using (var pPen = new Pen(col))
            {
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.DrawLines(pPen, arrPts);
            }
        }

        private static TEXTMETRIC GetTextMetricsWrapper(Graphics graphics, Font font)
        {
            var hDC = new HandleRef(graphics, graphics.GetHdc());
            var hFont = new HandleRef(font, font.ToHfont());

            try
            {
                var hFontPreviouse = SelectObject(hDC, hFont);

                GetTextMetrics(hDC, out var textMetric);
                SelectObject(hDC, hFontPreviouse);

                return textMetric;
            }
            finally
            {
                DeleteObject(hFont);
                graphics.ReleaseHdc(hDC.Handle);
            }
        }

        [DllImport("Gdi32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SelectObject(HandleRef hdc, IntPtr hgdiobj);

        [DllImport("Gdi32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SelectObject(HandleRef hdc, HandleRef hgdiobj);

        [DllImport("Gdi32.dll", CharSet = CharSet.Auto)]
        private static extern bool GetTextMetrics(HandleRef hdc, out TEXTMETRIC lptm);

        [DllImport("Gdi32.dll", CharSet = CharSet.Auto)]
        private static extern bool DeleteObject(HandleRef hdc);

        [Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct TEXTMETRIC
        {
            public int tmHeight;
            public int tmAscent;
            public int tmDescent;
            public int tmInternalLeading;
            public int tmExternalLeading;
            public int tmAveCharWidth;
            public int tmMaxCharWidth;
            public int tmWeight;
            public int tmOverhang;
            public int tmDigitizedAspectX;
            public int tmDigitizedAspectY;
            public byte tmFirstChar;    // this assumes the ANSI charset; for the UNICODE charset the type is char (or short)
            public byte tmLastChar;     // this assumes the ANSI charset; for the UNICODE charset the type is char (or short)
            public byte tmDefaultChar;  // this assumes the ANSI charset; for the UNICODE charset the type is char (or short)
            public byte tmBreakChar;    // this assumes the ANSI charset; for the UNICODE charset the type is char (or short)
            public byte tmItalic;
            public byte tmUnderlined;
            public byte tmStruckOut;
            public byte tmPitchAndFamily;
            public byte tmCharSet;
        }
    }
}

相关问题