17.5 设置段落格式

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P816

        我们已经学会了如何选择和创建逻辑字体,显示是时候来尝试如何进行设置文本格式了。这一过程包括将分别使用以下四个对齐方式在边界范围内进行文本对齐:左对齐、右对齐、居中或两端对齐(即从一端的边界到另一端排列文本,平均分配字符间的空白)。对于前三种情况,可以使用 DrawText 函数的 DT_WORDBREAK 参数来实现,但这种方法有其局限限。例如,你无法确定 DrawText 在输出时,哪一部分文本能够落在矩形框内。DrawText 函数对于完成一些简单的工作十分方便,但对于更复杂的文本格式设置,可能需要借助 TextOut 函数。

17.5.1  设置简单的文本格式

        GetTextExtentPoint32 是处理文本时最有用的函数之一(这个函数的名称揭示了从 Windows 的早期版本以来的一些变化)。该函数可以根据当前设备环境中选定的字体,告诉你一个字符串在该字体中的宽度和高度:

GetTextExtentPoint32(hdc, pString, iCount, &size);
SIZE 结构的 cx 和 cy 字段中返回以逻辑单位来表示的该字符串的宽度和高度。这里我们用一个单行文本作为例子,假设你已经在设备环境选择了一个字体,现在想要输出如下文本:

TCHAR * szText[] = TEXT("Hello, how are you?");
文本要从纵坐标 yStart 开始,在 xLeft 和 xRight 坐标确定的边界之间。你的任务就是计算文本开始的横坐标 xStart 的值。

        如果显示的文本使用的是等宽字体,那么这个任务将变得非常容易,可惜大多数情况并非如此。你首先需要得到字符串的宽度和高度:

GetTextExtentPoint32(hdc, szText, lstrlen(szText), &size);
如果 size.cx 大于(xRight - xLeft),则表示两个边界间额距离不足以容纳该字符串。先让我们假设它足够长。

        如果要将文本对齐到左边界,你只需要设置 xStart 等于 xLeft,然后输出文本:

TextOut(hdc, xStart, yStart, szText, lstrlen(szText));
这十分简单。现在,你将 yStart 增加 size.cy,然后就可以开始写下一行文本了。

        如果要将文本对齐到右边界,你可以对 xStart 使用这个公式:

xStart = xRight - size.cx;

        如果要把文本对齐到左右边界的中央,则使用这个公式。

xStart = (xLeft + xRight - size.cx) / 2;

        现在,来看一下最难的任务——调整文本使其平均分布在平均左右边界之间。两个边界之间的距离是(xRight - xLeft)。如果不做任何调整,该文本的宽度为 size.cx。这两个值之间的差为

xLeft + xRight - size.cx
这个计算出的差值必须平均分布在字符串的三个空格字符上。这听起来像一个可怕的任务,不过还不算太难。为了实现这个目标,你可以调用:

SetTextJustification(hdc, xRight - xLeft - size.cx, 3);
第二个参数就是必须分配到字符串内的空格字符上的空间大小。第三个参数是空格字符的数量,在这个例子中是 3。现在设置 xStart 等于 xLeft,用 TextOut 输出文本:

TextOut(hdc, xStart, yStart, szText, lstrlen(szText));
文本将在 xLeft 和 xRight 边界之间进行调整。

        调用 SetTextJustification 时,如果要求的空间无法在空格字符之间平均分配,它将会生成一个误差项。这个误差项将会影响随后的 GetTextExtentPoint32 调用。每当开始新的一行时,需要调用下面的函数来清除这个误差项

SetTextJustification(hdc, 0, 0);

17.5.2  段落的处理

        如果你要处理整个段落,则需要从头开始扫描整个字符串寻找空格字符。每当遇到空格字符(或其他可以用来做间隔的字符),就可以调用 GetTextExtentPoint32 函数来确定左右边界之间的距离是否仍足够容纳文本。如果文本长度超过了该距离允许的范围,就回溯到前一个空格。这样你就确定了该行可以显示的字符串。如果你想调整该行,则调用 SetTextJustification 和 TextOut,清楚误差项后,继续处理下一行。

        图 17-9 所示的 JUSTIFY1 程序,对马克●吐温的《哈克贝利●费恩历险记》的第一段进行了排版。你可以从对话框中选择想要的字型,也可以使用菜单选项改变对齐方式(左、右、居中或两端对齐)。图 17-10 显示了使用 JUSTIFY1 程序对某个段落处理的结果。

/*------------------------------------------------
	JUSTIFY1.C -- Justified Type Program #1
					(c) Charles Petzold, 1998
------------------------------------------------*/

