关于LOGFONT和CreateFontIndirect的问题

ddeng 2001-08-21 05:26:30
请问LOGFONT结构中的lfWidth究竟该怎么设?我该将它设成多少才能和设为0时输出结果一样?
设为0时是正常的,而按照下面方法设置的输出竟然比设为0时扁一些,为什么?

LOGFONT lf;
ZeroMemory(&lf, sizeof(LOGFONT));
lf.lfCharSet = GB2312_CHARSET;
strcpy(lf.lfFaceName, "ËÎÌå");
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = FW_NORMAL;
lf.lfHeight = -100;
lf.lfWidth = -lf.lfHeight*72/GetDeviceCaps(Canvas->Handle, LOGPIXELSY);
Canvas->Font->Handle = ::CreateFontIndirect(&lf);
Canvas->TextOut(300, 200, "好");
...全文
2367 14 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
ddeng 2001-08-22
  • 打赏
  • 举报
回复
SCUM(人渣):不好意思,还是不能直接根据像素值设置宽度:(
不过谢了,下午再解决不了,还是要给你分……
SCUM 2001-08-21
  • 打赏
  • 举报
回复
另一篇:


Delphi利用Windows GDI实现文字倾斜 (编程开发相关 -- DELPHI, 人气:3)
网友对本文的评论共有0篇 [查看/发表评论] [专业IT技术论坛]

一、引言

  在Delphi开发环境中,文字的输出效果一般都是头上脚下的"正统"字符,如何输出带有一定倾斜角度的文字以达到特殊的显示效果呢?笔者在制作公章辅助设计软件过程中,通过Windows图形设备接口函数实现了文字呈辐射状分布的功能。

  Windows图形设备接口(Graphics Device Interface)是Windows系统中的图形核心模块,以GDI.EXE形式提供给开发者使用。Windows中的图形基本上是由GDI.EXE中的函数来处理的,它的主要特点是在输出设备上支持与设备无关的图形。在GDI函数中,文本亦被视为一种图形对象,但并非简单的几何方式绘制,因为文本的输出涉及到字体。

  根据Windows中字体所用的技术可以分为3类字体:光栅字体,矢量字体和TrueType字体。其中,True Type字体的主要优点之一是可被任意放大或旋转,在任何尺寸上,都可以达到较为满意的显示效果,尤其,其允许在显示器和打印机上使用相同的字体。Windows为应用程序之所以能够提供设备无关性是通过采用让应用程序创建逻辑对象从而使用物理对象的方法实现的,同样,对于字体的使用也是一样的:应用程序在使用字体时,首先创建逻辑字体,以便Windows从设备中选择一种与之匹配的物理字体,从而达到较为准确的输出效果。

  使用Windows GDI函数实现文字的倾斜效果的基本步骤如下:

  1.取得设备描述表或图形设备的句柄:

   HDC GetDc(Thandle AWinHandle);

  2.创建逻辑字体并设置文字的倾斜度:

   HFont CreateFontIndirect(LogFont Far *Iplf),

   其中,Iplf为tagLogFont结构体,通过设置其lfEscapement可设置文字的倾斜度(为实际度数的10倍),例如:0度-正常字体,450度-左上45度倾斜,900度-正向左,等。

  3.将逻辑字体选入设备列表,从而,Windows选择与之匹配的物理字体,并且新的字体取代旧的字体:

   HGDIOBJ SelectObject(HDC hdc,HGDIOBJ hgdiobj);

   注:在此之前现

  4.使用当前设备的字体进行文字输出:

   BOOL TextOut(HDC hdc, int PosX, int PosY, LPCTSTR OutStr, int StrLength);

  5.释放不再使用的图形对象(这里为逻辑字体):

   BOOL DeleteObject(HGDIOBJ hObject);

  6.释放设备描述表或图形设备的句柄,函数声明如下:

   ReleaseDc(THandle AWinHandle,HDC Dc);

   注:上述过程要求在窗口的Paint事件中编写重新绘画或绘制文字的代码。

  二、程序实现

  在Delphi5.0中新建一个工程,在主窗体的Paint事件中编写如下代码:


 procedure TForm1.FormPaint(Sender: TObject);
  var
   FLogFont : tagLogFontA; file://逻辑字体--结构体类型
   hTempFont, hPrevFont: HFONT; file://字体句柄
   hTempDC: HDC; file://设备描述表或图形设备句柄
   TempString: string; file://输出的文字
  begin
   FLogFont.lfHeight := 10; file://字高
   FLogFont.lfWidth := 10; file://字宽
   FLogFont.lfWeight := 1; file://字体笔划粗细程度
   FLogFont.lfUnderline := 0; file://没有下划线
   FLogFont.lfStrikeOut := 0; file://没有删除线
   FLogFont.lfItalic := 0; file://斜体效果否
   FLogFont.lfCharSet := GB2312_CHARSET; file://字符集
   FLogfont.lfEscapement := 450; file://倾斜度
   FLogFont.lfOrientation := 450; file://方向与倾斜度取值同
   FLogFont.lfFaceName := '宋体'; file://字体名称
   file://创建逻辑字体
   hTempFont := CreateFontIndirect(FLogFont);
   TempString := '测试';
   file://取得窗口的设备句柄
   hTempDC := GetDC(Handle);
   file://取出窗口设备的当前字体,并替换为新字体
   hPrevFont := SelectObject(hTempDC, hTempFont);
   file://设置设备窗口的文字色彩
   SetTextColor(hTempDc, clRed);
   file://输出文字
   TextOut(hTempDc, 200 , 200, PChar(TempString), Length(TempString));
   file://恢复原有的字体
   SelectObject(hTempDc, hPrevFont);
   file://删除逻辑字体
   DeleteObject(hTempFont);
   file://释放设备接口
   ReleaseDC(Handle, hTempDC);
  end;


以上代码若不写在窗体的Paint事件,则当窗体接收到重绘消息时,默认的窗口绘制过程将重新绘制窗口而使倾斜文字消失。

  三、结束语

  以上,只是Windows GDI接口函数使用的一个具体例子,希望能对广大读者起到抛砖引玉的作用,利用Windows GDI接口函数编写出更为丰富多彩的的文字效果。








以上两篇可以解决你的问题了。

SCUM 2001-08-21
  • 打赏
  • 举报
回复
一篇文摘:


深入WINDOW字型

在探讨更深入的剧情处理以前,我们必须拥有输出文字的能力。这次我们的目标是撷取WINDOW系统资源,并且与DirectX全萤幕游戏相结合,你将会学习到如何有效快速地将字型套用到你的游戏上面。

□ 掌握正确方向

记得我曾经说过,在DOS下开发游戏,与WINDOW下的差别是很大的,曾经令人苦恼的问题,在WINDOW下都有更方便的解决方式,最明显的例子就是音乐播放,以及我们这次讨论的字型使用。在很多较老旧的游戏书籍里面,都会教你如何秀中文,比较常见的方法就是利用倚天中文的字型档。并且还需要利用文字索引的技巧,以节省记忆体空间。这一切都过去了,等等你会看到我们在WINDOW下面的实作方式。这边所谓「正确的方向」是指有效率的解决方式,当然早期的作法仍然可以适用于WINDOW环境。只是要额外付出许多代价就是了。

□ WINDOW的字型

在中文的WINDOW里面,一安装完毕就会有许多字型可以套用,其中的中文字型至少有明体与标楷体,这些字型属于系统资源的一部份,任何应用程式皆可大方地使用。该如何使用呢?写过WIN32 APP的人大抵上都知道WINDOW API中,有一个字型对话方块,可以很方便的取的某个字型的资料,不过我不打算使用这个对话方块函示,实际上自己动手更有弹性,也不会浪费多少时间,以下开始实作。

□ 取得字型资讯

一个应用程式如果没有指定字型,则使用系统内定的字型。如果要使用标楷体字型的话,必须先取得此种字型的资讯,当然还可以指定字型的大小等等。打开字型对话方块,你会发觉字型的种类很多,但是我们需要的只有中文字型而已,所以我们忽略其他资讯,专注于我们需要的部份。这里我要强调的是,如果只强调秀出中文,你大可不必管他字型怎么来的(毕竟,我们的WINDOW环境本身就是中文的),使用内定字型即可,如果你还要强调系统字型的美观,就需要选择一种比较容易搭配的字型,更进一步如果要让使用者在游戏内自由选择系统任一种字型,则需要列举出所有可用的字型。并且将这些资讯收集起来,提供程式使用。

好的,我们先介绍如何列举出所有的系统字型,首先介绍这个函示:

 

int EnumFontFamilies(

HDC hdc, // handle to device control
LPCTSTR lpszFamily, // pointer to family-name string
FONTENUMPROC lpEnumFontFamProc, // pointer to callback function
LPARAM lParam // address of application-supplied data
); //取自VC线上说明

 

这个函示需要的参数共有四个,第一个参数是绘图使用的设备代码,在一般应用程式,我们会使用GetDC()来取得他的设备代码,而在DirectX里面,我们必须使用IDirectDrawSurface3::GetDC(),由这个函示取得的DC才能保证与GDI的函示相容。第二个参数设定为NULL则会取得所有的字型,包括固定宽度字型与向量字型。第三个参数是一个CALLBACK函示指标,他的原型固定,系统会自动呼叫这个函示,而实作此函示的我们,正好可以将系统字型撷取下来,第四个参数用不到,设为NULL即可。

 

至于FONTENUMPROC的原型是这样子的:

int CALLBACK EnumFontFamProc(

ENUMLOGFONT FAR* lpelf, // pointer to logical-font data
NEWTEXTMETRIC FAR* lpntm, // pointer to physical-font data
int FontType, // type of font
LPARAM lParam // address of application-defined data

); // 取字VC 线上说明

 

这个函示的四个参数由系统传给我们,里面包含我们所需要的一切资讯,现在我们就看看实际上该如何使用,底下撷取自CFONT类别的实作内容:

//此成员函示呼叫以后,会开始取得系统字型

void CFont::QueryFont(){

lpFrontBuffer->GetDC(&hdc);//取得前景DC
EnumFontFamilies(hdc, (LPCTSTR)NULL,(FONTENUMPROC) EnumFamCallBack,(LPARAM)NULL); lpFrontBuffer->ReleaseDC(hdc);//释放DC

}

 

CFont::QueryFont()仅设定好初始的资料,真正接收字型资料的部份在CALLBACK函示,而CALLBACK函示我们应该怎么实作呢?底下便是:

 

首先我们配置三个结构以存放「细明体」「新细明体」与「标楷体」的字型资料, LOGFONT logfont[3];

 

这个结构可以存放字型的细部资料,其内容相当繁杂,且不是所有的资料都派上用场,更详细的资料可以在VC线上说明取得。需要的话,你可以配置更多的空间以存放各式各样的字型资料,这边我只示范三种常用字型。接著我们看一下该怎么在CALLBACK函示里面接收这些资料:

 

BOOL CALLBACK CFont::EnumFamCallBack(LPLOGFONT lplf,LPNEWTEXTMETRIC lpntm ,DWORD FontType,LPARAM aFontCount)
{
if(strcmp(lplf->lfFaceName,"细明体")==0)//仅找明体与标楷体
memcpy(&logfont[0],lplf,sizeof(LOGFONT));//资料存放到logfont[]阵列
if(strcmp(lplf->lfFaceName,"新细明体")==0)
memcpy(&logfont[1],lplf,sizeof(LOGFONT));
if(strcmp(lplf->lfFaceName,"标楷体")==0)
memcpy(&logfont[2],lplf,sizeof(LOGFONT));
return TRUE;
}

 

刚刚有说到这个CALLBACK函示的四个参数是系统传给我们的,其中的LPLOGFONT包含了一种字型的资料,我们藉由判断其名称来决定这个字型是不是我们需要的,如果是的话,将他拷贝到我们预先配置好的LOGFONT结构里面。这个函示事实上会持续呼叫,直到找完所有的字型为止,所以在这个过程中,我们只接收三种字型的资料,其他的都忽略不处理。当这个函示完成以后,我们配置的LOGFONT[3]这个阵列里面,已经包含我们所需要的资料了。大事已经完成一半了,接著我们应该做什么事情呢?

 

□ 将字型设定给DC

取得字型资料以后,实际上什么事情也没发生,我们必须根据字型的资料,来产生一个字型代码给API函示使用,这个处理过程我将他包在成员函示CFont::SetFont(HDC hdc,int FontType,int width,int height)里面,实作内容如下:

 

void CFont::SetFont(HDC hdc,int FontType,int width,int height)

{

switch(FontType)
{
case MINGLIU:
logfont[0].lfHeight=height;
logfont[0].lfWidth=width;
hFont = CreateFontIndirect (&logfont[0]);
break;

case NEWMINGLIU:
logfont[1].lfHeight=height;
logfont[1].lfWidth=width;
hFont = CreateFontIndirect (&logfont[1]);
break;

case KAIU:
logfont[2].lfHeight=height;
logfont[2].lfWidth=width;
hFont = CreateFontIndirect (&logfont[2]);
break;
}

SelectObject(hdc,hFont);
}

 

这个成员函示接收四个参数,第一个参数是欲设定字型的目的DC,第二个参数决定要设定何种字型,第三第四个参数决定字型的大小。多方便阿,连字型大小都可以自由设定,不过还是要适中才会好看。所以实际呼叫的时候,我们是这样做的:

SetFont(hdc,NEWMINGLIU,10,15);//将前景DC与字型相结合

为了美观起见,我把三种字型另外定义其名称:

 

#define KAIU 5 //标楷体
#define MINGLIU 6//细明体
#define NEWMINGLIU 7//新细明体

所以上面的SetFont()我们是选择了新细明体,并且决定其字型宽度10,高度15,当然,宽度高度是可以任意变化的,决定好宽度高度以后,接著使用CreateFontIndirect ();其传回值为字型代码,最后利用SelectObject(hdc,hFont);把他真正设定给DC就可以了。

感觉上这个过程绕来绕去的,都没有一枪毙命的感觉,唔~~~我也这样认为,所以我还是把到目前为止的过程整理一下吧:

 

1. 使用EnumFontFamilies()列举字型
2. 在CALLBACK函示里面,接收字型资讯
3. 使用字型的时候,将字型与DC结合
4. 目前为止,大事完成2/3。

 

□ 规画秀字的方式

好的,我用最简单的方式来秀字看看,要用什么函示呢?TextOut()是也,简单又大方,亲切又可爱,而且在任何地方,秀字总免不了使用这个函示,我们来看看他的样子:

 

BOOL TextOut(
HDC hdc, // handle of device context
int nXStart, // x-coordinate of starting position
int nYStart, // y-coordinate of starting position
LPCTSTR lpString, // address of string
int cbString // number of characters in string
); // 取自VC++4.0线上说明

 

可以指定座标与字串,果然是为我们精心设计的API,不用怎么对得起别人呢?示范一下我要在座标(20,25)的地方秀出一段文字,我这么做:

TextOut(hdc,20,25,"相当稳用",8);

果然没问题,不过呢,我们需要再包装一层,让这个函示更人性化一点,所以我又实作了一个成员函示void CFont::ShowFont(char* string),这个成员函示接收一个指向任意长度的字串指标,并且按照我们预先设计好的格式秀出来,这个格式需要讨论一下,我自己决定的方式是这样子的:

 

1. 在萤幕座标 (46,120)的地方开始秀字
2. 一列以十四个中文字为最长的长度,超过换列。
3. 萤幕最多同时容纳四列,超过的话清除字串,从第一列输出。
4. .........(依照喜好,自己定格式)

 

一旦决定好以后,根据这些规则我们实作出来的内容是这样子的:

 

void CFont::ShowFont(char* string)
{
int Line,Cycle;
int i,j,k,m=0;
int count=0,ShowedFont=0;//累计秀出的字

while(string[count]!=0)

count++;//先取得这个字串的长度

if(count%2) //不足2 bytes则补足
count++;

count/=2; //COUNT除以二变成中文字个数

Line=count/14; // 每列14个中文字,所以我们计算共需要几列

Cycle=Line/4; //每个画面最多4列,所以我们计算需要几个画面

Cycle++; //至少一个画面

//可见页拷贝到隐藏页,文字秀出以后,恢复萤幕用

lpBackBuffer->Blt(NULL,lpFrontBuffer,NULL,DDBLT_WAIT,NULL);

SetFont(hdc,NEWMINGLIU,10,15);//将前景DC与字型相结合

for(k=0;k<Cycle;k++)//四行字为一个回圈
{
lpFrontBuffer->GetDC(&hdc);//取得前景DC
SetBkMode(hdc,TRANSPARENT);//设定秀字背景为透明色
for(i=0;i<4;i++)//一行字为一个回圈,共四行
{

for(j=0;j<14;j++)//一行字里面有14个中文字
{
//第一次秀黑色
SetTextColor(hdc,RGB(0,0,0));
TextOut(hdc,46+j*17,120+i*15,&string[m*28+j*2],2);

//第二次左移一个像点秀出另一色,制造框线效果
SetTextColor(hdc,RGB(255,0,0));
TextOut(hdc,45+j*17,120+i*15,&string[m*28+j*2],2);
Sleep(20);//稍微延迟,控制速度
ShowedFont++;

if(ShowedFont==count)goto Finish;//秀完了吗?离开回圈
}//for j end

m++;//指向下一列给TextOut()使用
}// for i end

Finish:

lpFrontBuffer->ReleaseDC(hdc);

ReleaseFont();

Sleep(200); //延迟

while(GetAsyncKeyState(VK_SPACE)>=0);//按空白键继续

lpFrontBuffer->Blt(NULL,lpBackBuffer,NULL,DDBLT_WAIT,NULL);

}//for k end

//还原萤幕画面
lpFrontBuffer->Blt(NULL,lpBackBuffer,NULL,DDBLT_WAIT,NULL);
Sleep(300);
return;

} // function end

 

虽然加上注解了,我还是稍微说明一下,首先函示会收到一个不定长度的字串,第一个步骤我们先计算他的长度,此处的长度单位是byte,除以二以后就变成中文字的个数了,接著利用国小数学,我们计算出总共要几列,几个画面才得以把这个字串秀完。所以回圈里面就是在做这一件事情,最后跳出回圈的方式,我们判断已经秀出的字是否跟传进来的长度一样,如果一样,代表我们已经秀字完毕了,goto跳出来就好了,轻松。

 

另外保存萤幕画面是有必要的,不过这边我的作法相当直接。在秀字的时候,我们是直接秀到前景的绘图页(surface),所以TextOut()一呼叫完毕,萤幕上马上会出现这些字串。所以你知道了,在我们秀字的当时,背景的绘图页是闲置的,于是我们在秀字串以前,把萤幕画面Blt()拷贝到背景绘图页,等到秀字完毕以后,要恢复画面,则反向操作,从背景绘图页拷贝到前景绘图页即可。我的初步结论是,一切较静态的画面,你直接画在前景surface即可,不必担心会有闪烁的现象。

 

在秀字的过程中,每一个字我们都秀两次,这是为什么呢?达成文字边框的效果是也。同一套字型,我们利用不同颜色画上去,效果不错,其原理是这样子的,我随便举例说明:

 

第一次我在座标(46,120)的地方用黑色秀出一个「中」字,第二次我在座标(45,120)的地方用红色秀出一个「中」字,你可以看到这两个字的偏移只有一个x座标单位,所以红色的「中」字会覆盖掉黑色的「中」字,但是最右边的黑色部份则不会覆盖到(因为我们偏移一个x单位嘛),这简单的过程,我们好像获得了一套新的,更漂亮的字型一样,这技巧够酷。

各位也可以看到函示里面Sleep()的部份,主要是控制秀出字串的速度,你可以在字与字之间控制间隔的速度,如果没有稍微延迟的话,则一整面的字瞬间秀完。当然最好的方法是把Sleep()函示的参数(延迟时间)当成变数,让使用者决定他想要的速度。

 

最后我要说的是,在你使用IDirectDrawSurface3::GetDC()获得DC以后,记得要释放他,不这么做的话,其他绘图的动作都会失败,这跟我们平常使用GDI的GetDC()是有差别的。

 

□ 整合一下

到目前为止,初步完成99%的任务。我们学到了如何利用系统的字型资源,并应用在我们的程式上面,整个字型类别是这样子的:

class CFont
{
public:
void QueryFont(); //找中文字型存入 LOGFONT[]
void SetFont(HDC,int,int,int);//设定字型以及大小
void ReleaseFont();//释放字型资源
void ShowFont(char *);//秀出格式化字串

private:
static BOOL CALLBACK EnumFamCallBack(LPLOGFONT,
LPNEWTEXTMETRIC,DWORD,LPARAM);
HFONT hFont;//字型代码
};

 

我的目的是解释其过程,并非要写一个现成的东西给你用,所以还有很多事情需要靠你自己去完成。比方说,一般人系统里面的中文字型,并不仅止于明体与标准楷体,或许还有华康等其他的字型,我们或许应该列举出所有的中文字型,让程式更有弹性。在字型大小方面,是可以调整的,所以你也不必要担心在320x200的画面字型是否会过大,在800x600的画面下,字型是否太小。一切的操控权都在你手上。在秀字的同时,加上一个美丽的背景图框也是不错的选择。

 

□ 末语

看到这里,你大概已经知道WINDOW下的处理方式跟DOS下有相当相当的差别吧。如果你掌握到正确的处理方式,并节省下额外的时间,那么,看这篇文章就值回票价了。

以处理字型为目标,我们已经达成阶段性任务,以处理剧情为目标,我们只完成了开始的5%,剧情的表现上,大致上可以看成「说故事」一样,不断的把文字输入到脑海里面,就形成了剧情,当然也需要各种事件的参与。更有趣的是,架构单线剧情与多线剧情都是相当富挑战性的一件工作。我们是不是该开始规画了呢?规画前记得先洗把脸(可以不使用洗面皂),让思绪更加畅通。
ddeng 2001-08-21
  • 打赏
  • 举报
回复
对于为什么lf.lfWidth要/2,我也不明白,是一个朋友告诉我的,他也不知道什么道理:(
lf.lfWidth值的设置究竟有没什么公式可用??
ddeng 2001-08-21
  • 打赏
  • 举报
回复
其实我主要目的是要控制输出的字符宽度和高度都为指定的具体值(像素值),但lfWidth设置的却是逻辑值,我想知道这个逻辑值和像素值什么关系?如我想输出一个“好”字,让其宽高都为50像素,那么对windows自带的字体这么设置就可以了:
lf.lfHeight = -50;
lf.lfWidth = 50/2;
...
然而对于office带的华文字体及方正字体等,输出的结果宽高却不是50,我画标尺测过,有的相差还挺大。
各位兄弟谁能知道怎么解决精确大小文字的输出??
whitelion 2001-08-21
  • 打赏
  • 举报
回复
cc
HEROIN 2001-08-21
  • 打赏
  • 举报
回复
也许我没明白你的意思。把72变成172,不就扁多了么?
HEROIN 2001-08-21
  • 打赏
  • 举报
回复
字体高度宽度没有关系,是你得程序让他们之间有了关系
lf.lfWidth = -lf.lfHeight*72/GetDeviceCaps(Canvas->Handle, LOGPIXELSY);
于是高度宽度总是成一个比例

ddeng 2001-08-21
  • 打赏
  • 举报
回复
不是,是想输出长字或者扁字,但宽度不好精确控制。
gloom 2001-08-21
  • 打赏
  • 举报
回复
Canvas->TextWidth(const AnsiString);
Canvas->TextHeight(const AnsiString);
gloom 2001-08-21
  • 打赏
  • 举报
回复
是不是有字间距啊
ddeng 2001-08-21
  • 打赏
  • 举报
回复
呵呵,我要的是通过怎样的计算就可以设置字体宽度啊,lfWidth和lfHeight是什么关系?
另外,一个奇怪的现象:
lf.lfWidth = w/2;
输出宽度就是w像素,不过只对Windows自带的字体管用,对其它象华文或者方正的字体就不管用了,不知道为什么。
HEROIN 2001-08-21
  • 打赏
  • 举报
回复
自己用循环语句测试,一个用默认,一个用自定义。
如果同样的字符如"A" GetCharWidth相同,那么...
ddeng 2001-08-21
  • 打赏
  • 举报
回复
奇怪,我明明写的是“宋体”嘛,怎么变成~~~??
strcpy(lf.lfFaceName, "宋体");

13,871

社区成员

发帖
与我相关
我的任务
社区描述
C++ Builder相关内容讨论区
社区管理员
  • 基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