根据纸张大小和允许的最大文本长度计算理想的字体大小

时间:2014-06-28 15:32:16

标签: c++ winapi fonts gdi+ gdi

我有打印代码,可以在纸上绘制网格。

网格有4列,它们具有相等的水平长度。单元格的高度是纸张尺寸的十分之一。总行数未知,但我知道至少会有一行。

每个细胞具有相同的物理尺寸 - >宽度是纸张宽度的四分之一,高度是纸张高度的十分之一。 可容纳到单元格的最大字符数为50。

我面临的问题是选择合适的字体大小,以便最大长度的文本可以适合单元格。

浏览 MSDN 文档和WinAPI示例,我看到他们使用GetTextExtPoint32用于类似目的,但只有如果字体已存在且已选中进入设备上下文,这不是这里的情况。

我唯一想到的就是创建"虚拟字体",看看示例文本是否适合单元格,然后在测试失败时调整它的大小。我还发现this blog建议采用有趣的方法解决这个问题,但由于缺乏经验,我无法决定这是否是正确的方法。

您能为我的问题推荐一个正确的解决方案吗?

于2014年6月30日编辑:

下面是绘制网格并以浅灰色绘制左上角单元格的示例函数,因为该单元格将包含示例文本。这样我们就可以直观地验证绘图代码的成功:

// hWnd is the window that owns the property sheet.
HRESULT GDI_PRINT(HWND hWnd)
{
    HRESULT hResult;
    PRINTDLGEX pdx = {0};
    LPPRINTPAGERANGE pPageRanges = NULL;

    // Allocate an array of PRINTPAGERANGE structures.
    pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));

    if (!pPageRanges)
        return E_OUTOFMEMORY;

    //  Initialize the PRINTDLGEX structure.
    pdx.lStructSize = sizeof(PRINTDLGEX);
    pdx.hwndOwner = hWnd;
    pdx.hDevMode = NULL;
    pdx.hDevNames = NULL;
    pdx.hDC = NULL;
    pdx.Flags = PD_RETURNDC;
    pdx.Flags2 = 0;
    pdx.ExclusionFlags = 0;
    pdx.nPageRanges = 0;
    pdx.nMaxPageRanges = 10;
    pdx.lpPageRanges = pPageRanges;
    pdx.nMinPage = 1;
    pdx.nMaxPage = 1000;
    pdx.nCopies = 1;
    pdx.hInstance = 0;
    pdx.lpPrintTemplateName = NULL;
    pdx.lpCallback = NULL;
    pdx.nPropertyPages = 0;
    pdx.lphPropertyPages = NULL;
    pdx.nStartPage = START_PAGE_GENERAL;
    pdx.dwResultAction = 0;

    //  Invoke the Print property sheet.

    hResult = PrintDlgEx(&pdx);

    if ( ( hResult == S_OK ) && ( pdx.dwResultAction == PD_RESULT_PRINT ) )
    {

        // User clicked the Print button, 
        // so use the DC and other information returned in the 
        // PRINTDLGEX structure to print the document.

        //======= Various initializations ==========//

        DOCINFO diDocInfo = {0};
        diDocInfo.cbSize = sizeof( DOCINFO ); 
        diDocInfo.lpszDocName = L"Testing printing...";

        int pageWidth = GetDeviceCaps( pdx.hDC, HORZRES ), 
            pageHeight = GetDeviceCaps( pdx.hDC, VERTRES ); 

        //===================== IMPORTANT !!! ==========================//
        //               Must test this on real printer !!!             //
        //         For now testing is done in XPS and MS OneNote2007    //
        //==============================================================//

        //================== end of initialization =====================//

        if( StartDoc( pdx.hDC, &diDocInfo ) > 0 )
        {
            if( StartPage( pdx.hDC ) > 0 )
            {
                //===== creating red pen that will draw grid =====//
                LOGBRUSH lb;
                lb.lbColor = RGB( 255, 0, 0 );
                lb.lbHatch = 0;
                lb.lbStyle = BS_SOLID;

                HPEN hPen = ExtCreatePen( PS_COSMETIC | PS_SOLID, 1, &lb, 0, NULL); 
                HGDIOBJ oldPen = SelectObject( pdx.hDC, hPen );

                // create test font
                HFONT font, oldFont; 

                long lfHeight = -MulDiv( 14,
                    GetDeviceCaps( pdx.hDC, LOGPIXELSY ), 
                    72 );

                font = CreateFont( lfHeight, 0, 0, 0, 
                    FW_BOLD, TRUE, FALSE, FALSE, 
                    0, 0, 0, 
                    0, 0, L"Microsoft Sans Serif" );

                oldFont = SelectFont( pdx.hDC, font );

                SetBkMode( pdx.hDC, TRANSPARENT );
                SetTextColor( pdx.hDC, RGB( 255, 0, 0 ) );

                // testing rectangle -> top left cell of the grid
                RECT rcText;

                rcText.left = 0;
                rcText.top = 0;
                rcText.right = pageWidth / 4;
                rcText.bottom = pageHeight / 10;

                // fill destination rectangle with gray brush
                // so we can visually validate rectangle coordinates 
                FillRect( pdx.hDC, &rcText, (HBRUSH)GetStockObject(LTGRAY_BRUSH) );

                // implement solution mentioned in the comment to this question
                SIZE s;
                ::GetTextExtentPoint32( pdx.hDC, 
                    L"Хидрогеотермална енергија Хидрогеотермална енерги", 
                    wcslen( L"Хидрогеотермална енергија Хидрогеотермална енерги" ), 
                    &s );

                // select old font back and dispose test font
                SelectObject( pdx.hDC, oldFont );
                DeleteObject( font );

                // adjust font height
                lfHeight *= s.cy / ( rcText.bottom - rcText.top );

                // now we can create proper font 
                font = CreateFont( lfHeight, 0, 0, 0, 
                    FW_BOLD, TRUE, FALSE, FALSE, 
                    0, 0, 0, 
                    0, 0, L"Microsoft Sans Serif" );

                oldFont = SelectFont( pdx.hDC, font );

                // draw text in test rectangle 
                DrawTextEx( pdx.hDC,
                    L"Хидрогеотермална енергија Хидрогеотермална енерги", 
                    wcslen( L"Хидрогеотермална енергија Хидрогеотермална енерги" ), 
                    &rcText, DT_CENTER | DT_WORDBREAK | DT_NOCLIP, NULL );

                //============== draw a testing grid ===============//

                // draw vertical lines of the grid
                for( int i = 0; i <= pageWidth; i += pageWidth / 4 )
                {
                    MoveToEx( pdx.hDC, i, 0, NULL );
                    LineTo( pdx.hDC, i, pageHeight );
                }

                // draw horizontal lines of the grid
                for( int j = 0; j <= pageHeight; j += pageHeight / 10 )
                {
                    MoveToEx( pdx.hDC, 0, j, NULL );
                    LineTo( pdx.hDC, pageWidth, j );
                }

                // no need for pen anymore so delete it
                SelectObject( pdx.hDC, oldPen );
                DeleteObject( hPen );

                // no need for font, delete it
                SelectFont( pdx.hDC, oldFont );
                DeleteFont( font );

                if( EndPage( pdx.hDC ) < 0 )
                // for now pop a message box saying something went wrong
                    MessageBox( hWnd, L"EndDoc failed!", L"Error", MB_OK );
            }

            EndDoc( pdx.hDC );
        }
    }

    if (pdx.hDevMode != NULL) 
        GlobalFree(pdx.hDevMode); 

    if (pdx.hDevNames != NULL) 
        GlobalFree(pdx.hDevNames); 

    if (pdx.lpPageRanges != NULL)
        GlobalFree(pPageRanges);

    if (pdx.hDC != NULL) 
        DeleteDC(pdx.hDC);

    return hResult;
}

要使用此功能,只需按下按钮/菜单选择或其他任何内容即可启动。

XPS 中的结果似乎是一致的,但我在 MS OneNote 2007 中得到了奇怪的结果,下面的图片说明了这些结果:

字体大小为14:

enter image description here

字体大小为20:

enter image description here

字体大小为20,但已应用上述功能缩放:

enter image description here

编辑结束

于2014年7月6日编辑:

上面编辑的第三张图片是GDI使用默认高度值的结果,因为我对字体高度进行数学调整的结果为0。一旦提到zero is passed to CreateFont提及的行为。

