ExtTextOut завершается ошибкой с очень длинными строками, если не указано более низкое качество шрифта

Иногда нашему приложению нужно рисовать очень длинные строки (например, 6000 символов), используя ExtTextOut. Иногда ExtTextOut дает сбой и возвращает ноль, а GetLastError также возвращает ноль.

Чтобы воссоздать ситуацию, создайте простое приложение MFC Single Document и установите для 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 );
}

Когда вы запустите приложение, вы увидите одну строку "Но это будет распечатано" на полпути вниз по экрану. Если вы установите lfQuality = ANTIALIASED_QUALITY, то он на самом деле что-то распечатает, но просто не будет выглядеть правильно.

Я проверял это на Vista и XP.

Есть идеи?

3 ответа

Решение

Дальнейшие эксперименты показали, что это не относится конкретно к длине линии или получающейся в результате ширине, то есть некоторые строки с большим количеством символов и большей шириной будут печататься очень хорошо (например, строка с 7 365 символами и 40 360 ширинами печатается отлично, тогда как другая линия с 6,572 символами и шириной 36,113 потерпела неудачу). Тем не менее, те же самые более длинные линии могут потерпеть неудачу, изменив что-то вроде цвета фона линии.

Это приводит меня к мысли, что это может быть связано со сложностью текстовой строки, а не только с длиной строки, и, возможно, существует внутренний тайм-аут, то есть, если ExtTextOut видит, что это занимает слишком много времени, то он просто завершает работу без печать любого вывода.

Нашим решением было разделить каждую строку на 500 символов. Таким образом, вместо одного ExtTextOut для строки из 6 000 символов в каждом последующем печатается 12 ExtTextOuts. Кажется, что это прекрасно работает при очень небольшом падении производительности и позволяет нам печатать очень большие строки (я прекратил тестирование после 60 000 символов).

Я создал простой шрифт:

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-битных координат было увеличено до 32-битных, начиная с NT, поэтому я понятия не имею, почему это не будет работать на XP или Vista. Я смутно помню статью в КБ на эту тему, но не могу ее найти.

Я попытался использовать TextOut и DrawText и получил те же результаты.

Затем я попытался нарисовать пару строк, чтобы убедиться, что они работают за пределами 16-битного ограничения:

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

и он работал нормально, так что я думаю, что есть ошибка в текстовых функциях GDI.

  1. Я наблюдаю ту же проблему с 4000 символов в Windows 10 (так что этот довольно старый вопрос все еще остается темой в 2016 году)
  2. Я наблюдаю проблему в Windows 7, но только на одном компьютере, в то время как он работает на другом компьютере с Windows 7.
  3. Когда ExtTextOut терпит неудачу, он возвращает FALSE. Так что, похоже, это не ошибка, потому что функция уже заметила, что что-то пошло не так.

Из этих наблюдений и из того, что здесь написали другие, я могу сделать вывод:

  1. Вывод Fat Elvis о том, что в функции есть ограничение в 16 бит, безусловно, неверен, иначе он потерпит неудачу на ВСЕХ машинах с Windows 7.
  2. Теория о том, что тайм-аут играет важную роль, имеет смысл, потому что я наблюдаю проблему на медленной машине с Windows 7, а на более быстрой машине с Windows 7 она правильно рисует ту же строку. Вероятно, графический драйвер имеет ограничение по времени, в течение которого он должен рисовать символы. Кроме того, в MSDN говорится, что строка не должна быть длиннее 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;
}
Другие вопросы по тегам