#include <Windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szAppName[] = TEXT("Justify1");

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	HWND		 hwnd;
	MSG			 msg;
	WNDCLASS	 wndclass;

	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = szAppName;
	wndclass.lpszClassName = szAppName;


	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName, TEXT("Justified Type #1"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL);

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

void DrawRuler(HDC hdc, RECT * prc)
{
     static int iRuleSize [16] = { 360, 72, 144, 72, 216, 72, 144, 72,
                                   288, 72, 144, 72, 216, 72, 144, 72 } ;
     int        i, j ;
     POINT      ptClient ;
     
     SaveDC (hdc) ;
     
          // Set Logical Twips mapping mode
     
     SetMapMode (hdc, MM_ANISOTROPIC) ;
     SetWindowExtEx (hdc, 1440, 1440, NULL) ;
     SetViewportExtEx (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
                            GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;
     
          // Move the origin to a half inch from upper left
     
     SetWindowOrgEx (hdc, -720, -720, NULL) ;
     
          // Find the right margin (quarter inch from right)
     
     ptClient.x = prc->right ;
     ptClient.y = prc->bottom ;
     DPtoLP (hdc, &ptClient, 1) ;
     ptClient.x -= 360 ;
     
          // Draw the rulers
     
     MoveToEx (hdc, 0,          -360, NULL) ;
     LineTo   (hdc, ptClient.x, -360) ;
     MoveToEx (hdc, -360,          0, NULL) ;
     LineTo   (hdc, -360, ptClient.y) ;
     
     for (i = 0, j = 0 ; i <= ptClient.x ; i += 1440 / 16, j++)
     {
          MoveToEx (hdc, i, -360, NULL) ;
          LineTo   (hdc, i, -360 - iRuleSize [j % 16]) ;
     }
     
     for (i = 0, j = 0 ; i <= ptClient.y ; i += 1440 / 16, j++)
     {
          MoveToEx (hdc, -360, i, NULL) ;
          LineTo   (hdc, -360 - iRuleSize [j % 16], i) ;
     }
     
     RestoreDC (hdc, -1) ;
}

void Justify(HDC hdc, PTSTR pText, RECT * prc, int iAlign)
{
	int		xStart, yStart, cSpaceChars;
	PTSTR	pBegin, pEnd;
	SIZE	size;

	yStart = prc->top;
	do							// for each text line
	{
		cSpaceChars = 0;		// initialize number of spaces in line

		while (*pText == ' ')	// skip over leading spaces
			pText++;

		pBegin = pText;			// set pointer to char at beginning of line

		do						// until the line is known
		{
			pEnd = pText;		// set pointer to char at end ot line

				// skip to next space

			while (*pText != '\0' && *pText++ != ' ');

			if (*pText == '\0')
				break;

				// after each space encountered, calculate extents

			cSpaceChars++;
			GetTextExtentPoint32(hdc, pBegin, pText - pBegin - 1, &size);
		} while (size.cx < (prc->right - prc->left));

		cSpaceChars--;				// discount last space at end of line

		while (*(pEnd - 1) == ' ')	// eliminate trailing spaces
		{
			pEnd--;
			cSpaceChars--;
		}

			// if end of text and no space characters, set pEnd to end

		if (*pText == '\0' || cSpaceChars <= 0)
			pEnd = pText;

		GetTextExtentPoint32(hdc, pBegin, pEnd - pBegin, &size);

		switch (iAlign)					// use alignment for xStart
		{
		case IDM_ALIGN_LEFT:
			xStart = prc->left;
			break;

		case IDM_ALIGN_RIGHT:
			xStart = prc->right - size.cx;
			break;

		case IDM_ALIGN_CENTER:
			xStart = (prc->right + prc->left - size.cx) / 2;
			break;

		case IDM_ALIGN_JUSTIFIED:
			if (*pText != '\0' && cSpaceChars > 0)
				SetTextJustification(hdc,
									prc->right - prc->left - size.cx, 
									cSpaceChars);
			xStart = prc->left;
			break;
		}

			// display the text

		TextOut(hdc, xStart, yStart, pBegin, pEnd - pBegin);

			// prepare for next line

		SetTextJustification(hdc, 0, 0);
		yStart += size.cy;
		pText = pEnd;

	} while (*pText && yStart < prc->bottom - size.cy);
	
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static CHOOSEFONT cf;
	static DOCINFO    di = { sizeof(DOCINFO), TEXT("Justify1: Printing") };
	static int        iAlign = IDM_ALIGN_LEFT;
	static LOGFONT    lf;
	static PRINTDLG   pd;
	static TCHAR      szText[] = {
								TEXT("You don't know about me, without you ")
								TEXT("have read a book by the name of \"The ")
								TEXT("Adventures of Tom Sawyer,\" but that ")
								TEXT("ain't no matter. That book was made by ")
								TEXT("Mr. Mark Twain, and he told the truth, ")
								TEXT("mainly. There was things which he ")
								TEXT("stretched, but mainly he told the truth. ")
								TEXT("That is nothing. I never seen anybody ")
								TEXT("but lied, one time or another, without ")
								TEXT("it was Aunt Polly, or the widow, or ")
								TEXT("maybe Mary. Aunt Polly -- Tom's Aunt ")
								TEXT("Polly, she is -- and Mary, and the Widow ")
								TEXT("Douglas, is all told about in that book ")
								TEXT("-- which is mostly a true book; with ")
								TEXT("some stretchers, as I said before.") };
	BOOL              fSuccess;
	HDC               hdc, hdcPrn;
	HMENU             hMenu;
	int               iSavePointSize;
	PAINTSTRUCT       ps;
	RECT              rect;


	switch (message)
	{
	case WM_CREATE:
				// Initialize the CHOOSEFONT structure

		GetObject(GetStockObject(SYSTEM_FONT), sizeof(lf), &lf);

		cf.lStructSize = sizeof(CHOOSEFONT);
		cf.hwndOwner = hwnd;
		cf.hDC = NULL;
		cf.lpLogFont = &lf;
		cf.iPointSize = 0;
		cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS |
					CF_EFFECTS;

		cf.rgbColors = 0;
		cf.lCustData = 0;
		cf.lpfnHook = NULL;
		cf.lpTemplateName = NULL;
		
		cf.hInstance = NULL;
		cf.lpszStyle = NULL;
		cf.nFontType = 0;
		cf.nSizeMin = 0;
		cf.nSizeMax = 0;
		return 0;

	case WM_COMMAND:
		hMenu = GetMenu(hwnd);

		switch (LOWORD(wParam))
		{
		case IDM_FILE_PRINT:
									// Get printer DC
			pd.lStructSize = sizeof(PRINTDLG);
			pd.hwndOwner = hwnd;
			pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION;

			if (!PrintDlg(&pd))
				return 0;

			if (NULL == (hdcPrn = pd.hDC))
			{
				MessageBox(hwnd, TEXT("Cannot obtain Printer DC"),
					szAppName, MB_ICONEXCLAMATION | MB_OK);
				return 0;
			}
				// Set margins of 1 inch
			rect.left = GetDeviceCaps(hdcPrn, LOGPIXELSX) -
				GetDeviceCaps(hdcPrn, PHYSICALOFFSETX);

			rect.top = GetDeviceCaps(hdcPrn, LOGPIXELSY) -
				GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);

			rect.right = GetDeviceCaps(hdcPrn, PHYSICALWIDTH) -
				GetDeviceCaps(hdcPrn, LOGPIXELSX) -
				GetDeviceCaps(hdcPrn, PHYSICALOFFSETX);

			rect.bottom = GetDeviceCaps(hdcPrn, PHYSICALHEIGHT) -
				GetDeviceCaps(hdcPrn, LOGPIXELSY) -
				GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);

			// Display text on printer

			SetCursor(LoadCursor(NULL, IDC_WAIT));
			ShowCursor(TRUE);

			fSuccess = FALSE;

			if ((StartDoc(hdcPrn, &di) > 0) && (StartPage(hdcPrn) > 0))
			{
					// Select font using adjusted lfHeight

				iSavePointSize = lf.lfHeight;
				lf.lfHeight = -(GetDeviceCaps(hdcPrn, LOGPIXELSY) *
										cf.iPointSize) / 720;

				SelectObject(hdcPrn, CreateFontIndirect(&lf));
				lf.lfHeight = iSavePointSize;

					// Set text color

				SetTextColor(hdcPrn, cf.rgbColors);

					// Display text

				Justify(hdcPrn, szText, &rect, iAlign);

				if (EndPage(hdcPrn) > 0)
				{
					fSuccess = TRUE;
					EndDoc(hdcPrn);
				}
			}
			ShowCursor(FALSE);
			SetCursor(LoadCursor(NULL, IDC_ARROW));

			DeleteDC(hdcPrn);

			if (!fSuccess)
				MessageBox(hwnd, TEXT("Could not print text"),
				szAppName, MB_ICONEXCLAMATION | MB_OK);
			return 0;

		case IDM_FONT:
			if (ChooseFont(&cf))
				InvalidateRect(hwnd, NULL, TRUE);
			return 0;

		case IDM_ALIGN_LEFT:
		case IDM_ALIGN_RIGHT:
		case IDM_ALIGN_CENTER:
		case IDM_ALIGN_JUSTIFIED:
			CheckMenuItem(hMenu, iAlign, MF_UNCHECKED);
			iAlign = LOWORD(wParam);

			CheckMenuItem(hMenu, iAlign, MF_CHECKED);
			InvalidateRect(hwnd, NULL, TRUE);
			return 0;
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		DrawRuler(hdc, &rect);

		rect.left += GetDeviceCaps(hdc, LOGPIXELSX) / 2;
		rect.top += GetDeviceCaps(hdc, LOGPIXELSY) / 2;
		rect.right -= GetDeviceCaps(hdc, LOGPIXELSX) / 4;

		SelectObject(hdc, CreateFontIndirect(&lf));
		SetTextColor(hdc, cf.rgbColors);

		Justify(hdc, szText, &rect, iAlign);

		DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
JUSTIFY1.RC
// Microsoft Visual C++ 生成的资源脚本。
//
#include "resource.h"

/
//
// Menu
//

JUSTIFY1 MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Print", IDM_FILE_PRINT
    END
    POPUP "&Font"
    BEGIN
        MENUITEM "&Font...", IDM_FONT
    END
    POPUP "&Align"
    BEGIN
        MENUITEM "&Left", IDM_ALIGN_LEFT, CHECKED
        MENUITEM "&Right", IDM_ALIGN_RIGHT
        MENUITEM "&Centered", IDM_ALIGN_CENTER
        MENUITEM "&Justified", IDM_ALIGN_JUSTIFIED
    END
EN
RESOURCE.H
// Microsoft Visual C++ generated include file.
// Used by Justify1.rc
#define IDM_FILE_PRINT                  40001
#define IDM_FONT                        40002
#define IDM_ALIGN_LEFT                  40003
#define IDM_ALIGN_RIGHT                 40004
#define IDM_ALIGN_CENTER                40005
#define IDM_ALIGN_JUSTIFIED             40006

        JUSTIFY1 在程序工作区的顶端和左侧各县市一个标尺(单位当然是逻辑英寸)。DrawRuler 函数绘制了这个标尺。一个矩形结构确定了文本可以分布的区域范围。

        格式化文本的大多数工作式在 Justify 函数中完成的。该函数从文本起始处开始搜索空格,并调用 GetTextExtentPoint32 函数来度量每一行。一旦该行的长度超过显示区域的宽度时,JUSTIFY1 就返回上一个空格, 然后使用当时形成的行。根据 iAlign 长数值的不同,该行可以为左对齐、右对齐、居中对齐或两端对齐。

        JUSTIFY1 并不完美。比如它没有任何支持“连字符”(hyphen)的逻辑。此外,该算法在一行中单词数量少于两个的情况下完全失效。即使我们解决了这个并不是特别难的问题。如果有一个单词比左右边界间的距离更长时,该程序仍然无法正常工作。当然,当你开始编写一个可以在同一行使用多种字体的程序(比如 Windows 自带的 Wordpad 程序)时,事情会变得更加复杂。

        当然,从没有人号称这些很容易办到。使用这些函数只是比你自己做从头开始完成所有的工作要容易些。

图 17-10  使用 JUSTIFY1 程序对某个段落处理的结果

17.5.3  打印预览

        某些文本并不仅仅需要显示在屏幕上,它们还需要被打印出来。往往在这种情况下,屏幕上文本大打印预览必须精确符合打印机输出的格式。仅显示相同的字体、字号和文本格式是不够的。但是自从有了 TrueType 字体,这一切都变得容易了。剩下的还需要做的事情就是确保在不同设备下,段落的每一行在同一个地方换行。这才是实现“所见即所得”功能里面比较困难的部分。

        JUSTIFY1 程序包括一个 Print (打印)选项,但它所做的仅仅是设置页面的上、左、右边距为 1 英寸。因此,格式和屏幕显示是完全独立的。这里你可以做一个有趣的尝试:修改 JUSTIFY1 中的几行代码,使得屏幕和打印机的逻辑都是基于 6 英寸格式的矩形。要达到此目的,改变 WM_PAINT 和 Print(打印)命令逻辑中的 rect.right 的定义即可。在 WM_PAINT 逻辑中,如下修改语句:

rect.right = rect.left + 6 * GetDeviceCaps(hdc, LOGPIXELSX);
在 Print(打印)命令逻辑中,如下修改语句:

rect.right = rect.left + 6 * GetDeviceCaps(hdcPrn, LOGPIXELSX);
如果你选择了一个 TrueType 字体,那么在屏幕上的断行应该和打印机输出时的断行是相同的。

        实际上却不是。即使两个设备使用相同的字体、相同的字号并将文本显示在相同的格式矩形里,不同的显示分辨率和四舍五入引入的误差仍会导致断行出现在不同的地方。显然,需要更完善的方法来实现打印预览功能。

        图 17-11 所示的 JUSTIFY2 程序试图解决这个问题。JUSTIFY2 的代码是基于 Microsoft 的 David Weise 编写的一个名为 TTJUST(TrueType Justify,TrueType 排版)的程序,而这个程序又是基于本书较早版本中的 JUSTIFY1 程序的。为了表现增长后的程序复杂度,我们将马克●吐温的摘录片段换为 Herman Melville 的《白鲸》(Movy-Dick)的第一段。

/*------------------------------------------------
	JUSTIFY2.C -- Justified Type Program #2
					(c) Charles Petzold, 1998
------------------------------------------------*/

#include <Windows.h>
#include "resource.h"

#define OUTWIDTH 6			// Width of formatted output in inches
#define LASTCHAR 127		// Last character code used in text

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szAppName[] = TEXT("Justify2");

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	HWND		 hwnd;
	MSG			 msg;
	WNDCLASS	 wndclass;

	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = szAppName;
	wndclass.lpszClassName = szAppName;


	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName, TEXT("Justified Type #2"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL);

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

void DrawRuler(HDC hdc, RECT * prc)
{
	static int iRuleSize[16] = { 360, 72, 144, 72, 216, 72, 144, 72,
								 288, 72, 144, 72, 216, 72, 144, 72 };
	int        i, j;
	POINT      ptClient;

	SaveDC(hdc);

	// Set Logical Twips mapping mode

	SetMapMode(hdc, MM_ANISOTROPIC);
	SetWindowExtEx(hdc, 1440, 1440, NULL);
	SetViewportExtEx(hdc, GetDeviceCaps(hdc, LOGPIXELSX),
		GetDeviceCaps(hdc, LOGPIXELSY), NULL);

	// Move the origin to a half inch from upper left

	SetWindowOrgEx(hdc, -720, -720, NULL);

	// Find the right margin (quarter inch from right)

	ptClient.x = prc->right;
	ptClient.y = prc->bottom;
	DPtoLP(hdc, &ptClient, 1);
	ptClient.x -= 360;

	// Draw the rulers

	MoveToEx(hdc, 0, -360, NULL);
	LineTo(hdc, ptClient.x, -360);
	MoveToEx(hdc, -360, 0, NULL);
	LineTo(hdc, -360, ptClient.y);

	for (i = 0, j = 0; i <= ptClient.x; i += 1440 / 16, j++)
	{
		MoveToEx(hdc, i, -360, NULL);
		LineTo(hdc, i, -360 - iRuleSize[j % 16]);
	}

	for (i = 0, j = 0; i <= ptClient.y; i += 1440 / 16, j++)
	{
		MoveToEx(hdc, -360, i, NULL);
		LineTo(hdc, -360 - iRuleSize[j % 16], i);
	}

	RestoreDC(hdc, -1);
}

/*-----------------------------------------------------------------------
	GetCharDesignWidths: Gets character widths for font as large as the
						 original design size
-----------------------------------------------------------------------*/
UINT GetCharDesingWidths(HDC hdc, UINT uFirst, UINT uLast, int * piWidths)
{
	HFONT				hFont, hFontDesign;
	LOGFONT				lf;
	OUTLINETEXTMETRIC	otm;

	hFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
	GetObject(hFont, sizeof(LOGFONT), &lf);

		// Get outline text metrics (we'll only be using a field that is
		//	independent of the DC the font is selected into)

	otm.otmSize = sizeof(OUTLINETEXTMETRIC);
	GetOutlineTextMetrics(hdc, sizeof(OUTLINETEXTMETRIC), &otm);

		// Create a new font based on the design size

	lf.lfHeight = -(int)otm.otmEMSquare;
	lf.lfWidth = 0;
	hFontDesign = CreateFontIndirect(&lf);

		// Select the font into the DC and get the character widths

	SaveDC(hdc);
	SetMapMode(hdc, MM_TEXT);
	SelectObject(hdc, hFontDesign);

	GetCharWidth(hdc, uFirst, uLast, piWidths);
	SelectObject(hdc, hFont);
	RestoreDC(hdc, -1);

		// Clean up

	DeleteObject(hFontDesign);

	return otm.otmEMSquare;
}

/*---------------------------------------------------------------------
	GetScaledWidths: Gets floating point character widths for selected
						font size
----------------------------------------------------------------------*/
void GetScaledWidths(HDC hdc, double * pdWidths)
{
	double	dScale;
	HFONT	hFont;
	int		aiDesignWidths[LASTCHAR + 1];
	int		i;
	LOGFONT lf;
	UINT	uEMSquare;

		// Call function above

	uEMSquare = GetCharDesingWidths(hdc, 0, LASTCHAR, aiDesignWidths);

		// Get LOGFONT for current font in device context

	hFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
	GetObject(hFont, sizeof(LOGFONT), &lf);

		// Scale the widths and store as floating point values

	dScale = (double)-lf.lfHeight / (double)uEMSquare;

	for(i = 0; i <= LASTCHAR; i++)
		pdWidths[i] = dScale * aiDesignWidths[i];
}

/*---------------------------------------------------------------------
	GetTextExtentFloat: Calculates text width in floating point
----------------------------------------------------------------------*/
double GetTextExtentFloat(double * pdWidths, PTSTR psText, int iCount)
{
	double	dWidth = 0;
	int		i;

	for (i = 0; i < iCount; i++)
		dWidth += pdWidths[psText[i]];

	return dWidth;
}

/*---------------------------------------------------------------------
	Justify: Based on design units for screen/printer compatibility
----------------------------------------------------------------------*/
void Justify(HDC hdc, PTSTR pText, RECT * prc, int iAlign)
{
	double	dWidth, adWidths[LASTCHAR + 1];
	int		xStart, yStart, cSpaceChars;
	PTSTR	pBegin, pEnd;
	SIZE	size;

			// Fill the adWidths array with floating point character widths

	GetScaledWidths(hdc, adWidths);

	yStart = prc->top;
	do							// for each text line
	{
		cSpaceChars = 0;		// initialize number of spaces in line

		while (*pText == ' ')	// skip over leading spaces
			pText++;

		pBegin = pText;			// set pointer to char at beginning of line

		do						// until the line is known
		{
			pEnd = pText;		// set pointer to char at end ot line

			// skip to next space

			while (*pText != '\0' && *pText++ != ' ');

			if (*pText == '\0')
				break;

			// after each space encountered, calculate extents

			cSpaceChars++;
			dWidth = GetTextExtentFloat(adWidths, pBegin,
												pText - pBegin - 1);
		} while (dWidth < (prc->right - prc->left));

		cSpaceChars--;				// discount last space at end of line

		while (*(pEnd - 1) == ' ')	// eliminate trailing spaces
		{
			pEnd--;
			cSpaceChars--;
		}

		// if end of text and no space characters, set pEnd to end

		if (*pText == '\0' || cSpaceChars <= 0)
			pEnd = pText;

		GetTextExtentPoint32(hdc, pBegin, pEnd - pBegin, &size);

		switch (iAlign)					// use alignment for xStart
		{
		case IDM_ALIGN_LEFT:
			xStart = prc->left;
			break;

		case IDM_ALIGN_RIGHT:
			xStart = prc->right - size.cx;
			break;

		case IDM_ALIGN_CENTER:
			xStart = (prc->right + prc->left - size.cx) / 2;
			break;

		case IDM_ALIGN_JUSTIFIED:
			if (*pText != '\0' && cSpaceChars > 0)
				SetTextJustification(hdc,
									prc->right - prc->left - size.cx,
									cSpaceChars);
			xStart = prc->left;
			break;
		}

		// display the text

		TextOut(hdc, xStart, yStart, pBegin, pEnd - pBegin);

		// prepare for next line

		SetTextJustification(hdc, 0, 0);
		yStart += size.cy;
		pText = pEnd;

	} while (*pText && yStart < prc->bottom - size.cy);

}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static CHOOSEFONT cf;
	static DOCINFO    di = { sizeof(DOCINFO), TEXT("Justify2: Printing") };
	static int        iAlign = IDM_ALIGN_LEFT;
	static LOGFONT    lf;
	static PRINTDLG   pd;
	static TCHAR      szText[] = {
							TEXT("Call me Ishmael. Some years ago -- never ")
							TEXT("mind how long precisely -- having little ")
							TEXT("or no money in my purse, and nothing ")
							TEXT("particular to interest me on shore, I ")
							TEXT("thought I would sail about a little and ")
							TEXT("see the watery part of the world. It is ")
							TEXT("a way I have of driving off the spleen, ")
							TEXT("and regulating the circulation. Whenever ")
							TEXT("I find myself growing grim about the ")
							TEXT("mouth; whenever it is a damp, drizzly ")
							TEXT("November in my soul; whenever I find ")
							TEXT("myself involuntarily pausing before ")
							TEXT("coffin warehouses, and bringing up the ")
							TEXT("rear of every funeral I meet; and ")
							TEXT("especially whenever my hypos get such an ")
							TEXT("upper hand of me, that it requires a ")
							TEXT("strong moral principle to prevent me ")
							TEXT("from deliberately stepping into the ")
							TEXT("street, and methodically knocking ")
							TEXT("people's hats off -- then, I account it ")
							TEXT("high time to get to sea as soon as I ")
							TEXT("can. This is my substitute for pistol ")
							TEXT("and ball. With a philosophical flourish ")
							TEXT("Cato throws himself upon his sword; I ")
							TEXT("quietly take to the ship. There is ")
							TEXT("nothing surprising in this. If they but ")
							TEXT("knew it, almost all men in their degree, ")
							TEXT("some time or other, cherish very nearly ")
							TEXT("the same feelings towards the ocean with ")
							TEXT("me.") };
	BOOL              fSuccess;
	HDC               hdc, hdcPrn;
	HMENU             hMenu;
	int               iSavePointSize;
	PAINTSTRUCT       ps;
	RECT              rect;

	switch (message)
	{
	case WM_CREATE:
		// Initialize the CHOOSEFONT structure

		hdc = GetDC(hwnd);
		lf.lfHeight = -GetDeviceCaps(hdc, LOGPIXELSX) / 6;
		lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
		lstrcpy(lf.lfFaceName, TEXT("Times New Roman"));
		ReleaseDC(hwnd, hdc);

		cf.lStructSize = sizeof(CHOOSEFONT);
		cf.hwndOwner = hwnd;
		cf.hDC = NULL;
		cf.lpLogFont = &lf;
		cf.iPointSize = 120;
	
				// Set flags for TrueType only!

		cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS |
				   CF_TTONLY | CF_EFFECTS;
		cf.rgbColors = 0;
		cf.lCustData = 0;
		cf.lpfnHook = NULL;
		cf.lpTemplateName = NULL;
		cf.hInstance = NULL;
		cf.lpszStyle = NULL;
		cf.nFontType = 0;
		cf.nSizeMin = 0;
		cf.nSizeMax = 0;
		return 0;

	case WM_COMMAND:
		hMenu = GetMenu(hwnd);

		switch (LOWORD(wParam))
		{
		case IDM_FILE_PRINT:
			// Get printer DC
			pd.lStructSize = sizeof(PRINTDLG);
			pd.hwndOwner = hwnd;
			pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION;

			if (!PrintDlg(&pd))
				return 0;

			if (NULL == (hdcPrn = pd.hDC))
			{
				MessageBox(hwnd, TEXT("Cannot obtain Printer DC"),
					szAppName, MB_ICONEXCLAMATION | MB_OK);
				return 0;
			}
				
				// Set margins of OUTWIDTH inchs wide

			rect.left = (GetDeviceCaps(hdcPrn, LOGPIXELSX) -
						GetDeviceCaps(hdcPrn, PHYSICALOFFSETX) * OUTWIDTH) / 2
					  - GetDeviceCaps(hdcPrn, PHYSICALOFFSETX);

			rect.right = rect.left +
						GetDeviceCaps(hdcPrn, LOGPIXELSX) * OUTWIDTH;
			
				// Set margins of 1 inch at top and bottom

			rect.top = GetDeviceCaps(hdcPrn, LOGPIXELSY) -
				GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);


			rect.bottom = GetDeviceCaps(hdcPrn, PHYSICALHEIGHT) -
				GetDeviceCaps(hdcPrn, LOGPIXELSY) -
				GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);

			// Display text on printer

			SetCursor(LoadCursor(NULL, IDC_WAIT));
			ShowCursor(TRUE);

			fSuccess = FALSE;

			if ((StartDoc(hdcPrn, &di) > 0) && (StartPage(hdcPrn) > 0))
			{
				// Select font using adjusted lfHeight

				iSavePointSize = lf.lfHeight;
				lf.lfHeight = -(GetDeviceCaps(hdcPrn, LOGPIXELSY) *
					cf.iPointSize) / 720;

				SelectObject(hdcPrn, CreateFontIndirect(&lf));
				lf.lfHeight = iSavePointSize;

				// Set text color

				SetTextColor(hdcPrn, cf.rgbColors);

				// Display text

				Justify(hdcPrn, szText, &rect, iAlign);

				if (EndPage(hdcPrn) > 0)
				{
					fSuccess = TRUE;
					EndDoc(hdcPrn);
				}
			}
			ShowCursor(FALSE);
			SetCursor(LoadCursor(NULL, IDC_ARROW));

			DeleteDC(hdcPrn);

			if (!fSuccess)
				MessageBox(hwnd, TEXT("Could not print text"),
				szAppName, MB_ICONEXCLAMATION | MB_OK);
			return 0;

		case IDM_FONT:
			if (ChooseFont(&cf))
				InvalidateRect(hwnd, NULL, TRUE);
			return 0;

		case IDM_ALIGN_LEFT:
		case IDM_ALIGN_RIGHT:
		case IDM_ALIGN_CENTER:
		case IDM_ALIGN_JUSTIFIED:
			CheckMenuItem(hMenu, iAlign, MF_UNCHECKED);
			iAlign = LOWORD(wParam);
			CheckMenuItem(hMenu, iAlign, MF_CHECKED);
			InvalidateRect(hwnd, NULL, TRUE);
			return 0;
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		DrawRuler(hdc, &rect);

		rect.left += GetDeviceCaps(hdc, LOGPIXELSX) / 2;
		rect.top += GetDeviceCaps(hdc, LOGPIXELSY) / 2;
		rect.right = rect.left + OUTWIDTH * GetDeviceCaps(hdc, LOGPIXELSX);

		SelectObject(hdc, CreateFontIndirect(&lf));
		SetTextColor(hdc, cf.rgbColors);

		Justify(hdc, szText, &rect, iAlign);

		DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
		EndPaint(hwnd, &ps);
		return 0;
	
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
JUSTIFY2.RC
// Microsoft Visual C++ 生成的资源脚本。
//
#include "resource.h"

/
//
// Menu
//

JUSTIFY2 MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Print", IDM_FILE_PRINT
    END
    POPUP "&Font"
    BEGIN
        MENUITEM "&Font...", IDM_FONT
    END
    POPUP "&Align"
    BEGIN
        MENUITEM "&Left", IDM_ALIGN_LEFT, CHECKED
        MENUITEM "&Right", IDM_ALIGN_RIGHT
        MENUITEM "&Centered", IDM_ALIGN_CENTER
        MENUITEM "&Justified", IDM_ALIGN_JUSTIFIED
    END
EN
RESOURCE.H
// Microsoft Visual C++ generated include file.
// Used by Justify2.rc
#define IDM_FILE_PRINT                  40001
#define IDM_FONT                        40002
#define IDM_ALIGN_LEFT                  40003
#define IDM_ALIGN_RIGHT                 40004
#define IDM_ALIGN_CENTER                40005
#define IDM_ALIGN_JUSTIFIED             40006

        JUSTIFY2 仅对 TrueType 字体有效。该程序在 GetCharDesignWidths 函数中,曾调用 GetOutlineTextMetrics 函数来得到一些看似不起眼的信息,即OUTLINETEXTMETRIC 结构中的 otmEMSquare 字段

        在设计一个 TrueType 字体时,使用了“EM 正方形”(EM Square)网格。一个特定的 TrueType 字体的所有字符虽然一般来说宽度都不相同,但都在同样的网格中被设计出来。OUTLINETEXTMETRIC 结构的 otmEMSquare 字段给出了该字体的网格尺寸。对于大多数的 TrueType 字体,你会发现 otmEMSquare 字段都等于 2048,这意味着这些字体是在 2048 * 2048 的网格中设计的

        这里的关键是:你可以使用某个你需要的特定的 TrueType 字体的字样名字来建立一个 LOGFONT 结构,但要将 lfHeight 字段赋值为 otmEMSquare 值的相反数。在创建好字体并将其选择到设备环境中后,你可以调用 GetCharWidth 函数。这个函数会告诉你该字体中的每个字符的逻辑单位宽度。通常这些字符宽度并不准确,因为它们的字体大小被缩放过了。但是,如果该字体的 lfHeight 使用了 otmEMSquare,得到的宽度总是准确的整数,与设备环境无关。

        GetCharDesignWidths 函数通过这个方法获得字符的原始设计宽度,并存储在一个整数数组里,JUSTIFY2 程序知道它的文本只使用 ASCII 字符,因此这个数组不必很大。GetScaledWidths 函数根据字体在设备逻辑坐标中的实际点值大小,将这些整数宽度转换为浮点宽度。GetTextExtentFloat 函数使用这些浮点宽度计算整个字符串的宽度。这就是新的 Justify 函数使用的计算文本行宽度的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值