[Gdiplus/Gdi]_[中级]_[实现多行文本的多种颜色绘制-富文本绘制]

场景

  1. 在开发WTL/WIN32界面程序时,有时候需要绘制多行的段落文本,但是文本里的数值需要设置红色以便能让人第一时间关注到它。这种文本可以称之为富文本。GDIDrawTextGDIPlusDrawString方法都只能连续绘制某个颜色的文本。怎么实现?

说明

  1. 《绘图实现单行文本的多种颜色》[1]里介绍了绘制单行多种颜色文本的实现,但是并不支持换行,换行需要知道上一行绘制的文本长度和换行后的文本起始位置,实现起来并不容易。

  2. 要实现多行富文本,那么关键的方法是计算一行字符串在给定以下的3个重要的参数计算当前行需要绘制的字符个数,之后把剩余的字符串交给下一行绘制。方法calcOneLineFitStringLength就是只存储给定行剩余宽度绘制适合个数的字符串,把剩余的字符递归传递给calcOneLineFitStringLength本身计算并存储。

    1. 起始横坐标x

    2. 纵坐标y

    3. 最大行宽度maxWidth

    std::pair<float,float> StringMultiLineDrawObject::calcOneLineFitStringLength(Gdiplus::Graphics* graphics, const wchar_t* str,
    	int startX, int startY, int maxWidth,int lineSpace,Gdiplus::Font* font,Gdiplus::Color* color)
    
  3. 在计算字符串在哪个长度时换行,可以通过行的像素长度,字符串总个数,绘制字符串需要的像素长度来按比例计算最接近的可以在当前行绘制完的字符串个数。

auto startWidth = maxWidth - startX; // 行的像素长度
int mLen = startWidth * len / (int)rect_default.Width; // len:字符串总个数 rect_default.Width: 绘制字符串需要的像素长度
  1. GdiGdiplus实现都是类似的,以下例子给了两种库的实现。类StringMultiLineDrawObject封装了设置替换字符串和计算存储字符串绘制的数组对象vector<GDIPlusStringsFormat>。这个对象在绘制的时候调用drawGDIPlusStringsFormat方法时会枚举对象并绘制。当然计算的时候可以默认x,y坐标为0,通过setOffsetXY来设置实际的界面x,y坐标。

  2. 注意,这里查找各部分独立绘制的字符串是通过关键字如%who%来替换为真实字符串来设置的,比较省事。大家可以实现类似[1]实际字符串[2]这种方式的字符串提取。

auto str = L"%who1%今天是考试得了%number%分, 因为我从一个很好的技术作者网站%url%那里获得了指导.谢谢%who2%.";
...
sdoGdiplus_.setKeyValue(L"%who1%",L"GDIPLUS",&color_blue_,font_);
sdoGdiplus_.setKeyValue(L"%who2%",L"Tobey",&color_blue_,font_);
sdoGdiplus_.setKeyValue(L"%number%",L"100",&color_red_,font_);
sdoGdiplus_.setKeyValue(L"%url%",L"https://blog.youkuaiyun.com/infoworld",&color_blue_,font_);

例子

  1. 以下项目例子实现了GDIGDIPlus绘制多行多种颜色的文本(富文本)。CView类调用富文本实现StringMultiLineDrawObject

string_multi_line_draw_object.h

#ifndef STRING_DRAW_UTIL_H
#define STRING_DRAW_UTIL_H

#include <Windows.h>
#include <vector>
#include <map>
#include <string>
#include <GdiPlus.h>
#include <tuple>

using namespace std;

typedef tuple<wstring,Gdiplus::RectF,Gdiplus::Color*,Gdiplus::Font*> GDIPlusStringsFormat;
typedef tuple<wstring,CRect,COLORREF,HFONT> GDIStringsFormat;

class StringMultiLineDrawObject
{
public:
	StringMultiLineDrawObject();

	inline StringMultiLineDrawObject& setOffsetXY(int offsetx,int offsety){
		offsetx_ = offsetx;
		offsety_ = offsety;
		return *this;
	}

	inline StringMultiLineDrawObject& setString(const wchar_t* str){
		str_ = str;
		return *this;
	}

