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.

Любые идеи?


person snowdude    schedule 02.09.2009    source источник


Ответы (3)


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

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 есть ошибка.

person Fat Elvis    schedule 02.09.2009

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

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

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

person snowdude    schedule 03.09.2009

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

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

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