doubleint执行正确的投射后,我得到了近乎完美的输出 - &gt;字符串勉强中的最后一个字母超出限制。因为我认为有希望,我将继续尝试改进这个公式。如果有人有其他数学解决方案,请随时发布。

编辑结束

如果需要进一步的信息/编辑,请发表评论,我会尽快做出反应。

2 个答案:

答案 0 :(得分:3)

涉及多个问题。

我看到的最大问题是这一行:

lfHeight *= s.cy / ( rcText.bottom - rcText.top );

这些都是整数。在C和C ++中,使用整数除法会导致截断为零。因此,如果划分的结果&#34;应该&#34;如果是3.7,你最终会得到3,这可能是一个相当粗略的近似值。

另一个问题是GetTextExtentPoint32不会包装文本,但DrawText会。因此,您正在测量文本,就好像您要将其作为单行打印一样,并且您实际上将其绘制为多行。您可以使用DrawText DT_CALCRECT标志来测量高度,而不是使用GetTextExtendPoint32。

将这些放在一起,你想要像这样测量你的文字:

WCHAR szText[] = L"Хидрогеотермална енергија Хидрогеотермална енерги";
RECT rcText;
rcText.left = 0;
rcText.top = 0;
rcText.right = pageWidth / 4;
rcText.bottom = top;
const DWORD options = DT_CENTER | DT_WORDBREAK | DT_NOCLIP;
DrawTextEx( pdx.hDC, szText, -1, &rcText,  options | DT_CALCRECT, NULL);

// Because we used DT_CALCRECT, the DrawTextEx call didn't draw anything,
// but it did adjust the bottom of rcText to account for the actual height.
double actual_height = static_cast<double>(rcText.bottom - rcText.top);
double desired_height = pageHeight / 10.0;
double ratio = desired_heigth / actual_height;

// Scale the font height by the ratio, and round it off to the nearest int.
lf.lfHeight = static_cast<int>(lf.lfHeight * ratio + 0.5);

答案 1 :(得分:3)

好。基本上,我从建议的pointSize(代码中的14)开始,并尝试使用提供的边界矩形绘制文本。如果文本太大,我会进入一个迭代循环,减少pointsize并再次测量,直到文本适合边界矩形。

另一方面,如果文本“太小”,我会进入一个循环,逐渐增加它的大小,直到它太大。一旦达到这一点,我将点大小减小2并返回。

减少2是一个kludge或hack。我注意到有时报告的大小等于或小于边界矩形的报告大小,但仍有一些字符会突出到边界矩形的边缘。

更好的解决方案是利用DrawTextEx函数来计算大小并绘制文本。这样会更好,因为您可以使用传递给该函数的DRAWTEXTPARAMS结构的iLeftmargin和iRightMargin成员。无论你是希望在每一侧都有一个边距,还是只想添加一个角色的宽度,那么在绘制文本时你减半就完全取决于所需的结果。我还添加了DT_EXTERNALLEADING标志以获得文本上方/下方的小边距,尽管没有一个用于垂直填充,因此您必须使用我提到的边距属性。

由于DT_VCENTER标志不适用于多行文本,如果您希望文本垂直居中,则还需要自己垂直偏移文本。您只需将用于实际绘制文本的矩形偏移为区域矩形高度与文本边界高度之间差异的一半。

我可以在一些项目中使用这样的功能,所以感谢实际运用灰质并推动它的推动力!

最后,我使用了一个交互式演示 - 一个响应(空)对话框的WM_PAINT消息的演示。由于无论是打印机还是屏幕,HDC的处理方式大致相同,因此可以更快地调查结果。

插入代码时的输出:(通过cutePDF虚拟打印机) enter image description here

<强>代码:

int rectWidth(RECT &r)
{
    return (r.right - r.left) + 1;
}

int rectHeight(RECT &r)
{
    return (r.bottom - r.top) + 1;
}

