场景
-
在使用
Gdiplus
绘制文本时, 偶尔会遇到需要支持绘制格式化文本的时候,而Gdiplus
在这方面并不好,没有官方提供支持的类,也不支持HTML
标签和CSS
的样式. 而大部分提供这类富文本的绘制目前我所知道的一种就是通过Web控件
来处理的. 比如调用Chromium
的渲染实现. -
另外一种实现就是通过实现自定义的富文本
RichText
控件,QQ
以前就是用的富文本来实现,显示可能是HTML
渲染?不过貌似现在它的聊天窗口也不支持一个消息里用两种字体设置不同的文本部分.
说明
-
Gdiplus
常见的计算文本区域的方法是 graphics.MeasureString. 我们如果要绘制文本局部样式,那么一个方案就是通过把这段文本截取几部分,之后使用DrawString
方法对不同部分进行设置不同的Font
和Color
里进行绘制, 从而不同部分显示不同的样式,问题是我们如何计算不同部分的文本所在的区域RectF
? 使用MeasureString
分别计算不同的文本部分的RectF
,之后放入一个vector
, 在绘制的时候进行循环绘制。这个方案只能用于单行文本,还有一个问题就是每个部分在绘制时出现了多余的空白区域,导致每个文本部分间隔比较大。 -
使用
MeasureString
这个方法会会连文本的周围填充块pad
都算上. 在Gdiplus
里,由于Graphics
设置了反锯齿之类的效果, 会在绘制时自动拉伸,填充锯齿等行为,导致计算的大小会偏大,并且在使用DrawString
时如果设置的RectF
小于它所计算的宽时,会绘制不出来。这个问题目前我还没找到解决办法。比如MeasureString
得到的RectF
宽我减少10个像素, 绘制时剩余区域还有,但是文字就是不显示出来, 如图1。
CClientDC dc(m_hWnd);
Gdiplus::Graphics graphics(dc);
graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
图1:
Gdiplus
提供了 graphics.MeasureCharacterRanges 来获取一段范围文字的区域 Gdiplus::Region. 通过区域的Region->GetBounds()
来获取不包括外边填充pad
部分的文本大小。我们借此来获取文本的最小宽度. 之后再通过把下一段文本的X
坐标向左偏移的方式覆盖前面文本的pad
部分来达到无多余空白区域的效果。
如图2:
图2:
- 我封装了一个类,便于实现绘制单行的文本样式。至于换行的实现,暂时还没有好的方案.
代码
string_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*> GDIStringsFormat;
class StringDrawObject
{
public:
StringDrawObject();
inline StringDrawObject& SetFont(Gdiplus::Font* font){
font_ = font;
return *this;
}
inline StringDrawObject& SetColor(Gdiplus::Color* color){
color_ = color;
return *this;
}
inline StringDrawObject& SetString(const wchar_t* str){
str_ = str;
return *this;
}
inline StringDrawObject& 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;
}
inline void ClearGDIStringsFormat(){
sf_.clear();
}
inline void ClearSfValueMap(){
sf_value_map_.clear();
}
inline StringDrawObject& SetOffsetXY(int offsetx,int offsety){
offsetx_ = offsetx;
offsety_ = offsety;
return *this;
}
void CalcGDIStringsFormat(Gdiplus::Graphics* graphics);
void DrawGDIStringsFormat(Gdiplus::Graphics* graphics);
private:
Gdiplus::Font* font_;
wstring str_;
Gdiplus::Color* color_;
map<wstring,tuple<wstring,Gdiplus::Color*,Gdiplus::Font*>> sf_value_map_;
int offsetx_;
int offsety_;
vector<GDIStringsFormat> sf_;
};
#endif
string_draw_object.cpp
#include "stdafx.h"
#include "string_draw_object.h"
#include <regex>
#include <assert.h>
using namespace Gdiplus;
StringDrawObject::StringDrawObject()
{
font_ = NULL;
color_ = NULL;
offsetx_ = 0;
offsety_ = 0;
}
void StringDrawObject::DrawGDIStringsFormat(Gdiplus::Graphics* graphics)
{
StringFormat strFormat;
// 测试
//Color color_green(Color::Green);
//SolidBrush brush1(color_green);
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_);
//if(i%2 == 0)
// graphics->FillRectangle(&brush1,rect_one);
SolidBrush brush(*color);
graphics->DrawString(str_one.c_str(),str_one.size(),font,rect_one,&strFormat,&brush);
}
}
// https://stackoverflow.com/questions/11708621/how-to-measure-width-of-a-string-precisely
// https://bbs.csdn.net/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 StringDrawObject::CalcGDIStringsFormat(Gdiplus::Graphics* graphics)
{
assert(str_.size() && font_ && color_ && 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 index = 0;
StringFormat strFormat;
strFormat.SetFormatFlags(StringFormatFlagsMeasureTrailingSpaces);
StringFormat strFormatNoSpace;
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);
Region* r1 = NULL;
RectF rect_temp;
Gdiplus::RectF rect_default;
if(str1.size()){
strFormat.SetFormatFlags(StringFormatFlagsMeasureTrailingSpaces);
r1 = new Region[1];
rect_default.X = posx;
graphics->MeasureString(str1.c_str(),str1.size(),font_,rect_default,&strFormat,&rect_default);
CharacterRange charRanges[1] = { CharacterRange(0,str1.size())};
strFormat.SetMeasurableCharacterRanges (1,charRanges);
graphics->MeasureCharacterRanges(str1.c_str(), -1,font_, rect_default, &strFormat, 1, r1);
r1->GetBounds(&rect_temp,graphics);
rect_temp.X = rect_default.X;
sf_.push_back(GDIStringsFormat(str1,rect_default,color_,font_));
delete []r1;
}
///
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);
}
strFormatNoSpace.SetFormatFlags(StringFormatFlagsMeasureTrailingSpaces);
r1 = new Region[1];
Gdiplus::RectF rect_attrib;
rect_attrib.X = rect_temp.GetRight();
graphics->MeasureString(str2.c_str(),str2.size(),font_value,rect_attrib,&strFormatNoSpace,&rect_attrib);
CharacterRange charRanges[1] = {CharacterRange(0,str2.size())};
strFormatNoSpace.SetMeasurableCharacterRanges (1,charRanges);
graphics->MeasureCharacterRanges(str2.c_str(), -1,font_value, rect_attrib, &strFormatNoSpace, 1, r1);
r1->GetBounds(&rect_temp,graphics);
rect_temp.X = rect_attrib.X;
posx = rect_temp.GetRight();
// 可能前面没字符串
if(rect_default.Height)
rect_attrib.Y = rect_default.Y+(rect_default.Height-rect_attrib.Height)/2;
delete []r1;
sf_.push_back(GDIStringsFormat(str2,rect_attrib,color_value,font_value));
index = pos+length;
}
if(index+1 <= str_.size()){
auto xstr = str_.substr(index);
Gdiplus::RectF rect_default;
rect_default.X = posx;
graphics->MeasureString(xstr.c_str(),xstr.size(),font_,rect_default,&strFormat,&rect_default);
sf_.push_back(GDIStringsFormat(xstr,rect_default,color_,font_));
}
}
调用 View.cpp
// View.cpp : implementation of the CView class
//
/
#include "stdafx.h"
#include "resource.h"
#include "atlmisc.h"
#include "View.h"
using namespace Gdiplus;
BOOL CView::PreTranslateMessage(MSG* pMsg)
{
pMsg;
return FALSE;
}
int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
myview_.Create(m_hWnd,CRect(0,0,100,100));
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);
/// 应用字符串格式化 //
CClientDC dc(m_hWnd);
Gdiplus::Graphics graphics(dc);
graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
sdo_.SetString(L"今天是考试得了 %number% 分, 因为我从 %url% 那里获得了指导.");
sdo_.SetFont(font_);
sdo_.SetColor(&color_black_);
sdo_.SetOffsetXY(10,10);
sdo_.SetKeyValue(L"%number%",L"100",&color_red_,font_);
sdo_.SetKeyValue(L"%url%",L"https://infoworld.blog.csdn.net",&color_blue_,font_bold_);
sdo_.CalcGDIStringsFormat(&graphics);
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);
//背景图
Gdiplus::Graphics graphics(mdc);
graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
sdo_.DrawGDIStringsFormat(&graphics);
return 0;
}
项目下载
https://download.csdn.net/download/infoworld/12261663
参考
how-to-measure-width-of-a-string-precisely
c# winfrom GDI+ DrawString怎么画不同颜色的字