	std::wstring getSimpleString();

public:
	inline StringMultiLineDrawObject& setFont(Gdiplus::Font* font){
		font_ = font;
		return *this;
	}

	inline StringMultiLineDrawObject& setColor(Gdiplus::Color* color){
		color_ = color;
		return *this;
	}

	inline StringMultiLineDrawObject& setKeyValue(const wchar_t* key,
		const wchar_t* value,Gdiplus::Color* color,Gdiplus::Font* font){
		sf_value_map_[key] = std::tuple<wstring,Gdiplus::Color*,Gdiplus::Font*>(value,color,font);
		return *this;
	}

	void calcGDIPlusStringsFormat(Gdiplus::Graphics* graphics,int maxWidth,int lineSpace);

	void drawGDIPlusStringsFormat(Gdiplus::Graphics* graphics);
	
protected:

	void measureString(Gdiplus::Graphics* graphics, const wchar_t* str, int strLen,
		Gdiplus::Font* font,Gdiplus::StringFormat* strFormat, Gdiplus::RectF& rect_default);
	
	std::pair<float,float> calcOneLineFitStringLength(Gdiplus::Graphics* graphics, const wchar_t* str,
		int startX,int startY, int maxWidth,int lineSpace,Gdiplus::Font* font,Gdiplus::Color* color);

public:

	inline StringMultiLineDrawObject& setFont(HFONT font){
		hfont_ = font;
		return *this;
	}

	inline StringMultiLineDrawObject& setColor(COLORREF color){
		hcolor_ = color;
		return *this;
	}

	inline StringMultiLineDrawObject& setKeyValue(const wchar_t* key,
		const wchar_t* value,COLORREF color,HFONT font){
		hsf_value_map_[key] = std::tuple<wstring,COLORREF,HFONT>(value,color,font);
		return *this;
	}

	void calcGDIStringsFormat(CDC& cdc,int maxWidth,int lineSpace);

	void drawGDIStringsFormat(CDC& cdc);

protected:

	void measureString(CDC& cdc, const wchar_t* str, int strLen, HFONT font, CRect& rect_default);
	
	std::pair<float,float> calcOneLineFitStringLength(CDC& cdc, const wchar_t* str,
		int startX,int startY, int maxWidth,int lineSpace,HFONT font,COLORREF color);

private:

	Gdiplus::Font* font_ = nullptr;

	Gdiplus::Color* color_= nullptr;

	map<wstring,tuple<wstring,Gdiplus::Color*,Gdiplus::Font*>> sf_value_map_;

	vector<GDIPlusStringsFormat> sf_;

private:

	HFONT hfont_ = NULL;

	COLORREF hcolor_ = 0;

	map<wstring,tuple<wstring,COLORREF,HFONT>> hsf_value_map_;

	vector<GDIStringsFormat> hsf_;

private:

	int offsetx_;

	int offsety_;

	wstring str_;

	wstring allString_;

	float defaultStringHeight_ = 0;
};

#endif

string_multi_line_draw_object.cpp

#include "stdafx.h"
#include "string_multi_line_draw_object.h"
#include <regex>
#include <assert.h>

using namespace Gdiplus;

StringMultiLineDrawObject::StringMultiLineDrawObject()
{
	offsetx_ = 0;
	offsety_ = 0;
}

void StringMultiLineDrawObject::drawGDIPlusStringsFormat(Gdiplus::Graphics* graphics)
{
	StringFormat strFormat;
	Gdiplus::Pen pen(*color_);

	for(int i =0;i< sf_.size();++i){
		auto& one = sf_[i];
		auto& str_one = std::get<0>(one);
		auto rect_one = std::get<1>(one);
		auto color = std::get<2>(one);
		auto font = std::get<3>(one);
		rect_one.Offset(offsetx_,offsety_);

		// -- 绘制计量矩形框调试
		// graphics->DrawRectangle(&pen, rect_one);

		SolidBrush brush(*color);
		if (font != font_) 
			rect_one.Y = rect_one.Y - (rect_one.Height - defaultStringHeight_) / 2;
		
		graphics->DrawString(str_one.c_str(),str_one.size(),font,rect_one,&strFormat,&brush);
	}
}