void measureFunc(int pointSize, HDC hdc, RECT &pRectBounding, WCHAR *textToDraw, WCHAR *fontFaceName, int &resultWidth, int &resultHeight)
{
    int pixelsPerInchY = GetDeviceCaps(hdc, LOGPIXELSY);
    int logHeight = -MulDiv(pointSize, pixelsPerInchY, 72);
    RECT tmpRect = pRectBounding;
    HFONT old, tmp = CreateFont( logHeight, 0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0, 0, 0, fontFaceName );
    old = (HFONT)SelectObject(hdc, tmp);
    DrawText(hdc, textToDraw, -1, &tmpRect, DT_CENTER | DT_WORDBREAK | DT_NOCLIP | DT_CALCRECT| DT_EXTERNALLEADING  );
    SelectObject(hdc, old);
    DeleteObject(tmp);
    resultWidth = rectWidth(tmpRect);
    resultHeight = rectHeight(tmpRect);
}

HFONT getMaxFont(HDC hdc, WCHAR *fontName, WCHAR *textToDraw, RECT boundingRect)
{
    int maxWidth = rectWidth(boundingRect), maxHeight = rectHeight(boundingRect);
    int curWidth, curHeight, pointSize=14;

    measureFunc(pointSize, hdc, boundingRect, textToDraw, fontName, curWidth, curHeight);

    if ( (curWidth>maxWidth) || (curHeight>maxHeight) )
    {
        bool tooLarge = true;
        while (tooLarge)
        {
            pointSize--;
            measureFunc(pointSize, hdc, boundingRect, textToDraw, fontName, curWidth, curHeight);

            if ((curWidth>maxWidth)||(curHeight>maxHeight))
                tooLarge = true;
            else
                tooLarge = false;
        }
    }

    else
    {
        bool tooSmall = true;
        while (tooSmall)
        {
            pointSize++;
            measureFunc(pointSize, hdc, boundingRect, textToDraw, fontName, curWidth, curHeight);
            if ( (curWidth<maxWidth) && (curHeight<maxHeight) )
                tooSmall = true;
            else
                tooSmall = false;
        }
        if ((curWidth>maxWidth) || (curHeight>maxHeight))
        {
            pointSize-=2;
        }
    }

    int pixelsPerInchY = GetDeviceCaps( hdc, LOGPIXELSY );
    int curFontSize;
    HFONT result;
    curFontSize = -MulDiv(pointSize, pixelsPerInchY, 72);
    result = CreateFont(curFontSize, 0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0, 0, 0, fontName );

    return result;
}

BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_INITDIALOG:
    {
    }
    return TRUE;

    case WM_SIZE:
        InvalidateRect(hwndDlg, NULL, true);
        return 0;

    case WM_ERASEBKGND:
        {
            RECT mRect;
            GetClientRect(hwndDlg, &mRect);
            HBRUSH redBrush = CreateSolidBrush(RGB(255,0,0));
            FillRect((HDC)wParam, &mRect, redBrush);
            DeleteObject(redBrush);
        }
        return true;

    case WM_PAINT:
        {
            HDC hdc;
            PAINTSTRUCT ps;
            HFONT requiredFont, oldFont;
            WCHAR *textToDraw = L"Хидрогеотермална енергија Хидрогеотермална енерги";
            WCHAR *fontFace = L"Microsoft Sans Serif";
            RECT boundingRect, dlgRect;

            hdc = BeginPaint(hwndDlg, &ps);
                oldFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);

                GetClientRect(hwndDlg, &dlgRect);
                SetRect(&boundingRect, 0,0, rectWidth(dlgRect) / 4, rectHeight(dlgRect) / 10);
                FillRect(hdc, &boundingRect, (HBRUSH)GetStockObject(WHITE_BRUSH));

                requiredFont = getMaxFont(hdc, fontFace, textToDraw, boundingRect);
                SelectObject(hdc, requiredFont);
                SetBkMode(hdc, TRANSPARENT);
                DrawText(hdc, textToDraw, -1, &boundingRect, DT_CENTER | DT_WORDBREAK | DT_NOCLIP | DT_EXTERNALLEADING  );

                SelectObject(hdc, oldFont);
                DeleteObject(requiredFont);

            EndPaint(hwndDlg, &ps);
        }
        return false;

    case WM_CLOSE:
    {
        EndDialog(hwndDlg, 0);
    }
    return TRUE;

    case WM_COMMAND:
    {
        switch(LOWORD(wParam))
        {
        }
    }
    return TRUE;
    }
    return FALSE;
}