Calculate ideal font size, based on the paper size and maximum allowed text length

和自甴很熟 提交于 2019-12-04 13:14:06

There are multiple issues involved.

The biggest problem I see is in this line:

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

These are all integers. In C and C++, division with integers results in truncation toward zero. So if the result of the division "should" be 3.7, you'll end up with 3, which can be a pretty crude approximation.

Another problem is that GetTextExtentPoint32 does not wrap text, but DrawText does. So you're measuring the text as though you're going to print it as a single line, and you actually draw it as multiple lines. Instead of using GetTextExtendPoint32, you can measure the height with DrawText by DT_CALCRECT flag.

Putting these together, you want to measure your text like this:

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);

Okay. Basically, I start off with the suggested pointSize (14 in your code) and try to draw the text using the supplied bounding rect. If the text is too large, I go into an iterative loop that decreases the pointsize and measures again until the text will fit into the bounding rect.

If, on the other hand, the text is 'too small' I go into a loop that gradually increases it's size until it is too large. Once I reach this point, I decrease the point-size by 2 and return.

The reduction by 2 is a kludge or hack. I noticed that at times the size was reported as being equal to or smaller than the reported size of the bounding rect, yet still some characters would protrude past the edge of the bounding rect.

A better solution would make use of the DrawTextEx function to both calculate the size and draw the text. This would be better since you could make use of the iLeftmargin and iRightMargin members of the DRAWTEXTPARAMS struct that is passed to that function. Whether you wished to have a margin on each side, or simply wanted to add a single character's width, that you then halved when drawing the text would depend entirely on the desired outcome. I also added the DT_EXTERNALLEADING flag to obtain a small margin above/below the text, though there isn't one for vertical padding, so you'd have to make use of the margin attributes I mention.

Since the DT_VCENTER flag doesn't work with multi-line text, you'd also need to vertically offset the text yourself if you wished it to be vertically centered. You'd just have to offset the rect used for actually drawing the text by half of the difference between the area bounding rect's height and the text bounding rect's height.

I could have used a function like this for a few projects, so thanks for the impetus to actually exercise the grey matter and work it out!

Lastly, I used an interactive demo - one that responded to the WM_PAINT message of a (empty) dialog box. Since a HDC can be treated more-or-less the same whether it be for a printer or the screen, it provided a much quicker way of investigating the result.

Output when plugged into your code: (via cutePDF virtual printer)

Code:

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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!