第5章 文本的输出方法与字体的设置 文本的输出方法与字体的设置 Windows经常使用GDI进行文本输出。事实上,在Windows中,图形和文本并没有明显的界限,很多时候,Windows把文本也当作图形对待。在一定意义上,任何内容都可以看成图形实体,文本事实上也是按照所选用字体的格式画出来的。一个字体包含了字符集中每一个字母、数字和标点符号的形状和外表的特殊信息。使用定义好的与设备无关的字体集,Windows就能维护它的设备无关性,提供“所见即所得”的好处,这就意味着屏幕上显示的文本与用打印机或绘图仪等输出设备输出的文本是完全一样的。 在Windows编程中,文本操作首先要获得文本句柄,此外,应用程序还要设置字体、字符大小、字符颜色等有关属性,并将这一些属性选入设备环境,然后输出到输出设备。 5.1 设置文本的设备环境 字体描述了所要显示的文本的大小、类型和外形,也就是说,字体包含了字符集中每个字符的一个特殊描述。在Windows中,字体一般又可以分成两大类型: 逻辑字体和物理字体。逻辑字体定义的字符集是设备无关的,而物理字体则是为特殊设备设计的,因而是设备相关的。逻辑字体的开发相对设备字体来说更为困难,但是由于其与设备无关的特性使得逻辑字体更灵活,而且逻辑字体往往是可精确标度的,因此逻辑字体得到了广泛的应用。 5.1.1 字体句柄 Windows 系统提供了7种基本字体,如表5-1所示。表5-1 Windows系统提供的基本字体 字 体说 明字 体说 明ANSI_FIXEDANSI标准的固定宽度的字体ANSI_VARANSI标准的可变宽度的字体DEFAULT_GUI当前GUI的默认字体DEVICE_DEFAULT当前图形设备的字体OEM_FIXED标准原设备制造商(OEM)提供的字体SYSTEM_FIXEDWindows的标准固定宽度的字体SYSTEMWindows提供的可变宽度的字体,它常作为默认字体 常用的默认字体为SYSTEM,Windows使用该字体作为系统界面字体,选择系统字体一般需要执行如下步骤: (1) 定义字体句柄变量,语法如下: HFONT hF; //hF为字体的句柄 (2) 调函数GetStockObject获得系统字体句柄,它返回的是系统的默认字体,语法如下: hF=GetStockObject(); (3) 调用函数SelectObject将字体选入设备环境,语法如下: SelectObject(hdc,hF);5.1.2 创建自定义字体 系统提供的字体往往不能满足应用程序的需要,实际上,中文的字体是很丰富的。目前有40多种字体,程序员可调用函数CreateFont创建自定义字体。该函数的调用形式如下: HFont=CreateFont ( int nHeight, //字体高度,取0则采用系统默认值,使用逻辑单位 int nWidth, //字体宽度,取0则由系统根据高宽比取最佳值,使用逻辑单位 int nEscapement, //每行文字相对于页底的角度,以十分之一度为单位 int nOrientation, //每个文字相对于页底的角度,以十分之一度为单位 int nWeight, //字体粗细度,取值范围为0~1000,例如400为正常字体,700为黑体 DWORD fdwltalic, //如果要求字体倾斜,则取非零 DWORD fdwUnderLine, //如果要求下划线,则取非零 DWORD fdwStrikeOout, //如果要求中划线,则取非零 DWORD fdwCharSet, //字体所属字符集 DWORD fdwOutputPrecision, //输出精度,一般取默认值OUT_DEFAULT_PRECIS DWORD fdwClipPrecision, //剪裁精度,一般取默认值CLIP_DEFAULT_PRECIS DWORD fdwQuality, //输出质量,一般取默认值DEFAULT_QUALITY DWORD fdwPitchAndFamily, //字体的间距及字体的系列,一般取默认值DEFAULT_PITCH DWORD lpszFacename //字体名 );其中参数fdwCharSet 定义的字符集有: ANSI_CHARSET, BALTIC_CHARSET, CHINESEBIG5_CHARSET, DEFAULT_CHARSET, EASTEUROPE_CHARSET, GB2312_CHARSET, GREEK_CHARSET, HANGUL_CHARSET, MAC_CHARSET, OEM_CHARSET, RUSSIAN_CHARSET, SHIFTJIS_CHARSET, SYMBOL_CHARSET, TURKISH_CHARSET, VIETNAMESE_CHARSET等,此外还有朝鲜、中东和泰国等国家地区的字符集。 5.1.3 设置字体和背景颜色 了解了字体句柄及创建字体以后,在有关文本输出的编程中,还需要进一步了解字体的设置及背景颜色的设置,这样才能得到精美的输出效果。 应用程序通过调用函数SetTextColor设置字体颜色,其形式为: SetTextColor(hdc,crColor); //crColor为设置的颜色 应用程序还可调用函数SetBkColor设置背景颜色,其形式为: SetBkColor(hdc,crColor); 5.2 文本的输出过程 在定义了字体句柄、字体及字体颜色以后,就可以把设置的字体输出到相应的设备上。Windows应用程序的文本输出过程比较复杂,因为程序员除了要确定输出内容外,还要管理输出的格式及位置,由应用程序完成窗口用户区管理,Windows系统并不参与窗口用户区的管理。这样虽然为程序员管理用户区提供了编程的自由,但也加重了编写应用程序的负担。例如,在用户区内输出文本,应用程序必须管理换行、后续字符的位置等输出格式,Windows系统并未提供管理输出文本格式的函数。 文本的输出过程中应用程序必须先确定文本在窗口中输出的位置。确定文本的位置通常用绝对定位和相对定位的方式。绝对定位就是用逻辑坐标来定位,它的缺点是已输出文本对后续位置有影响,这种影响无法从直接定位坐标中体现出来。而且当窗口的位置或输出字体发生变化时,文本不能随着窗口的尺寸和新的字体的变化而灵活调整。相对定位则根据已输出内容,通过获取字体信息,然后格式化文本,确定后续文本的输出的位置,调用函数在窗口中输出文本。 1. 获取字体信息 应用程序在输出文本之前必须获取当前使用字体的有关信息,如当前使用的字符高度等,以确定输出文本格式和下一行字符的输出位置。 Windows程序中通过调用函数GetTextMetrics获取当前使用的字体信息。调用该函数时,系统将当前字体的信息拷贝到tm标识的TEXTMETRIC结构中。其形式为: GetTextMetrics(hdc,&tm); //tm为TEXTMETRIC结构 系统定义的TEXTMETRIC的结构如下: typedef struct tagTEXTMETRIC { LONG tmHeight; //字符高度 LONG tmAscent; //字符基线以上高度 LONG tmDescent; //字符基线以下高度 LONG tmInternalLeading; //tmHeight制订的字符高度顶部的控件 LONG tmExternalLeading; //行与行之间的间隔 LONG tmAveCharWidth; //平均字符宽度 LONG tmMaxCharWidth; //最大字符宽度 LONG tmWeight; //字符的粗细度 LONG tmOverhang; //合成字体间附加的宽度 LONG tmDigitizedAspectX; //为输出设备设计的X轴尺寸 LONG tmDigitizedAspectY; //为输出设备设计的Y轴尺寸 BCHAR tmFirstChar; //字体中第一个字符值 BCHAR tmLastChar; //字体中最后一个字符值 BCHAR tmDefaultChar; //代替不在字体中字符的字符 BCHAR tmBreakChar; //作为分割符的字符 BYTE tmItalic; //非0则表示字体为斜体 BYTE tmUnderlined; //非0则表示字体有下划线 BYTE tmStruckOut; //非0则表示字符为删除字体 BYTE tmPitchAndFamily; //字体间距和字体族 BYTE tmCharSet; //字符集 }TEXTMETRIC; 调用函数GetTextMetrics获取当前字体的TEXTMETRIC结构后,即可为其中的成员设置文本输出格式。 2. 格式化文本 格式化处理一般针对两种情况: 一是在文本行中确定后续文本的坐标,二是在换行时确定下一行文本的坐标。 1) 确定后续文本坐标 确定后续文本的坐标,应先获取当前的字符串的宽度,Windows系统提供函数GetTextExtentPoint32完成这项任务,并把它存储于一个SIZE结构中。该函数的原型为: BOOL GetTextExtentPoint32 ( HDC hdc, LPCTSTR lpszString, //lpszString为指定的字符串 int nLength, //nLength为字符串中的字符数 LPSIZE lpSize //lpSize为返回字符串宽度及高度的SIZE数据结构的地址 );SIZE数据结构的定义如下。 typedef struct tagSIZE { LONG cx; LONG cy; } SIZE;通过计算字符串的起始坐标与字符串宽度之和,即可得到后续文本的起始坐标。例如,X轴起始坐标为x0,如果当前字符串的信息存储在size指向的SIZE结构中,则后续文本的起始坐标x为: x=x0+size.cx;2) 确定换行时文本坐标 通过计算当前行文本字符的高度与行间隔之和,即可得到换行时文本的起始坐标,而上述两个数值均可通过获取当前字体的信息得到。若当前行的坐标为y0,则换行时Y轴上文本的坐标y为: y=y0+tm.tmHeight+tm.tmExternalLeading; //tm的信息由函数GetTextMetrics获取或: y=y0+size.cy; //size的信息由函数GetTextExtentPoint32获取3. 文本输出 Windows编程中最常用的文本输出函数是TextOut,其函数原型如下: BOOL TextOut ( HDC hdc, int X,int Y, //(X,Y)为用户区中字符串的起始坐标 LPCTSTR lpString, //lpString为显示的字符串 int nCount //nCount为字符串中的字节数 );程序员调用函数TextOut,以坐标(X,Y)为起点,输出字节数为 nCount、名为lpString的字符串。 也可以使用DrawText函数将文本输出到一个矩形中,DrawText函数的原型如下: int DrawText( HDC hDC, //HDC句柄 LPCTSTR lpString, //输出的文本内容 int nCount, //文本长度 LPRECT lpRect, //输出尺寸 UINT uFormat //输出选项 ); 5.3 文本操作实例 【例5-1】 在用户窗口上输出一个扇形,并在扇面竖向输出一首唐诗,本例使用绝对定位确定输出文字的位置,并采用多种自定义字体输出文字。本例的运行结果见图5-1. 本例的源程序代码如下: #include #include #include #define PI 3.1415926 BOOLEAN InitWindowClass(HINSTANCE hInstance,int nCmdShow); LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); HFONT CreateMyFont(TCHAR fontName,int height,int lean); //创建自定义字体, //三个参数分别是字体名称,字体大小,字体的倾斜度,倾斜度以/10为一个逻辑单位 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) { MSG msg; if(!InitWindowClass(hInstance,nCmdShow)) { MessageBox(NULL,L"创建窗口失败!",_T("创建窗口"),NULL); return 1; } while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return(int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; HFONT font; HPEN hPen; LPWSTR title=L"登高唐.杜甫",poem\={L"风急天高猿啸哀",L"渚清沙白鸟飞回",L"无边落木萧萧下",L"不尽长江滚滚来",L"万里悲秋常作客",L"百年多病独登台",L"艰难苦恨繁霜鬓",L"潦倒新停浊酒杯"}; int r,r0,i,j=-1,fontSize,fontSize0,color; RECT clientDimension; //存放客户区的尺寸 POINT begin,end,org; //保存点的信息,org表示圆心坐标 double sita; //表示文字倾斜及画图时的角度 switch(message) { case WM_SIZE: InvalidateRect(hWnd,NULL,true); break; case WM_PAINT: hDC=BeginPaint(hWnd,&ps); hPen=CreatePen(PS_DASH,1,RGB(127,127,127)); SelectObject(hDC,hPen); GetClientRect(hWnd,&clientDimension); //获取客户区的尺寸 if((clientDimension.right-clientDimension.left)<400||(clientDimension.bottom-clientDimension.top)<300) //判断屏幕尺寸 { MessageBox(hWnd,L"屏幕尺寸太小,无法绘图!",L"错误信息",0); break; } r=(clientDimension.bottom-clientDimension.top)8/10; //用屏幕高度的/5作为扇形的半径 org.x=(clientDimension.right-clientDimension.left)/2; org.y=(clientDimension.bottom-clientDimension.top)9/10; //将圆心坐标定在屏幕中间向下的/10处 Arc(hDC,org.x-r,org.y-r,org.x+r,org.y+r,org.x+(int)(rsin(PI/3)), org.y-(int)(rcos(PI/3)),org.x-(int)(rsin(2PI/3)), org.y+(int)(rcos(2PI/3))); //画外围圆弧 for(sita=PI/6;sita<=PI5/6;sita+=PI2/27) { begin.x=org.x-(int)(rcos(sita)); begin.y=org.y-(int)(rsin(sita)); MoveToEx(hDC,begin.x,begin.y,NULL); end.x=org.x; end.y=org.y; LineTo(hDC,end.x,end.y); } //画折线 r0=r2/5; Arc(hDC,org.x-r0,org.y-r0,org.x+r0,org.y+r0,org.x+(int)(r0sin(PI/3)),org.y-(int)(r0cos(PI/3)),org.x-(int)(r0sin(2PI/3)),org.y+(int) (r0cos(2PI/3)));//画内侧圆弧 sita=PI/6+PI4/15/5; //右侧第一列角度 fontSize0=fontSize=(r-r0)/7; //字体的大小 r0=r-20; //半径逐步减小 for(i=0;i<7;i++) { LPCWSTR outInfo=&title\; //逐步取诗的标题字 fontSize-=3; font=CreateMyFont(L"楷体_GB2312",fontSize-5,-(sita+PI/15)1800/PI); //创建字体 SelectObject(hDC,font); //将创建的字体句柄选入设备环境 begin.x=org.x+(int)(r0cos(sita)); begin.y=org.y-(int)(r0sin(sita)); //计算输出文字的坐标 TextOut(hDC,begin.x,begin.y,outInfo,1); //输出文字 r0-=fontSize; //文字位置由外向内移动 DeleteObject(font); } for(sita=PI/6+PI4/27-PI/40;sita #include BOOLEAN InitWindowClass(HINSTANCE hInstance,int nCmdShow); LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) { MSG msg; if(!InitWindowClass(hInstance,nCmdShow)) { MessageBox(NULL, L"创建窗口失败!", _T("创建窗口"), NULL);