void StringMultiLineDrawObject::measureString(Gdiplus::Graphics* graphics, const wchar_t* str,int strLen,
	Gdiplus::Font* font,Gdiplus::StringFormat* strFormat, Gdiplus::RectF& rect_default)
{
	graphics->MeasureString(str, strLen,font,Gdiplus::PointF(0,0),strFormat,&rect_default);
}

std::pair<float,float> StringMultiLineDrawObject::calcOneLineFitStringLength(Gdiplus::Graphics* graphics, const wchar_t* str,
	int startX, int startY, int maxWidth,int lineSpace,Gdiplus::Font* font,Gdiplus::Color* color)
{
	if (!str)
		return std::make_pair(startX,startY);

	StringFormat strFormatNoSpace;
	Gdiplus::RectF rect_default;
	auto len = wcslen(str);
	measureString(graphics, str, len, font, &strFormatNoSpace, rect_default);
	
	auto startWidth = maxWidth - startX;
	if (rect_default.Width > startWidth) {
		int strHeight = rect_default.Height;
		int mLen = startWidth * len / (int)rect_default.Width;
		measureString(graphics, str, mLen, font, &strFormatNoSpace, rect_default);
		
		if (rect_default.Width > startWidth) {
			while (mLen--) {
				// minus one character
				measureString(graphics, str, mLen, font, &strFormatNoSpace, rect_default);
				
				if (rect_default.Width > startWidth){
					continue;
				}else {
					std::wstring sub(str, mLen);
					rect_default.X = startX;
					rect_default.Y = startY;
					sf_.push_back(GDIPlusStringsFormat(sub,rect_default,color,font));
					return calcOneLineFitStringLength(graphics, str + mLen, 0,
						startY + strHeight + lineSpace, maxWidth,lineSpace, font,color);
				}
			}
		}else {
			// new line
			if (mLen) {
				std::wstring sub(str, mLen);
				rect_default.X = startX;
				rect_default.Y = startY;
				sf_.push_back(GDIPlusStringsFormat(sub,rect_default,color,font));
				return calcOneLineFitStringLength(graphics, str + mLen, 0,
					startY + strHeight + lineSpace, maxWidth, lineSpace,font,color);
			}else {
				return calcOneLineFitStringLength(graphics, str, 0,
					startY + strHeight + lineSpace, maxWidth, lineSpace,font,color);
			}
			
		}

	}else {
		rect_default.X = startX;
		rect_default.Y = startY;
		sf_.push_back(GDIPlusStringsFormat(str,rect_default,color,font));

		return std::make_pair(rect_default.GetRight(), startY);
	}

	return std::make_pair(startX,startY);
}

// https://stackoverflow.com/questions/11708621/how-to-measure-width-of-a-string-precisely
// https://bbs.youkuaiyun.com/topics/391929399?page=1
// https://stackoverflow.com/questions/118686/measurestring-pads-the-text-on-the-left-and-the-right
// https://docs.microsoft.com/en-us/windows/win32/api/gdiplusgraphics/nf-gdiplusgraphics-graphics-measurecharacterranges
void StringMultiLineDrawObject::calcGDIPlusStringsFormat(Gdiplus::Graphics* graphics,int maxWidth,int lineSpace)
{
	defaultStringHeight_ = font_->GetHeight(graphics);

	wstring ptext;
	for(auto ite = sf_value_map_.begin();ite != sf_value_map_.end();++ite){
		auto& key = ite->first;
		ptext.append(key).append(L"|");
	}
	ptext.erase(ptext.end()-1);

	wregex pattern1(ptext);
	auto words_begin =
        wsregex_iterator(str_.begin(),str_.end(), pattern1);
	auto words_end = std::wsregex_iterator();

	wsmatch match;
	int posx = 0;
	int posy = 0;
	int index = 0;

	allString_.clear();

	for (std::wsregex_iterator i = words_begin; i != words_end; ++i){

		std::wsmatch match = *i;
		size_t pos = match.position();
		size_t length = match.length();

		wstring str1 = str_.substr(index,pos-index);
		wstring str2 = str_.substr(pos,length);

		////////////////////////////
		if(str1.size()){
			auto pp = calcOneLineFitStringLength(graphics, str1.c_str(), posx, posy, maxWidth, lineSpace, font_, color_);
			posx = pp.first;
			posy = pp.second;
		}
		
		///////////////////////////
		auto ite = sf_value_map_.find(str2);
		auto font_value = font_;
		Color* color_value = color_;
		if(ite != sf_value_map_.end()){
			auto& tt = ite->second;
			str2 = get<0>(tt);
			color_value = get<1>(tt);
			font_value = get<2>(tt);
		}

		auto pp = calcOneLineFitStringLength(graphics, str2.c_str(), posx, posy, maxWidth, lineSpace, font_value, color_value);
		posx = pp.first;
		posy = pp.second;
		allString_.append(str2);

		index = pos+length;
	}

	if(index+1 <= str_.size()){
		auto xstr = str_.substr(index);

		auto pp = calcOneLineFitStringLength(graphics, xstr.c_str(), posx, posy, maxWidth, lineSpace, font_, color_);
		posx = pp.first;
		posy = pp.second;

		allString_.append(xstr);
	}

}

