第17章 文本和字体_17.5 设置段落格式

17.5 设置段落格式

17.5.1 设置简单的文本格式

(1)对齐方式及起始坐标:设字符串的长度为size.cx

对齐方式

文本输出的起始位置(设输出框左右边界分别为xLeft、XRight)

左对齐

xStart =xLeft; 

右对齐

xStart = xRight – size.cx

中间对齐

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

两端对齐

xStart = xLeft,但要调用SetTextJustification进行调整

(2)两个重要函数

   ①GetTextExtentPoint32(hdc,pString,iCount,&size);——计算字符串长度

   ②SetTextJustification(hdc,xRight- xLeft –size.cx,3);

    //参数2表示要分配到字符串内空格字符上的空间大小;即输出框中剩余的空间大小。

    //参数3表示该范围内(xRight -xLeft)的字符串中空格的数量。

 (3)每次调用SetTextJustification后,如果要求的空间无法在空格字符间平均分配,将产生一个误差项。这个误差会影响下一次的GetTextExtentPoint32。所以当开始新的一行时,要清除这个误差项,即SetTextJusitifaction(hdc,0,0);

17.5.2 段落的处理——图解段落调整(代码部分见示例中的Justify函数)

【Justify1程序】
效果图

/*------------------------------------------------------------
JUSTIFY1.C -- Justified Type Program #1
(c)Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawRuler(HDC hdc, RECT *pRc);
void Justify(HDC hdc, PTSTR pText, RECT *pRc, int iAlign);
TCHAR szAppName[] = TEXT("Justify1");
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
    HWND         hwnd;
    MSG          msg;
    WNDCLASSEX     wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.cbSize = sizeof(WNDCLASSEX);
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, szAppName);
    wndclass.hIconSm = LoadIcon(hInstance, szAppName);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = szAppName;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClassEx(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
                        TEXT("Justified Type #1"), // window caption
                        WS_OVERLAPPEDWINDOW,        // window style
                        CW_USEDEFAULT,              // initial x position
                        CW_USEDEFAULT,              // initial y position
                        CW_USEDEFAULT,              // initial x size
                        CW_USEDEFAULT,              // initial y size
                        NULL,                       // parent window handle
                        NULL,                       // window menu handle
                        hInstance,                  // program instance handle
                        NULL);                     // creation parameters

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

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    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.") };
    static LOGFONT     lf;
    static CHOOSEFONT  cf;
    static int iAlign = IDM_ALIGN_LEFT;
    HDC         hdc, hdcPrn;
    static PRINTDLG    pd;
    static DOCINFO     di = { sizeof(DOCINFO), TEXT("Justify1:Printing") };
    PAINTSTRUCT ps;
    RECT        rect;
    HMENU       hMenu;
    BOOL        fSuccess;
    int         iSavePointSize;

    switch (message)
    {
    case WM_CREATE:
        //初始化CHOOSEFONT结构体
        memset(&cf, 0, sizeof(CHOOSEFONT));
        cf.lStructSize = sizeof(CHOOSEFONT);
        cf.hwndOwner = hwnd;
        cf.lpLogFont = &lf;
        cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS;
        return 0;
    case WM_COMMAND:
        hMenu = GetMenu(hwnd);
        switch (LOWORD(wParam))
        {
        case IDM_FILE_PRINT:
            //获取打印机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;
            }
            //设置1英寸的页边距
            rect.left = GetDeviceCaps(hdcPrn, LOGPIXELSX) -
                GetDeviceCaps(hdcPrn, PHYSICALOFFSETX);
            rect.top = GetDeviceCaps(hdcPrn, LOGPIXELSY) -
                GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);
            rect.right = GetDeviceCaps(hdcPrn, PHYSICALWIDTH) -
                GetDeviceCaps(hdcPrn, LOGPIXELSX);
            rect.bottom = GetDeviceCaps(hdcPrn, PHYSICALHEIGHT) -
                GetDeviceCaps(hdcPrn, LOGPIXELSY);
            //在打印内容
            SetCursor(LoadCursor(NULL, IDC_WAIT));
            ShowCursor(TRUE);
            fSuccess = FALSE;
            iSavePointSize = 5;
            if (0 < iSavePointSize < 10);
            if ((StartDoc(hdcPrn, &di) > 0) && (StartPage(hdcPrn) > 0))
            {
                //根据所选的字体来调整高度
                iSavePointSize = lf.lfHeight;
                lf.lfHeight = -(GetDeviceCaps(hdcPrn, LOGPIXELSY)*cf.iPointSize) / 720;  //因为iPointSize以1/10磅为单位
                SelectObject(hdcPrn, CreateFontIndirect(&lf));
                lf.lfHeight = iSavePointSize;
                //设置颜色
                SetTextColor(hdcPrn, cf.rgbColors);
                //显示文本
                Justify(hdcPrn, szText, &rect, iAlign);
                if (EndPage(hdcPrn) > 0)
                {
                    fSuccess = TRUE;
                    EndDoc(hdcPrn);
                }
            }
            ShowCursor(FALSE);
            SetCursor(LoadCursor(NULL, IDC_ARROW));
            DeleteObject(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;
        }
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        GetClientRect(hwnd, &rect);
        DrawRuler(hdc, &rect);
        //计算文本输出的区域(跳过两个标尺的客户区范围 )
        rect.left += GetDeviceCaps(hdc, LOGPIXELSX) / 2; //0.5英寸
        rect.top += GetDeviceCaps(hdc, LOGPIXELSY) / 2; //0.5英寸
        rect.right -= GetDeviceCaps(hdc, LOGPIXELSX) / 4; //0.25英寸
        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);
}
//画标尺
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);
    //设置为逻辑Twips映射模式——这是一种自定义的模式 
    SetMapMode(hdc, MM_ANISOTROPIC);
    SetWindowExtEx(hdc, 1440, 1440, NULL); //逻辑单位为1/1440英寸
    SetViewportExtEx(hdc, GetDeviceCaps(hdc, LOGPIXELSX),
                     GetDeviceCaps(hdc, LOGPIXELSY), NULL);
    //将窗口原点移动左上角半英寸的地方,也就是逻辑坐标系的原点设在(0.5,0.5)英寸的地方 
    SetWindowOrgEx(hdc, -720, -720, NULL);
    //计算右边距(距离最右边1/4英寸)
    ptClient.x = pRc->right;   //设备坐标
    ptClient.y = pRc->bottom;  //设备坐标
    DPtoLP(hdc, &ptClient, 1); //转为逻辑坐标
    ptClient.x -= 360;  // 右边缩进1/4英寸
    //画标尺
    MoveToEx(hdc, 0, -360, NULL); //
    LineTo(hdc, ptClient.x, -360);  //画水平标尺横线
    MoveToEx(hdc, -360, 0, NULL);
    LineTo(hdc, -360, ptClient.y);
    //水平标尺各刻度线,1/16英寸的间隔画刻度线(1英寸是1440个逻辑单位)
    for (i = 0, j = 0; i <= ptClient.x; i += 1440 / 16, j++)
    {
        MoveToEx(hdc, i, -360, NULL);
        LineTo(hdc, i, -360 - iRuleSize[j % 16]);
    }
    //垂直标尺各刻度线,1/16英寸的间隔画刻度线(1英寸是1440个逻辑单位)
    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                              //处理每一行文本
    {
        cSpaceChars = 0;            //每行空格的个数,先初始化为0
        while (*pText == ' ')       //跳过每行开始处的空格
            pText++;
        pBegin = pText;             //pBegin指向该行的第一个字母
        do
        {
            pEnd = pText;          //将pEnd指针定位在行尾(注意,最终指向行尾)
            //但每次循环,先将上次的pText的位置保存在pEnd中。
            //跳到下一个空格的地方
            while (*pText != '' && *pText++ != ' '); //找到下一个空格后,pText++,指向
            //该空格后的下一个位置
            if (*pText == '')
                break;
            //当每次遇到空格时,都要统计一下该空格前面的字符串的长度
            cSpaceChars++;
            GetTextExtentPoint32(hdc, pBegin, pText - pBegin - 1, &size);
        } while (size.cx < (pRc->right - pRc->left));
        cSpaceChars--;            //文本超出范围后,减去每行最后的空格
        while (*(pEnd - 1) == ' ')  //删除行尾那些连续的空格
        {
            pEnd--;
            cSpaceChars--;
        }
        //如果到了文本的最后或没有空格,将pEnd指针设到最末的位置
        if (*pText == '' || cSpaceChars <= 0)
            pEnd = pText;
        GetTextExtentPoint32(hdc, pBegin, pEnd - pBegin, &size);
        switch (iAlign)
        {
        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 != '' && cSpaceChars > 0)
                SetTextJustification(hdc, pRc->right - pRc->left - size.cx, cSpaceChars);
            xStart = pRc->left;
        }
        //显示文本
        TextOut(hdc, xStart, yStart, pBegin, pEnd - pBegin);
        //准备下一行的输出,并消除误差
        SetTextJustification(hdc, 0, 0);
        yStart += size.cy;
        pText = pEnd;
    } while (*pText && yStart < pRc->bottom - size.cy);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 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 ID_ALIGN_JUSTIFIED              40006
#define IDM_ALIGN_JUSTIFIED             40007
// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40008
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//Justify1.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""
"
""
END
3 TEXTINCLUDE
BEGIN
"
"
""
END
#endif    // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
JUSTIFY1 MENU
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 "&Center", IDM_ALIGN_CENTER
MENUITEM "&Justified", IDM_ALIGN_JUSTIFIED
END
END
#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

(1)己知的几个问题

   ①不支持“连字符”的逻辑
   ②当一行中单词数量少于两个时,算法完全失效。
   ③如果有个单词比左右边界间的距离更长时,该程序无法工作。
   ④屏幕上的显示与打印机上的显示并非完全一致,即不是“所见即所得”

17.5.3 打印预览

(1)Justify2仅对TrueType字体有效。克服了上述非“所见即所得”的问题。
(2)GetOutlineTextMetrics可以获得TrueType字体原始的设计尺寸信息。
(3)本例中创建的字体,其lfHeight使用otmEMSquare,得到的宽度与设备无关。

【Justify2程序】——在Justify1的基础上修改。

/*------------------------------------------------------------
JUSTIFY2.C -- Justified Type Program #2
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#define OUTWIDTH 6     //6英寸
#define LASTCHAR 127   //在文本中使用的最后一个字符编码
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawRuler(HDC hdc, RECT *pRc);
void Justify(HDC hdc, PTSTR pText, RECT *pRc, int iAlign);
UINT GetCharDesignWidths(HDC hdc, UINT uFirst, UINT uLast, int* piWidths);
void GetScaleWidths(HDC hdc, double* pdWidths);
double GetTextExtentFloat(double* pdWidths, PTSTR psText, int iCount);
TCHAR szAppName[] = TEXT("Justify2");
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
    HWND         hwnd;
    MSG          msg;
    WNDCLASSEX     wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.cbSize = sizeof(WNDCLASSEX);
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, szAppName);
    wndclass.hIconSm = LoadIcon(hInstance, szAppName);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = szAppName;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClassEx(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
                        TEXT("Justified Type #2"), // window caption
                        WS_OVERLAPPEDWINDOW,        // window style
                        CW_USEDEFAULT,              // initial x position
                        CW_USEDEFAULT,              // initial y position
                        CW_USEDEFAULT,              // initial x size
                        CW_USEDEFAULT,              // initial y size
                        NULL,                       // parent window handle
                        NULL,                       // window menu handle
                        hInstance,                  // program instance handle
                        NULL);                     // creation parameters

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

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    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.") };
    static LOGFONT     lf;
    static CHOOSEFONT  cf;
    static int iAlign = IDM_ALIGN_LEFT;
    HDC         hdc, hdcPrn;
    static PRINTDLG    pd;
    static DOCINFO     di = { sizeof(DOCINFO), TEXT("Justify1:Printing") };
    PAINTSTRUCT ps;
    RECT        rect;
    HMENU       hMenu;
    BOOL        fSuccess;
    int         iSavePointSize;

    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);
        lf.lfHeight = -GetDeviceCaps(hdc, LOGPIXELSY) / 6;  //1/6英寸的高度
        lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;             //只使用TrueType字体
        lstrcpy(lf.lfFaceName, TEXT("Times New Roman"));
        ReleaseDC(hwnd, hdc);
        //初始化CHOOSEFONT结构体
        memset(&cf, 0, sizeof(CHOOSEFONT));
        cf.lStructSize = sizeof(CHOOSEFONT);
        cf.hwndOwner = hwnd;
        cf.lpLogFont = &lf;
        cf.iPointSize = 120;  //初始值为12磅
        cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS;
        return 0;
    case WM_COMMAND:
        hMenu = GetMenu(hwnd);
        switch (LOWORD(wParam))
        {
        case IDM_FILE_PRINT:
            //获取打印机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;
            }
            //设置1英寸的页边距
            rect.left = (GetDeviceCaps(hdcPrn, PHYSICALWIDTH) -
                         GetDeviceCaps(hdcPrn, LOGPIXELSX)*OUTWIDTH) / 2 -
                         GetDeviceCaps(hdcPrn, PHYSICALOFFSETX);
            rect.top = GetDeviceCaps(hdcPrn, LOGPIXELSY) -
                GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);
            rect.right = rect.left + GetDeviceCaps(hdcPrn, LOGPIXELSX)*OUTWIDTH;
            rect.bottom = GetDeviceCaps(hdcPrn, PHYSICALHEIGHT) -
                GetDeviceCaps(hdcPrn, LOGPIXELSY) -
                GetDeviceCaps(hdcPrn, PHYSICALOFFSETY);
            //在打印内容
            SetCursor(LoadCursor(NULL, IDC_WAIT));
            ShowCursor(TRUE);
            fSuccess = FALSE;
            iSavePointSize = 5;
            if (0 < iSavePointSize < 10);
            if ((StartDoc(hdcPrn, &di)>0) && (StartPage(hdcPrn)>0))
            {
                //根据所选的字体来调整高度
                iSavePointSize = lf.lfHeight;
                lf.lfHeight = -(GetDeviceCaps(hdcPrn, LOGPIXELSY)*cf.iPointSize) / 720;  //因为iPointSize以1/10磅为单位
                SelectObject(hdcPrn, CreateFontIndirect(&lf));
                lf.lfHeight = iSavePointSize;
                //设置颜色
                SetTextColor(hdcPrn, cf.rgbColors);
                //显示文本
                Justify(hdcPrn, szText, &rect, iAlign);
                if (EndPage(hdcPrn)>0)
                {
                    fSuccess = TRUE;
                    EndDoc(hdcPrn);
                }
            }
            ShowCursor(FALSE);
            SetCursor(LoadCursor(NULL, IDC_ARROW));
            DeleteObject(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;
        }
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        GetClientRect(hwnd, &rect);
        DrawRuler(hdc, &rect);
        //计算文本输出的区域(跳过两个标尺的客户区范围 )
        rect.left += GetDeviceCaps(hdc, LOGPIXELSX) / 2; //0.5英寸
        rect.top += GetDeviceCaps(hdc, LOGPIXELSY) / 2; //0.5英寸
        rect.right = rect.left + OUTWIDTH*GetDeviceCaps(hdc, LOGPIXELSX); //3英寸
        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);
}
//画标尺
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);
    //设置为逻辑Twips映射模式——这是一种自定义的模式 
    SetMapMode(hdc, MM_ANISOTROPIC);
    SetWindowExtEx(hdc, 1440, 1440, NULL); //逻辑单位为1/1440英寸
    SetViewportExtEx(hdc, GetDeviceCaps(hdc, LOGPIXELSX),
                     GetDeviceCaps(hdc, LOGPIXELSY), NULL);
    //将窗口原点移动左上角半英寸的地方,也就是逻辑坐标系的原点设在(0.5,0.5)英寸的地方 
    SetWindowOrgEx(hdc, -720, -720, NULL);
    //计算右边距(距离最右边1/4英寸)
    ptClient.x = pRc->right;   //设备坐标
    ptClient.y = pRc->bottom;  //设备坐标
    DPtoLP(hdc, &ptClient, 1); //转为逻辑坐标
    ptClient.x -= 360;  // 右边缩进1/4英寸
    //画标尺
    MoveToEx(hdc, 0, -360, NULL); //
    LineTo(hdc, OUTWIDTH * 1440, -360);  //画水平标尺横线
    MoveToEx(hdc, -360, 0, NULL);
    LineTo(hdc, -360, ptClient.y);
    //水平标尺各刻度线,1/16英寸的间隔画刻度线(1英寸是1440个逻辑单位)
    for (i = 0, j = 0; i <= ptClient.x && i <= OUTWIDTH * 1440; i += 1440 / 16, j++)
    {
        MoveToEx(hdc, i, -360, NULL);
        LineTo(hdc, i, -360 - iRuleSize[j % 16]);
    }
    //垂直标尺各刻度线,1/16英寸的间隔画刻度线(1英寸是1440个逻辑单位)
    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);
}
/*-------------------------------------------------------------------------------------------------
Justify:基于屏幕或打印机设计
_________________________________________________________________________________________________*/
void Justify(HDC hdc, PTSTR pText, RECT *pRc, int iAlign)
{
    int xStart, yStart, cSpaceChars;
    PTSTR pBegin, pEnd;
    SIZE  size;
    double dWidth, adWidths[LASTCHAR + 1];
    //利用字符的浮点宽度填充adWidths数组
    GetScaleWidths(hdc, adWidths);
    yStart = pRc->top;
    do                              //处理每一行文本
    {
        cSpaceChars = 0;            //每行空格的个数,先初始化为0
        while (*pText == ' ')       //跳过每行开始处的空格
            pText++;
        pBegin = pText;             //pBegin指向该行的第一个字母
        do
        {
            pEnd = pText;          //将pEnd指针定位在行尾(注意,最终指向行尾)
            //但每次循环,先将上次的pText的位置保存在pEnd中。
            //跳到下一个空格的地方
            while (*pText != '' && *pText++ != ' '); //找到下一个空格后,pText++,指向
            //该空格后的下一个位置
            if (*pText == '')
                break;
            //当每次遇到空格时,都要统计一下该空格前面的字符串的长度
            cSpaceChars++;
            dWidth = GetTextExtentFloat(adWidths, pBegin, pText - pBegin - 1);
        } while (dWidth<(double)(pRc->right - pRc->left));
        cSpaceChars--;            //文本超出范围后,减去每行最后的空格
        while (*(pEnd - 1) == ' ')  //删除行尾那些连续的空格
        {
            pEnd--;
            cSpaceChars--;
        }
        //如果到了文本的最后或没有空格,将pEnd指针设到最末的位置
        if (*pText == '' || cSpaceChars <= 0)
            pEnd = pText;
        //获得整型的长度,因为SetTextJustification要使用这样的长度。
        GetTextExtentPoint32(hdc, pBegin, pEnd - pBegin, &size);
        switch (iAlign)
        {
        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 != '' && cSpaceChars > 0)
                SetTextJustification(hdc, pRc->right - pRc->left - size.cx, cSpaceChars);
            xStart = pRc->left;
        }
        //显示文本
        TextOut(hdc, xStart, yStart, pBegin, pEnd - pBegin);
        //准备下一行的输出,并消除误差
        SetTextJustification(hdc, 0, 0);
        yStart += size.cy;
        pText = pEnd;
    } while (*pText && yStart < pRc->bottom - size.cy);
}
/*-------------------------------------------------------------------------------------------------
GetCharDesignWidths:获得指定字体的字符,其原始的设计尺寸
_________________________________________________________________________________________________*/
UINT GetCharDesignWidths(HDC hdc, UINT uFirst, UINT uLast, int* piWidths)
{
    HFONT hFont, hFontDesign;
    LOGFONT lf;
    OUTLINETEXTMETRIC otm;  //
    hFont = GetCurrentObject(hdc, OBJ_FONT); //获得当前DC中的字体
    GetObject(hFont, sizeof(LOGFONT), &lf);
    //获得Outline文本的尺寸信息
    //设计TrueType字体时,使用了“EM正方形”网格,字符宽度不同,但网格大小是一样的。
    otm.otmSize = sizeof(OUTLINETEXTMETRIC);
    GetOutlineTextMetrics(hdc, sizeof(OUTLINETEXTMETRIC), &otm);
    //创建基于设计尺寸的字体
    lf.lfHeight = -(int)otm.otmEMSquare;
    lf.lfWidth = 0;
    hFontDesign = CreateFontIndirect(&lf);
    //选入设备环境,并获得字体的宽度
    SaveDC(hdc);
    SetMapMode(hdc, MM_TEXT);
    SelectObject(hdc, hFontDesign);
    GetCharWidth(hdc, uFirst, uLast, piWidths);  //GetCharWidth是系统API
    //本例中获得所有ASCII码字符的设计宽度。
    SelectObject(hdc, hFont);
    RestoreDC(hdc, -1);
    //清除
    DeleteObject(hFontDesign);
    return otm.otmEMSquare;
}
/*-------------------------------------------------------------------------------------------------
GetScalWidths:获得指定字体经缩放后的尺寸
_________________________________________________________________________________________________*/
void GetScaleWidths(HDC hdc, double* pdWidths)
{
    double dScale;
    HFONT hFont;
    int  aiDesignWidths[LASTCHAR + 1]; //所有ASCII码,共128个
    int i;
    LOGFONT lf;
    UINT uEMSquare;
    //调用以上的函数
    uEMSquare = GetCharDesignWidths(hdc, 0, LASTCHAR, aiDesignWidths);
    //获得当前DC中的字体
    hFont = GetCurrentObject(hdc, OBJ_FONT);
    GetObject(hFont, sizeof(LOGFONT), &lf);
    //缩放各宽度,并存储成浮点数数据
    dScale = (double)-lf.lfHeight / (double)uEMSquare;
    for (i = 0; i <= LASTCHAR; i++)
    {
        pdWidths[i] = dScale*aiDesignWidths[i];
    }
}
/*-------------------------------------------------------------------------------------------------
GetTextExtentFloat:计算文本字符串的浮点宽度。
功能与GetTextExtentPoint32类似
_________________________________________________________________________________________________*/
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;
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 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 ID_ALIGN_JUSTIFIED              40006
#define IDM_ALIGN_JUSTIFIED             40007
// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40008
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//Justify2.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""
"
""
END
3 TEXTINCLUDE
BEGIN
"
"
""
END
#endif    // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
JUSTIFY2 MENU
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 "&Center", IDM_ALIGN_CENTER
MENUITEM "&Justified", IDM_ALIGN_JUSTIFIED
END
END
#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
原文地址:https://www.cnblogs.com/5iedu/p/4701532.html