在指定矩形内拟合字符串

Posted

技术标签:

【中文标题】在指定矩形内拟合字符串【英文标题】:Fit string inside specified rectangle 【发布时间】:2015-06-13 18:43:19 【问题描述】:

我有需要在矩形内绘制的字符串。

问题在于有时字符串可能太大而无法放入。

如何调整字体大小以使字符串适合里面?

我已阅读 GDI 的文档,但一无所获。我仍然在互联网上不断搜索,希望能找到一些东西或获得自己的想法......

GDI+ 也是一个选项...

以下代码是为了回应用户 Jonathan Potter 的评论而发布的:

#include <windows.h>
#include <windowsx.h>   
#include <CommCtrl.h>
#include <stdio.h>      // swprintf_s()
#include <math.h>
#include <gdiplus.h>
#include <string>
using namespace Gdiplus;

// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
                         name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
                         processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
                         language='*'\"")

// link with Common Controls library
#pragma comment(lib, "comctl32.lib") 
#pragma comment(lib, "GdiPlus.lib")

//global variables
HINSTANCE hInst;

// main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

    switch (msg)
    
    case WM_PAINT:
    
        PAINTSTRUCT ps =  0 ;
        RECT rcClient =  0 ;
        HDC hdc = BeginPaint(hwnd, &ps);

        GetClientRect(hwnd, &rcClient);

        int pageWidth = rcClient.right - rcClient.left,
            pageHeight = rcClient.bottom - rcClient.top;

        HFONT font = NULL, oldFont = NULL;

        // target rectangle, text should fit inside
        Rectangle(hdc, 0, 0, pageWidth / 4, pageHeight / 10);

        SIZE sz;

        GetTextExtentPoint32(hdc, L"This is very long string that might not fit into specified rectangle",
            lstrlen(L"This is very long string that might not fit into specified rectangle"), &sz);

        if (sz.cx > (pageWidth / 4))
        

            // get current font
            LOGFONT lf;
            GetObject(GetCurrentObject(hdc, OBJ_FONT), sizeof(lf), &lf);

            // scale it
            lf.lfHeight = MulDiv(lf.lfHeight, (pageWidth / 4), sz.cx);

            font = CreateFontIndirect(&lf);

            oldFont = SelectFont(hdc, font);
        

        SetBkMode(hdc, TRANSPARENT);
        SetTextColor(hdc, RGB(255, 0, 0));

        // draw text in test rectangle 
        RECT rcText =  0 ;

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

        DrawTextEx(hdc,
            L"This is very long string that might not fit into specified rectangle",
            wcslen(L"This is very long string that might not fit into specified rectangle"),
            &rcText, DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOCLIP, NULL);

        if (font != NULL)
        
            SelectFont(hdc, oldFont);
            DeleteFont(font);
        

        EndPaint(hwnd, &ps);
    
        return 0L;
    case WM_SIZE:
    
        InvalidateRect(hwnd, NULL, TRUE);
    
        return 0L;
    case WM_CLOSE:
        ::DestroyWindow(hwnd);
        return 0L;
    case WM_DESTROY:
    
        ::PostQuitMessage(0);
    
        return 0L;
    default:
        return ::DefWindowProc(hwnd, msg, wParam, lParam);
    
    return 0;


// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
    int nCmdShow)

    // store hInstance in global variable for later use
    hInst = hInstance;

    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    // register main window class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Main_Window";
    wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    
        MessageBox(NULL, L"Window Registration Failed!", L"Error!",
            MB_ICONEXCLAMATION | MB_OK);

        return 0;
    

    // initialize common controls
    INITCOMMONCONTROLSEX iccex;
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    iccex.dwICC = ICC_LISTVIEW_CLASSES | ICC_STANDARD_CLASSES;
    InitCommonControlsEx(&iccex);

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    // create main window
    hwnd = CreateWindowEx(0, L"Main_Window", L"Autofit text inside rectangle",
        WS_OVERLAPPEDWINDOW, 50, 50, 200, 200, NULL, NULL, hInstance, 0);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    

    GdiplusShutdown(gdiplusToken);
    return Msg.wParam;

【问题讨论】:

调整字体大小并不是一个好主意。 Windows API 绘图功能支持换行,我记得,在太长的字符串末尾也有“…”(但也许我对后者的看法是错误的,也许那只是在 Windows 标题中)。如果您不希望缩短通常的解决方案是提供滚动功能(使用只读可能禁用的编辑控件)或工具提示或两者兼而有之。但是,如果您确实希望将文本缩放以适合,那么我建议将其绘制在一个适当大的矩形中的位图中,可能是所需大小的两倍,然后按比例缩小。 用当前字体测量字符串(例如用 DrawRect(DT_CALCRECT)`),找出你需要缩放字体大小以适应字符串的比例。例如。如果字符串当前是两倍长,则需要一半大小的字体。 计算出你需要缩放字体大小的比例 没那么简单。 DrawTextExDT_CALCRECT 在字符串无法容纳时扩展矩形,这让我的生活更加艰难...... GetTextExtentPoint32 告诉您字符串的宽度,如果这是您想要的。 @JonathanPotter:是的,我在自己的计算中使用了它。它仍然未能“拯救我”。也许我做错了什么,我不排除这个选项,所以如果你能详细说明我会很感激。如果需要,我也可以编写示例代码。 【参考方案1】:

你正在寻找DrawText:

int DrawText(_In_     HDC     hDC,
             _Inout_  LPCTSTR lpchText,
             _In_     int     nCount,
             _Inout_  LPRECT  lpRect,
             _In_     UINT    uFormat
            );

您指定矩形,它确保文本不会被绘制到该矩形之外。如果您需要根据文本和当前选择的字体计算矩形,它还有一个DT_CALCRECT 标志。或者您可以使用DT_END_ELLIPSISDT_PATH_ELLIPSISDT_WORD_ELLIPSIS 标志来截断文本的绘制并添加省略号,以便用户可以看到文本何时比矩形长。

【讨论】:

遗憾的是,DrawTextEx 会扩展原始矩形,如果文本无法容纳,请仔细阅读有关 DT_CALCRECT 标志的文档。正是这种行为使我无法正确使用此 API,我只是不知道如何计算将这种行为考虑在内的缩放比例。 @AlwaysLearningNewStuff,我可以建议,如果返回的矩形比预期的大,您可以减小字体大小并重试。如果可能有很大范围的字体大小,请使用二分搜索来减少搜索中的步骤数。此外,搜索可以通过所需的矩形大小和返回的矩形大小之间的差异来指导。例如。如果 rect 太大了 50%,则将字体大小减小 50% 并从那里搜索。 Uniscribe 可能有更高级别的解决方案,我没有使用过。 @cardiffspaceman:我听说过 Uniscribe,但从未使用过。我会试试你的其他建议。理解这对我来说很难,因为这是我第一次尝试解决此类问题。【参考方案2】:

理论上,这样的事情应该可以工作,但我还没有测试过。添加适当的错误检查等。

SIZE sz;
GetTextExtentPoint32(hDC, pszMyString, lstrlen(pszMyString), &sz);

if (sz.cx > iMyMaximumWidth)

    // get current font
    LOGFONT lf;
    GetObject(GetCurrentObject(hDC, OBJ_FONT), sizeof(lf), &lf);

    // scale it
    lf.lfHeight = MulDiv(lf.lfHeight, iMyMaximumWidth, sz.cx);
    HFONT hNewFont = CreateFontIndirect(&lf);

    .. use hNewFont to render string, remember to delete it when done

【讨论】:

我已经尝试过你的代码 sn-p 但结果不是我所期望的。文本不适合矩形。谢谢你的尝试。我还能做些什么来帮助您帮助我吗? 哦,这种方法只适用于单行文本DrawTextExDT_SINGLELINE | DT_VCENTER | DT_NOCLIP 尝试单行,但失败了。文本超出矩形的右边界。调整主窗口的大小后(我在主窗口的 WM_PAINT 中绘图),这种效果仍然存在。将窗口大小调整为 非常小 大小时,文本高度超过下限... 所以字体变小了 不,字体大致保持不变... 但还是溢出了? 是的,水平方向。在我将窗口缩小到非常小的尺寸的极端情况下,它也会垂直溢出...... 在渲染文本之前是否将新字体选择到 DC 中?请提供更多信息,而不是让我把它从你身上拖出来:) 如果字体没有变小,那就这么说吧,因为这就是你问题的重点。 请提供更多信息 使用演示应用编辑帖子。

以上是关于在指定矩形内拟合字符串的主要内容,如果未能解决你的问题,请参考以下文章

Java基础 awt 生成矩形图片并向内写入字符串

在多个矩形中绘制一个 NSString

java判断某个经纬度是不是在矩形内

在其他多边形中找到最大空矩形的算法

OpenCV求最小外接圆最小外接矩形椭圆拟合直线拟合

SMT 语法中的矩形拟合(Z3 求解器)