std::wstring StringMultiLineDrawObject::getSimpleString()
{
	return allString_;
}

void StringMultiLineDrawObject::calcGDIStringsFormat(CDC& cdc, int maxWidth, int lineSpace)
{
	CFontHandle font(hfont_);
	LOGFONT lfont;
	font.GetLogFont(lfont);
	defaultStringHeight_ = lfont.lfHeight;

	wstring ptext;
	for(auto ite = hsf_value_map_.begin();ite != hsf_value_map_.end();++ite){
		auto& key = ite->first;
		ptext.append(key).append(L"|");
	}
	ptext.erase(ptext.end()-1);

	wregex pattern1(ptext);
	auto words_begin =
        wsregex_iterator(str_.begin(),str_.end(), pattern1);
	auto words_end = std::wsregex_iterator();

	wsmatch match;
	int posx = 0;
	int posy = 0;
	int index = 0;
	
	allString_.clear();

	for (std::wsregex_iterator i = words_begin; i != words_end; ++i){

		std::wsmatch match = *i;
		size_t pos = match.position();
		size_t length = match.length();

		wstring str1 = str_.substr(index,pos-index);
		wstring str2 = str_.substr(pos,length);

		////////////////////////////
		if(str1.size()){
			auto pp = calcOneLineFitStringLength(cdc, str1.c_str(), posx, posy, maxWidth, lineSpace, hfont_, hcolor_);
			posx = pp.first;
			posy = pp.second;
		}
		
		///////////////////////////
		auto ite = hsf_value_map_.find(str2);
		auto font_value = hfont_;
		auto color_value = hcolor_;
		if(ite != hsf_value_map_.end()){
			auto& tt = ite->second;
			str2 = get<0>(tt);
			color_value = get<1>(tt);
			font_value = get<2>(tt);
		}

		auto pp = calcOneLineFitStringLength(cdc, str2.c_str(), posx, posy, maxWidth, lineSpace, font_value, color_value);
		posx = pp.first;
		posy = pp.second;
		allString_.append(str2);

		index = pos+length;
	}

	if(index+1 <= str_.size()){
		auto xstr = str_.substr(index);

		auto pp = calcOneLineFitStringLength(cdc, xstr.c_str(), posx, posy, maxWidth, lineSpace, hfont_, hcolor_);
		posx = pp.first;
		posy = pp.second;

		allString_.append(xstr);
	}
}

void StringMultiLineDrawObject::drawGDIStringsFormat(CDC& cdc)
{
	for(int i =0;i< hsf_.size();++i){
		auto& one = hsf_[i];
		auto& str_one = std::get<0>(one);
		auto rect_one = std::get<1>(one);
		auto color = std::get<2>(one);
		auto font = std::get<3>(one);
		rect_one.OffsetRect(CPoint(offsetx_, offsety_));

		if (font != hfont_) 
			rect_one.top = rect_one.top - (rect_one.Height() - defaultStringHeight_) / 2;
		
		auto oldFont = cdc.SelectFont(font);
		cdc.SetTextColor(color);
		cdc.DrawText(str_one.c_str(), str_one.size(), rect_one, DT_LEFT);
		cdc.SelectFont(oldFont);
	}
}

