Directwrite: получение высоты шрифта
Моя цель: я хочу получить высоту шрифта IDWriteTextFormat, чтобы я мог рассчитать, сколько строк текста может уместиться в IDWriteTextLayout определенной высоты.
Моя проблема: сейчас я использую этот код для вычисления видимого количества строк:
inline int kmTextCtrl::GetVisLines() const
{
/* pTextFormat is an IDWriteTextFormat pointer, dpi_y is the desktop's vertical dpi,
and GetHeight() returns the height (in pixels) of the render target. */
float size = (pTextFormat->GetFontSize()/72.0f)*dpi_y;
return (int)(GetHeight()/size);
}
Расчет представляется точным для некоторых шрифтов, но не для любых шрифтов TrueType (например, Courier New, Arial, Times New Roman). Для этих шрифтов показанный текст обрезается значительно ниже нижней вертикальной границы цели рендеринга.
Некоторый контекст: я делаю текстовый прокручиваемый буферный элемент управления, который использует IDWriteTextLayout, чтобы поместить текст в цель рендеринга элемента управления. Я использую результат GetVisLines(), чтобы определить, сколько строк текста из кольцевого буфера (который хранит текст в строке std::strings), чтобы вставить в макет, и воссоздаю его каждый раз, когда окно прокручивается или изменяется.
Это делается с помощью "родного" Win32 API C++.
2 ответа
Самый простой и надежный подход - просто запросить у самого макета текстовые метрики, поскольку это одна из двух вещей, для которых он был разработан: рисование и измерение. Вы бы создали IDWriteTextLayout
используя текстовый формат и вызов GetMetrics
чтобы получить DWRITE_TEXT_METRICS::height
, Я предполагаю, что вы используете ID2D1RenderTarget::DrawText
и передачи текстового формата, так что вы, возможно, не создали макет напрямую, но вызывая DrawText
это как звонить CreateTextLayout
за собой следуют DrawTextLayout
,
Остерегайтесь того, что проходите через нижние слои, чтобы получить этот ответ (IDWriteFontFace
и тому подобное) делает определенные предположения о том, что универсальный текстовый готовый элемент управления не должен предполагаться, например, предполагается, что будет использован базовый шрифт и что все строки имеют одинаковую высоту. Пока все символы присутствуют в данном базовом шрифте, это сработает (скорее всего, вы в основном отображаете английский, поэтому все выглядит хорошо), но добавляете некоторые языки CJK или RTL (который является базовым шрифтом, таким как Times). Новый римлянин, конечно, не поддерживает), и высота строки будет увеличиваться или уменьшаться в соответствии с заменяемыми шрифтами. GDI изменяет размеры замененных шрифтов таким образом, чтобы они соответствовали высоте основного шрифта, но это приводит к плохо сжатым буквам на языках, таких как тайский и тибетский, которые нуждаются в большем пространстве для подъема и спуска. IDWriteTextLayout
и другие макеты, такие как в WPF/Word, сохраняют все глифы шрифтов в одном и том же размере em, что означает, что они располагаются лучше, когда они расположены рядом друг с другом; но это означает, что высота линии является переменной.
Если вы просто нарисуете каждую строку текста, как если бы все они были одинаковой высоты, вы можете увидеть перекрытие между глифами и неоднородными базовыми линиями между линиями или отсечение вверху и внизу элемента управления. Таким образом, идеальная вещь - это использовать фактическую высоту каждой строки; но если вам нужно, чтобы все они были одинаковой высоты (или если это слишком усложняет элемент управления), то по крайней мере установите явный межстрочный интервал, используя SetLineSpacing
с DWRITE_LINE_SPACING_UNIFORM
по сравнению с базовым шрифтом - таким образом, базовые линии равномерно разнесены.
Хотя, для любопытных, да, высота линии действительно определяется с использованием метрик дизайна шрифта ascent + descent, а также любого присутствующего lineGap (большинство шрифтов устанавливают его на ноль, но Gabriola является хорошим примером большого разрыва строки), умножить на размер em и разделить на единицы на em. Обратите внимание, что все размеры em указаны в DIP (что при типичном 96DPI означает 1:1, DIP точно == пиксели), а не в точках (1/72 дюйма).
Я нашел ответ. Чтобы найти межстрочный интервал (высота шрифта плюс разрыв) в Directwrite, вы должны сделать что-то похожее на следующее:
inline int kmTextCtrl::GetVisLines() const
{
IDWriteFontCollection* collection;
TCHAR name[64]; UINT32 findex; BOOL exists;
pTextFormat->GetFontFamilyName(name, 64);
pTextFormat->GetFontCollection(&collection);
collection->FindFamilyName(name, &findex, &exists);
IDWriteFontFamily *ffamily;
collection->GetFontFamily(findex, &ffamily);
IDWriteFont* font;
ffamily->GetFirstMatchingFont(pTextFormat->GetFontWeight(), pTextFormat->GetFontStretch(), pTextFormat->GetFontStyle(), &font);
DWRITE_FONT_METRICS metrics;
font->GetMetrics(&metrics);
float ratio = pTextFormat->GetFontSize() / (float)metrics.designUnitsPerEm;
float size = (metrics.ascent + metrics.descent + metrics.lineGap) * ratio;
float height = GetHeight();
int retval = static_cast<int>(height/size);
ffamily->Release();
collection->Release();
font->Release();
return retval;
}
Конечно, вы, вероятно, не хотите делать все это каждый раз, когда вам приходится вызывать часто используемую встроенную функцию.