C++ 除非指定了较低的字体质量,否则ExtTextOut失败,字符串很长

C++ 除非指定了较低的字体质量,否则ExtTextOut失败,字符串很长,c++,mfc,draw,C++,Mfc,Draw,有时我们的应用程序需要使用ExtTextOut绘制很长的字符串(例如6000个字符)。有时ExtTextOut失败并返回零,GetLastError也返回零 要重新创建这种情况,请创建一个简单的MFC单文档应用程序,然后将OnDraw设置为: void CTestExtTextView::OnDraw(CDC* pDC) { CTestExtTextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; L

有时我们的应用程序需要使用ExtTextOut绘制很长的字符串(例如6000个字符)。有时ExtTextOut失败并返回零,GetLastError也返回零

要重新创建这种情况,请创建一个简单的MFC单文档应用程序,然后将OnDraw设置为:

void CTestExtTextView::OnDraw(CDC* pDC)
{
 CTestExtTextDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 if (!pDoc)
  return;

 LOGFONT lfDetail = {0};
 lfDetail.lfHeight = -(::MulDiv(100, pDC->GetDeviceCaps(LOGPIXELSY), 720));
 lfDetail.lfCharSet = ANSI_CHARSET;
 lfDetail.lfOutPrecision = OUT_DEFAULT_PRECIS;
 lfDetail.lfQuality = CLEARTYPE_QUALITY;
 lfDetail.lfWeight = 400;
 _tcscpy_s(lfDetail.lfFaceName, LF_FACESIZE, _T("Arial"));

 CFont font;
 font.CreateFontIndirectW( &lfDetail );

 CFont * pold = pDC->SelectObject( &font );

 CString str = L"2 <office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:ooo=\"http://openoffice.org/2004/office\" xmlns:ooow=\"http://openoffice.org/2004/writer\" xmlns:oooc=\"http://openoffice.org/2004/calc\" xmlns:dom=\"http://www.w3.org/2001/xml-events\" xmlns:xforms=\"http://www.w3.org/2002/xforms\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" office:version=\"1.1\"><office:scripts/><office:font-face-decls><style:font-face style:name=\"Arial\" svg:font-family=\"Arial\" style:font-family-generic=\"swiss\" style:font-pitch=\"variable\"/><style:font-face style:name=\"Arial Unicode MS\" svg:font-family=\"&apos;Arial Unicode MS&apos;\" style:font-family-generic=\"system\" style:font-pitch=\"variable\"/><style:font-face style:name=\"Tahoma\" svg:font-family=\"Tahoma\" style:font-family-generic=\"system\" style:font-pitch=\"variable\"/></office:font-face-decls><office:automatic-styles><style:style style:name=\"co1\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:column-width=\"6.659cm\"/></style:style><style:style style:name=\"co2\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:column-width=\"7.408cm\"/></style:style><style:style style:name=\"co3\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:column-width=\"7.02cm\"/></style:style><style:style style:name=\"co4\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:column-width=\"7.214cm\"/></style:style><style:style style:name=\"co5\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:column-width=\"2.267cm\"/></style:style><style:style style:name=\"ro1\" style:family=\"table-row\"><style:table-row-properties style:row-height=\"0.473cm\" fo:break-before=\"auto\" style:use-optimal-row-height=\"true\"/></style:style><style:style style:name=\"ro2\" style:family=\"table-row\"><style:table-row-properties style:row-height=\"0.453cm\" fo:break-before=\"auto\" style:use-optimal-row-height=\"true\"/></style:style><style:style style:name=\"ta1\" style:family=\"table\" style:master-page-name=\"Default\"><style:table-properties table:display=\"true\" style:writing-mode=\"lr-tb\"/></style:style><style:style style:name=\"T1\" style:family=\"text\"><style:text-properties style:text-position=\"super 58%\"/></style:style></office:automatic-styles><office:body><office:spreadsheet><table:table table:name=\"Sheet1\" table:style-name=\"ta1\" table:print=\"false\"><office:forms form:automatic-focus=\"false\" form:apply-design-mode=\"false\"/><table:table-column table:style-name=\"co1\" table:default-cell-style-name=\"Default\"/><table:table-column table:style-name=\"co2\" table:default-cell-style-name=\"Default\"/><table:table-column table:style-name=\"co3\" table:default-cell-style-name=\"Default\"/><table:table-column table:style-name=\"co4\" table:default-cell-style-name=\"Default\"/><table:table-row table:style-name=\"ro1\"><table:table-cell office:value-type=\"string\"><text:p>Sample text for ExtTextOut</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>Second Column Sample Text</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>3<text:span text:style-name=\"T1\">rd</text:span> Column</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>A More Lengthly bit of text just to make sure we increase the size of the line to something that might make ExtTextOut fail. It doesn&apos;t actually fail if we switch to Anti-Aliased Fonts. Not sure why. It also doesn&apos;t fail if we make the text shorter.</text:p></table:table-cell></table:table-row><table:table-row table:style-name=\"ro1\"><table:table-cell office:value-type=\"string\"><text:p>Sample text for ExtTextOut</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>Second Column Sample Text</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>3<text:span text:style-name=\"T1\">rd</text:span> Column</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>A More Lengthly bit of text just to make sure we increase the size of the line to something that might make ExtTextOut fail. It doesn&apos;t actually fail if we switch to Anti-Aliased Fonts. Not sure why. It also doesn&apos;t fail if we make the text shorter.</text:p></table:table-cell></table:table-row><table:table-row table:style-name=\"ro1\"><table:table-cell office:value-type=\"string\"><text:p>Sample text for ExtTextOut</text:p></table:table-cell><table:table-cell office:value-type=\"string\"><text:p>Second Column Sample Text</text:p></table:table-cell><table:table-cell office:value-type=\"string\">";
 pDC->ExtTextOutW( 0,0, NULL, NULL, str, NULL );
 pDC->ExtTextOutW( 0,50, NULL, NULL, CString(L"But this will print out"), NULL );

 pDC->SelectObject( pold );
}
void ctestextextview::OnDraw(CDC*pDC)
{
CTestExtTextDoc*pDoc=GetDocument();
断言有效(pDoc);
if(!pDoc)
返回;
LOGFONT lfDetail={0};
lfDetail.lfHeight=-(::MulDiv(100,pDC->GetDeviceCaps(LOGPIXELSY),720));
lfDetail.lfCharSet=ANSI_字符集;
lfDetail.lfOutPrecision=OUT\u DEFAULT\u precision;
lfDetail.lfQuality=CLEARTYPE\u质量;
lfDetail.lfWeight=400;
_tcscpy_s(lfDetail.lfFaceName,LF_FACESIZE,_T(“Arial”);
CFont字体;
font.CreateFontIndirectW(&lfDetail);
CFont*pold=pDC->SelectObject(&font);
CString str=L"ExtTextOutSecond Column示例文本ExtText3rd Column示例文本第二列示例文本第三列更长一点的文本,只是为了确保我们将行的大小增加到可能导致ExtTextOut失败的程度。如果我们切换到抗锯齿字体,它实际上不会失败。不确定原因。如果我们将文本变短,它也不会失败。ExtTextOutS示例文本第二列示例文本第三列更长一点的文本,只是为了确保我们将行的大小增加到可能导致ExtTextOut失败的程度。如果我们切换到抗锯齿字体,它实际上不会失败。不确定原因。如果我们将文本变短,它也不会失败。ExtTextOutSecond列示例文本的示例文本“;
pDC->extTextOut(0,0,NULL,NULL,str,NULL);
pDC->extTextOut(0,50,NULL,NULL,CString(L“但这将打印出来”),NULL;
pDC->选择对象(pold);
}
当你运行应用程序时,你会看到屏幕中间有一行“但这会打印出来”。如果你设置lfQuality=ANTIALIASED\u QUALITY,那么它确实会打印出一些东西,但看起来不太对劲

我已经在Vista和XP上测试过了


有什么想法吗?

我创建了一种简单的字体:

CFont font;
font.CreatePointFont(720, _T("Times New Roman"));
CFont * pold = pDC->SelectObject( &font );
然后初始化字符串,直到无法打印。761个字符有效,762个字符失败:

CString str('a', 761); // Works
CString str('a', 762); // Fails
我尝试了不同的字体,但在字符数较多时失败。直到我计算了每个字符串的大小后才有意义:

CSize s = pDC->GetTextExtent(str);
两个字符串的宽度约为32700;正好接近16位有符号限制32767

我的印象是,16位坐标限制从NT开始被提升到了32位,所以我不知道为什么这在XP或Vista上不起作用。我模糊地记得一篇关于这个主题的KB文章,但我找不到它

我尝试使用TextOut和DrawText,得到了相同的结果

然后我试着画了几条线,以确保它们在16位限制之外工作:

pDC->MoveTo(10,0);
pDC->LineTo(10,38000);
pDC->MoveTo(10,38000);
pDC->LineTo(100, 38000);

它工作得很好,所以我猜基于文本的GDI函数中有一个bug。

进一步的实验表明,它似乎并不具体取决于行的长度或结果的宽度,也就是说,一些字符更多、宽度更宽的行可以很好地打印出来(例如,7365个字符和40360个宽度的行打印得很好,而6572个字符和36113个宽度的另一行打印得不好)。但是,可以通过更改行的背景色之类的内容使这些较长的行失败

这让我相信,这可能是由于文本行的复杂性,而不仅仅是行的长度,并且可能存在内部超时,即如果ExtTextOut发现它花费的时间太长,那么它会在不打印任何输出的情况下退出

我们的解决方案是将每行分割成500个字符的块。因此,不是6000个字符的行只打印一个ExtTextOut,而是在最后一行末尾每次打印12个ExtTextOut。这似乎可以完美地工作,性能影响很小,并允许我们打印非常大的行(我在60000个字符后停止了测试)

  • 我在Windows 10上观察到了4000个字符的相同问题(因此这个相当老的问题在2016年仍然是一个话题)
  • 我在Windows 7上观察到了这个问题,但只有一台计算机上出现了这个问题,而另一台计算机上使用的是Windows 7
  • 当ExtTextOut失败时,它返回FALSE。因此这似乎不是一个bug,因为函数已经注意到出了问题
  • 从这些观察和其他人在这里写的内容,我可以推断:

  • Fat Elvis得出的结论是,函数中有16位限制肯定是错误的,否则它将在所有Windows 7机器上失败
  • snowdude关于超时起作用的理论非常有意义,因为我在速度较慢的Windows 7机器上观察到了问题,而在速度较快的Windows 7机器上,它正确地绘制了相同的字符串。可能图形驱动程序有一个时间限制,它必须在其中绘制字符。此外,MSDN说字符串不应为longer超过8192个字符。因此Microsoft已经声明可能存在字符串过长的问题
  • 解决方法当然是不要使用问题中建议的其他字体(质量较低的字体会加快绘图速度,这再次证实了超时理论)

    我写了一段代码,最终解决了这个问题。这个函数是高度优化的

    // ATTENTION:
    // The function returns FALSE on error but you cannot use GetLastError()!
    BOOL ExtTextOutChunks(HDC h_Dc, int X, int Y, UINT u32_Flags, const RECT* pk_Rect, 
                          const WCHAR* u16_String, UINT u32_StrLen, const int* ps32_DX)
    {
        // The maximum amount of characters that are printed at once.
        // The slower the computer the lower the value must be.
        const UINT CHUNK_SIZE = 500;
    
        // Speed optimization
        if (u32_StrLen <= CHUNK_SIZE)
            return ExtTextOut(h_Dc, X, Y, u32_Flags, pk_Rect, u16_String, u32_StrLen, ps32_DX);
    
        BOOL b_Return = TRUE;
        UINT u32_TxtAlign = GetTextAlign(h_Dc);
        BOOL b_SetFlag    = (u32_TxtAlign & TA_UPDATECP) == 0;
    
        // Set TA_UPDATECP to move the drawing position automagically after each drawing.
        // This is much faster than calling GetTextExtentPoint32() each time.
        if (b_SetFlag)
        {
            SetTextAlign(h_Dc, u32_TxtAlign | TA_UPDATECP);
            MoveToEx(h_Dc, X, Y, NULL);
        }
    
        while (u32_StrLen > 0)
        {
            UINT u32_Count = min(u32_StrLen, CHUNK_SIZE);
    
            if (!ExtTextOut(h_Dc, 0, 0, u32_Flags, pk_Rect, u16_String, u32_Count, ps32_DX))
            {
                b_Return = FALSE;
                break;
            }
    
            u32_StrLen -= u32_Count;
            u16_String += u32_Count;
    
            if (ps32_DX) ps32_DX += u32_Count;
        }
    
        // Reset the flag if it was not set before (ALWAYS!)
        if (b_SetFlag)
            SetTextAlign(h_Dc, u32_TxtAlign);
    
        assert(b_Return);
        return b_Return;
    }
    
    //注意:
    //函数在出错时返回FALSE,但不能使用GetLastError()!
    BOOL ExtTextOutChunks(HDC h_Dc、int X、int Y、UINT u32_标志、const RECT*pk_RECT、,
    常量WCHAR*u16_字符串,UINT u32_字符串,常量int*ps32_DX)
    {
    //一次打印的最大字符数。
    //计算机速度越慢,该值必须越低。
    consuint CHUNK_SIZE=500;
    //速度优化
    如果(u32_StrLen 0)
    {
    UINT u32_Count=min(u32_StrLen,块大小);
    if(!ExtTextOut(h_Dc,0,0,u32_标志,pk_Rect,u16_字符串,u32_计数,ps32_DX))
    {
    b_返回=F