void StringMultiLineDrawObject::measureString(CDC& cdc, const wchar_t* str, int strLen,
	HFONT font, CRect& rect_default)
{
	auto oldFont = cdc.SelectFont(font);

	CSize size;
	cdc.GetTextExtent(str, strLen, &size);
	rect_default.right = rect_default.left + size.cx;
	rect_default.bottom = rect_default.top + size.cy;
	cdc.SelectFont(oldFont);
}
	
std::pair<float, float> StringMultiLineDrawObject::calcOneLineFitStringLength(CDC& cdc, const wchar_t* str,
	int startX, int startY, int maxWidth, int lineSpace, HFONT font, COLORREF color)
{
	if (!str)
		return std::make_pair(startX,startY);

	CRect rect_default;
	auto len = wcslen(str);
	measureString(cdc, str, len, font, rect_default);
	

	auto startWidth = maxWidth - startX;
	if (rect_default.Width() > startWidth) {
		int strHeight = rect_default.Height();
		int mLen = startWidth * len / (int)rect_default.Width();
		measureString(cdc, str, mLen, font, rect_default);
		
		if (rect_default.Width() > startWidth) {
			while (mLen--) {
				// minus one character
				measureString(cdc, str, mLen, font, rect_default);
				
				if (rect_default.Width() > startWidth){
					continue;
				}else {
					std::wstring sub(str, mLen);
					rect_default.MoveToXY(startX, startY);
					hsf_.push_back(GDIStringsFormat(sub,rect_default,color,font));
					return calcOneLineFitStringLength(cdc, str + mLen, 0,
						startY + strHeight + lineSpace, maxWidth,lineSpace, font,color);
				}
			}
		}else {
			// new line
			if (mLen) {
				std::wstring sub(str, mLen);
				rect_default.MoveToXY(startX, startY);
				hsf_.push_back(GDIStringsFormat(sub,rect_default,color,font));
				return calcOneLineFitStringLength(cdc, str + mLen, 0,
					startY + strHeight + lineSpace, maxWidth, lineSpace,font,color);
			}else {
				return calcOneLineFitStringLength(cdc, str, 0,
					startY + strHeight + lineSpace, maxWidth, lineSpace,font,color);
			}
			
		}

	}else {
		rect_default.MoveToXY(startX, startY);
		hsf_.push_back(GDIStringsFormat(str,rect_default,color,font));

		return std::make_pair(rect_default.right, startY);
	}

	return std::make_pair(startX,startY);
}

View.h

// View.h : interface of the CView class
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

#include "MyView.h"
#include "test-string-draw-object.h"
#include "string_multi_line_draw_object.h"

class CView : public CWindowImpl<CView>
{
public:
	DECLARE_WND_CLASS(NULL)

	BOOL PreTranslateMessage(MSG* pMsg);

	BEGIN_MSG_MAP_EX(CView)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MSG_WM_CREATE(OnCreate)
	END_MSG_MAP()

// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
	int OnCreate(LPCREATESTRUCT lpCreateStruct);
	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);

	void ShowMyView();

protected:

	StringMultiLineDrawObject sdoGdiplus_;
	Gdiplus::Color color_black_;
	Gdiplus::Color color_red_;
	Gdiplus::Color color_blue_;

	Gdiplus::Font* font_ = nullptr;
	Gdiplus::Font* font_bold_ = nullptr;

	MyView myview_;

	StringMultiLineDrawObject sdoGdi_;
	COLORREF hcolor_black_;
	COLORREF hcolor_red_;
	COLORREF hcolor_blue_;

	HFONT hfont_ = NULL;
	HFONT hfont_bold_ = NULL;
};

View.cpp

// View.cpp : implementation of the CView class
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include "atlmisc.h"
#include "View.h"

using namespace Gdiplus;

