在给定目标打印机的 DPI 的情况下,如何以某种字体计算某些打印文本的大小?

Posted

技术标签:

【中文标题】在给定目标打印机的 DPI 的情况下,如何以某种字体计算某些打印文本的大小?【英文标题】:How do I calculate some printed text's size in a certain font given DPI of the target printer? 【发布时间】:2013-06-07 14:14:56 【问题描述】:

我有一个将数据打印到打印机的应用程序。现在,我已经将字体大小硬编码为 18 点,计算了所有与打印相关的坐标和偏移量(对于某个 18 点字体),我只是在打印。我这样做只是为了开发我的应用程序。

现在,我希望能够根据字体大小和脸型动态调整所有内容(应该如此)。

我编写了以下测试代码,没有任何错误检查,(C) 来获取字体的逻辑大小:

void GetTextSize(char *input, size_t inputSize, char *fontName, size_t fontSize, SIZE *size)

    if(input == NULL || size == NULL || fontName == NULL)
    
        return;
    
    else
    
        HFONT hFont = NULL;
        LOGFONT lf;
        HDC hdc = NULL;

        memset(&lf, 0, sizeof(lf));

        // Get the device context of the entire screen
        hdc = GetDC(NULL);

        // Set the face-name
        strcpy(lf.lfFaceName, fontName);

        // Set the font height
        lf.lfHeight = -MulDiv(fontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);

        // Set the font weight
        lf.lfWeight = FW_NORMAL;

        // Create the font
        hFont = CreateFontIndirect(&lf);

        // Re-associate the obtained device context with the font just created
        SelectObject(hdc, hFont);

        // Get the required dimensions for the text
        GetTextExtentPoint32(hdc, input, inputSize, &size);

        // Free resources
        ReleaseDC(NULL, hdc);
        DeleteObject(hFont);
        hFont = NULL;
        hdc = NULL;
    

基本上,

    获取整个屏幕的设备上下文。 使用CreateFontIndirectLOGFONT 结构创建所需的字体。 将设备上下文与创建的字体重新关联。 使用GetTextExtentPoint32 以逻辑单位计算字体宽度。

以上代码导致size 变量包含:cx = 241cy = 34。 (我显示器的 DPI 是 96)

如何将这些值映射到实际打印尺寸?由于默认文本映射模式为MM_TEXT,因此cxcy这些值对应的是像素。

我需要这样做有两个原因:

    我需要将长行拆分为多行。因为我知道以英寸为单位的页面宽度,所以我只需要以英寸为单位的文本宽度。 我需要根据字体大小决定从哪里开始打印。

根据打印机规格,每毫米点数为 8;即 DPI 约为 203.2。

【问题讨论】:

您可能有兴趣参考“Charles Petzold 的 Windows 编程”。它拥有你想要的一切。第 13 章。 @chux,你为什么推荐标记到 .NET?我上面介绍的代码是使用 Winapi 的 C 语言。如果有的话,我必须添加 Winapi 标记。 @chux,您所说的“打印机界面的点高大小”是什么意思? @bugsbunny,谢谢。现在调查一下。 【参考方案1】:

您的代码已经接近您所需要的。您需要修复句柄泄漏,需要在删除之前恢复设备上下文:

    HGDIOBJ hOldFont = SelectObject(hdc, hFont);

    // Get the required dimensions for the text
    GetTextExtentPoint32A(hdc, input, strlen(input), size);

    // Free resources
    SelectObject(hdc, hOldFont);
    ::ReleaseDC(NULL, hdc);
    DeleteObject(hFont);

您接下来要做什么在很大程度上取决于您希望结果的准确度。屏幕上 15 点的字体在纸上也将是 15 点。因此,如果您想找到适合纸张的最宽字符串,那么您可以从以下位置获得它:

 int maxWidth = (int)(paperWidthInInches * GetDeviceCaps(hdc, LOGPIXELSX));

请注意 DrawTextEx() 函数如何处理将文本放入纸张中的大量繁重工作。它需要一个您设置为纸张大小的 RECT,它会自动渲染文本以​​适应该矩形。您通常希望使用 DT_WORDBREAK 选项使其在单词边界处自动换行文本。使用 DT_CALCRECT 选项计算页面布局而不实际呈现文本。 DRAWTEXTPARAMS.uiLengthDrawn 值将被更新,以告诉您当字符串不适合页面时从哪里开始打印。

现在只需将打印机设备上下文传递给 DrawTextEx(),您就会得到渲染到打印机的文本。传递从 CreateDC() 获得的设备上下文。 PrintDlg() 函数方便用户选择打印机。

这是个好消息。坏消息是 GDI 不提供 true 与设备无关的文本呈现。特别是对于显示器,渲染文本的宽度被巧妙地改变以固定像素网格。这提供了高度可读的文本,但会在具有更高 DPI 的设备(如您的打印机)上抛出布局。差异很小,只有一行文本上的几个像素,字体越大,差异就越小。而这些微小的差异往往会因为换行而在印刷文本上变成很大的差异。

为避免这种情况,您需要使用打印机设备上下文来计算页面布局。通过将 RECT 传递给仅适合单行的 DrawTextEx() 来找出每行的结束位置。当然,您现在渲染到屏幕上的内容并不完美,您需要一些肘部空间来渲染可能更宽的字符串。 DirectWrite 为真正的设备无关文本渲染提供了一个 API。

【讨论】:

非常感谢您的回答!我无法使用 DirectWrite API,因为我的应用程序还需要支持 Windows 2000 和 XP。 所以如果我理解正确的话,我必须使用 DrawTextEx 将我的文本分成几行,然后用它一次渲染每一行? DrawTextEx 已经这样做了。这取决于您想要easy 还是accurate。使用简单的方法,无需单独渲染每一行,DrawTextEx 会为您完成。您只需要弄清楚分页符发生在哪里。如果您想要准确,那么您需要找出打印机上出现换行符的位置,并在屏幕上单独绘制每一行以进行匹配。 我刚刚意识到我无法使用打印机的设备上下文进行打印。这是因为我正在处理 ZPL 打印机(具体来说是 Argox OS-2140 DZ)。为了执行我的打印任务,我使用了APIs that Argox provides。要打印一些文本,API 只接受文本、X 和 Y 坐标(以点为单位)以及各种字体参数。所以我现在要做的是使用DrawTextEx 将文本拆分为一行,将其添加到行缓冲区,拆分下一行等等。 (续)我已经实现了一些逻辑来计算坐标。我还实现了一些不稳定的逻辑来将文本分成几行,直到你指出这个更好的方法。我正在做的事情是必要的还是我又错过了什么?非常感谢您的帮助!

以上是关于在给定目标打印机的 DPI 的情况下,如何以某种字体计算某些打印文本的大小?的主要内容,如果未能解决你的问题,请参考以下文章

Android - 如何在以编程方式加载或保存 JPEG 文件时获取或设置(打印)DPI(每英寸点数)?

PhoneGap - 视口上的目标密度 dpi

如何在不重复给定范围的情况下打印所有可能的变化?

Poppler:以目标分辨率渲染

如何在给定两个绝对路径的情况下找到相对路径?

Scintilla:如何在给定特定字符位置的情况下找到字节位置