static HFONT GetHFONT(int em,int charset,
	bool bold,const wchar_t* fontName)
{
	LOGFONT lf; 
	memset(&lf, 0, sizeof(LOGFONT)); // zero out structure 
	lf.lfHeight = em; // request a 8-pixel-height font
	lf.lfCharSet = charset;
	lstrcpy(lf.lfFaceName,fontName); // request a face name "Arial"
	if(bold)
		lf.lfWeight = FW_BOLD;
	else
		lf.lfWeight = FW_NORMAL;
	HFONT font = ::CreateFontIndirect(&lf);
	return font;
}

BOOL CView::PreTranslateMessage(MSG* pMsg)
{
	pMsg;
	return FALSE;
}

int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	myview_.Create(m_hWnd,CRect(0,0,100,100));
	auto str = L"%who1%今天是考试得了%number%分, 因为我从一个很好的技术作者网站%url%那里获得了指导.谢谢%who2%.";

	/////////////// 应用字符串格式化 //////////////////

	hfont_ = GetHFONT(16, DEFAULT_CHARSET, false, L"Arial");
	hfont_bold_ = GetHFONT(24, DEFAULT_CHARSET, true, L"Arial");

	hcolor_black_ = RGB(0, 0, 0);
	hcolor_blue_ = RGB(0, 0, 255);
	hcolor_red_ = RGB(255, 0, 0);

	CClientDC dc(m_hWnd);
	sdoGdi_.setString(str);
	sdoGdi_.setFont(hfont_);
	sdoGdi_.setColor(hcolor_black_);
	sdoGdi_.setOffsetXY(10,10);
	sdoGdi_.setKeyValue(L"%who1%",L"GDI",hcolor_blue_,hfont_);
	sdoGdi_.setKeyValue(L"%who2%",L"Tobey",hcolor_blue_,hfont_);
	sdoGdi_.setKeyValue(L"%number%",L"100",hcolor_red_,hfont_);
	sdoGdi_.setKeyValue(L"%url%",L"https://blog.youkuaiyun.com/infoworld",hcolor_blue_,hfont_bold_);
	sdoGdi_.calcGDIStringsFormat(dc,500,10);

	/////////////////// Gdiplus //////////////////////
	Gdiplus::Graphics graphics(m_hWnd);
	graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
	graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	Gdiplus::FontFamily fontFamily(L"Arial");
	font_ = new Gdiplus::Font(&fontFamily,16,
			Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);
	font_bold_ = new Gdiplus::Font(&fontFamily,24,
			Gdiplus::FontStyleBold,Gdiplus::UnitPixel);

	color_black_.SetValue(Gdiplus::Color::Black);
	color_red_.SetValue(Gdiplus::Color::Red);
	color_blue_.SetValue(Gdiplus::Color::Blue);

	sdoGdiplus_.setString(str);
	sdoGdiplus_.setFont(font_);
	sdoGdiplus_.setColor(&color_black_);
	sdoGdiplus_.setOffsetXY(10,200);
	sdoGdiplus_.setKeyValue(L"%who1%",L"GDIPLUS",&color_blue_,font_);
	sdoGdiplus_.setKeyValue(L"%who2%",L"Tobey",&color_blue_,font_);
	sdoGdiplus_.setKeyValue(L"%number%",L"100",&color_red_,font_);
	sdoGdiplus_.setKeyValue(L"%url%",L"https://blog.youkuaiyun.com/infoworld",&color_blue_,font_);
	sdoGdiplus_.calcGDIPlusStringsFormat(&graphics,500,10);

	return 0;
}

void CView::ShowMyView()
{
	myview_.ShowWindow(SW_SHOW);
}

LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	CPaintDC hdc(m_hWnd);

	CRect client_rect;
	GetClientRect(&client_rect);
	CMemoryDC mdc(hdc,client_rect);
	mdc.FillSolidRect(client_rect,RGB(255,255,255));
	mdc.SetBkMode(TRANSPARENT);
	
	// GDI
	sdoGdi_.drawGDIStringsFormat(mdc);

	//背景图
	Gdiplus::Graphics graphics(mdc);
	graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
	graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	// GDIPlus
	sdoGdiplus_.drawGDIPlusStringsFormat(&graphics);
	

	return 0;
}

项目

图1:
在这里插入图片描述

下载地址:

  1. https://download.youkuaiyun.com/download/infoworld/89925795

参考

  1. 绘图实现单行文本的多种颜色
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值