Метрики текста
Операционная система Windows содержит в себе сложную подсистему для работы со шрифтами. В деталях эта система будет описана позже, так как она нетривиальна.
Приложения Windows могут выводить текст с использованием различных шрифтов. Многие программы, такие, как текстовые процессоры, позволяют выбирать произвольную (до некоторой степени) высоту букв. Но характеристики шрифтов в Windows не ограничиваются высотой и шириной букв. Для точного определения внешнего вида шрифта используется больше дюжины различных характеристик.
Прежде всего следует отметить, что все шрифты в Windows можно разделить на две группы. К первой группе относятся шрифты с фиксированной шириной букв (fixed-pitch font). Все буквы (и знаки, такие, как запятая, точка и т. д.) такого шрифта имеют одинаковую ширину. Вторая группа шрифтов - шрифты с переменной шириной букв (variable-pitch font). Для таких шрифтов каждая буква имеет свою ширину. Буква "Ш", например, шире буквы "i".
Кроме того, шрифты Windows можно разделить на растровые (raster font), контурные (stroke font) и масштабируемые (типа TrueType). Образцы этих шрифтов представлены на рис. 4.3.
Рис. 4.3. Растровый, контурный и масштабируемый шрифты
Растровые шрифты состоят из отдельных пикселов и используются при выводе текста на экран видеомонитора или принтер. Для обеспечения приемлемого качества текста в Windows имеется набор одинаковых растровых шрифтов для нескольких размеров букв. Если попытаться выполнить масштабирование растрового шрифта в сторону увеличения размера букв, наклонные линии и закругления будут изображаться в виде "лестницы" (см. рис. 4.3).
Контурные шрифты больше подходят для плоттеров. При масштабировании таких шрифтов можно достигнуть лучших результатов, чем при масштабировании растровых. Однако при большом размере букв результат все равно получается неудовлетворительный.
Масштабируемые шрифты TrueType сохраняют начертание символов при любом изменении размера, поэтому они чаще всего используются, например, при подготовке текстовых документов.
Любой из перечисленных выше шрифтов может быть с фиксированной или переменной шириной букв.
Первое время в наших примерах приложений мы будем использовать так называемый системный шрифт SYSTEM_FONT. Этот шрифт выбран в контекст отображения по умолчанию, поэтому нам не потребуются никакие функции для выбора шрифта. Системный шрифт используется в Windows, например, для текста в заголовках окон, меню и диалоговых панелях.
Системный шрифт относится к растровым шрифтам с переменной шириной букв.
Так как ширина букв переменная, вы не сможете выполнять вывод таблицы в несколько столбцов, ориентируясь на ширину букв. Вам придется либо выводить каждый столбец таблицы по отдельности, начиная с некоторой позиции в окне, либо подсчитывать длину каждой ячейки и дополнять ее пробелами до некоторой фиксированной ширины (первый способ нам кажется удобнее).
Переменная ширина букв усложняет задачу вывода текста, так как длина текстовой строки зависит не только от количества букв в строке, но и от того, из каких букв состоит строка. К счастью, в составе программного интерфейса Windows имеется специальная функция GetTextExtent, предназначенная для подсчета длины текстовой строки.
Для получения информации о шрифте, выбранном в контекст устройства, предназначена функция GetTextMetrics. Она имеет следующий прототип:
BOOL WINAPI GetTextMetrics(HDC hdc, TEXTMETRIC FAR* lptm);
Первый параметр функции (hdc) указывает контекст устройства, для которого требуется получить информацию о метрике шрифта. В качестве этого параметра можно использовать значение, возвращаемое функцией BeginPaint или GetDC.
Второй параметр функции (lptm) является дальним указателем на структуру типа TEXTMETRIC, в которую будет записана информация о метриках шрифта, выбранного в указанный контекст устройства.
В случае успешного завершения функция возвращает значение TRUE, в противном случае - FALSE.
Структура TEXTMETRIC описана в файле windows.h следующим образом:
typedef struct tagTEXTMETRIC { int tmHeight; int tmAscent; int tmDescent; int tmInternalLeading; int tmExternalLeading; int tmAveCharWidth; int tmMaxCharWidth; int tmWeight; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmFirstChar; BYTE tmLastChar; BYTE tmDefaultChar; BYTE tmBreakChar; BYTE tmPitchAndFamily; BYTE tmCharSet; int tmOverhang; int tmDigitizedAspectX; int tmDigitizedAspectY; } TEXTMETRIC;
Описание этой структуры мы начнем с полей, определяющих вертикальные размеры шрифта. Программисты MS-DOS пользовались, как правило, одним параметром - высотой букв шрифта. Для приложений Windows используются целых пять параметров, имеющих отношение к вертикальным размерам букв (рис. 4.4).
Рис. 4.4. Метрики шрифта
Отсчет всех размеров выполняется от так называемой базовой линии шрифта.
Общая высота букв находится в поле tmHeight структуры TEXTMETRIC. Эта высота складывается из двух компонент - tmAscent и tmDescent. Компонента tmAscent представляет собой высоту букв от базовой линии с учетом таких элементов, как тильда в букве "Й". Компонента tmDescent определяет пространство, занимаемое буквами ниже базовой линии. Сумма tmAscent и tmDescent в точности равна tmHeight.
Величина tmInternalLeading определяет размер выступающих элементов букв. Эта величина может быть равно нулю.
Величина tmExternalLeading определяет минимальный межстрочный интервал, рекомендуемый разработчиком шрифта. Ваше приложение может игнорировать межстрочный интервал, однако в этом случае строки будут соприкасаться друг с другом, что не улучшит внешнего вида окна.
А как определить ширину букв?
В структуре TEXTMETRIC есть два поля с именами tmAveCharWidth и tmMaxCharWidth. Поле tmAveCharWidth содержит среднее значение ширины строчных букв шрифта. Это значение приблизительно соответствует ширине латинской буквы "x". Поле tmMaxCharWidth определяет ширину самой широкой буквы в шрифте. Для шрифта с фиксированной шириной букв поля tmAveCharWidth и tmMaxCharWidth содержат одинаковые значения, которые зависят от самого шрифта.
Хорошо спроектированные приложения позволяют вам выбирать для отображения текста произвольные шрифты. Поэтому приложение никогда не должно ориентироваться на конкретные размеры шрифта. Вместо этого следует определять эти размеры динамически во время работы приложения с помощью специально предназначенных для этого средств, таких, как функция GetTextMetrics.
Другие поля структуры TEXTMETRIC мы рассмотрим позже, в главе, посвященной шрифтам.
Для иллюстрации использования метрик шрифта мы подготовили приложение TMETRICS. Главный файл приложения, содержащий функцию WinMain, приведен в листинге 4.15.
Листинг 4.15. Файл tmetrics\tmetrics.cpp
// ---------------------------------------- // Определение метрики шрифта // ----------------------------------------
#define STRICT #include <windows.h> #include <mem.h>
BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
char const szClassName[] = "TMETRICSAppClass"; char const szWindowTitle[] = "TMETRICS Application";
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
if(!InitApp(hInstance)) return FALSE;
hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры if(!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
memset(&wc, 0, sizeof(wc));
// Определяем стиль класса окна, при // использовании которого окно требует // перерисовки в том случае, если // изменилась его ширина или высота wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc); return (aWndClass != 0); }
При регистрации класса окна мы, указав стиль класса окна, потребовали, чтобы при изменении горизонтального или вертикального размера окна окно отмечалось как требующее обновления:
wc.style = CS_HREDRAW | CS_VREDRAW;
Функция окна будет получать сообщение WM_PAINT при изменении ширины или высоты окна.
Как и раньше, вся полезная работа выполняется функцией главного окна приложения (листинг 4.16).
Листинг 4.16. Файл tmetrics\wndproc.cpp
// ===================================== // Функция WndProc // =====================================
#define STRICT #include <windows.h> #include <stdio.h> #include <string.h>
void Print(HDC, int, char *);
static int cxChar, cyChar; static int cxCurrentPosition; static int cyCurrentPosition;
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования static TEXTMETRIC tm; // структура для записи метрик // шрифта switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта hdc = GetDC(hwnd);
// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);
// Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth;
// Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading;
// Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = cyChar;
// Освобождаем контекст ReleaseDC(hwnd, hdc); return 0; }
case WM_PAINT: { // Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = cyChar;
hdc = BeginPaint(hwnd, &ps);
// Выводим параметры шрифта, полученные во // время создания окна при обработке // сообщения WM_CREATE Print(hdc, tm.tmHeight, "tmHeight"); Print(hdc, tm.tmAscent, "tmAscent"); Print(hdc, tm.tmDescent, "tmDescent"); Print(hdc, tm.tmInternalLeading, "tmInternalLeading"); Print(hdc, tm.tmExternalLeading, "tmExternalLeading"); Print(hdc, tm.tmAveCharWidth, "tmAveCharWidth"); Print(hdc, tm.tmMaxCharWidth, "tmMaxCharWidth"); Print(hdc, tm.tmWeight, "tmWeight"); Print(hdc, tm.tmItalic, "tmItalic"); Print(hdc, tm.tmUnderlined, "tmUnderlined"); Print(hdc, tm.tmStruckOut, "tmStruckOut"); Print(hdc, tm.tmFirstChar, "tmFirstChar"); Print(hdc, tm.tmLastChar, "tmLastChar"); Print(hdc, tm.tmDefaultChar, "tmDefaultChar"); Print(hdc, tm.tmBreakChar, "tmBreakChar"); Print(hdc, tm.tmPitchAndFamily, "tmPitchAndFamily"); Print(hdc, tm.tmCharSet, "tmCharSet"); Print(hdc, tm.tmOverhang, "tmOverhang"); Print(hdc, tm.tmDigitizedAspectX,"tmDigitizedAspectX"); Print(hdc, tm.tmDigitizedAspectY,"tmDigitizedAspectY");
EndPaint(hwnd, &ps); return 0; }
case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }
// ========================================== // Функция для вывода параметров шрифта // в окно // ==========================================
void Print(HDC hdc, int tmValue, char *str) { char buf[80]; int i;
// Подготавливаем в рабочем буфере // и выводим в окно начиная с текущей // позиции название параметра sprintf(buf, "%s", str); i = strlen(str);
TextOut(hdc, cxCurrentPosition, cyCurrentPosition, buf, i);
// Подготавливаем в рабочем буфере // и выводим в текущей строке окна // со смещением значение параметра sprintf(buf, "= %d", tmValue); i = strlen(buf);
TextOut(hdc, cxCurrentPosition + 12 * cxChar, cyCurrentPosition, buf, i);
// Увеличиваем текущую позицию по // вертикали на высоту символа cyCurrentPosition += cyChar; }
Как вы уже знаете, при создании окна в функцию окна передается сообщение WM_CREATE. Это сообщение удобно использовать для инициализации данных, имеющих отношение к окну. В нашем случае при получении сообщения WM_CREATE функция окна получает контекст отображения (с помощью функции GetDC) и вызывает функцию GetTextMetrics, которая записывает сведения о метрике текущего выбранного в контекст шрифта в структуру с именем tm.
На основании информации, хранящейся в этой структуре, вычисляются значения переменных cxChar и cyChar, используемых соответственно в качестве ширины и высоты символов.
Далее в обработчике сообщения WM_CREATE инициализируются переменные, которые будут использованы для указания текущей позиции вывода текста:
cxCurrentPosition = cxChar; cyCurrentPosition = cyChar;
Перед возвратом обработчик сообщения WM_CREATE освобождает полученный контекст, вызывая функцию ReleaseDC.
Обработчик сообщения WM_PAINT выполняет инициализацию текущей позиции вывода текста и получает контекст отображения (с помощью функции BeginPaint). Вслед за этим с помощью функции Print, определенной в нашем приложении, обработчик выводит в окно названия параметров и их значения.
Функция Print выводит параметры в виде таблицы, состоящей из двух колонок. Для этого она вызывает уже знакомую вам функцию TextOut два раза для каждого параметра. В первый раз выводится название параметра, во второй - знак "=" и значение параметра.
Перед возвратом из функции текущая позиция вывода текста по вертикали увеличивается на одну строку:
cyCurrentPosition += cyChar;
Файл определения модуля для приложения TMETRICS приведен в листинге 4.17.
Листинг 4.17. Файл tmetrics\tmetrics.def
; ============================= ; Файл определения модуля ; ============================= NAME TMETRICS DESCRIPTION 'Приложение TMETRICS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Внешний вид главного окна приложения TMETRICS показан на рис. 4.5.
Рис. 4.5. Главное окно приложения TMETRICS
Из этого рисунка видно, что для системного шрифта, выбранного по умолчанию, значение tmExternalLeading равно нулю, поэтому возможно "слипание" строк, содержащих символы с "хвостиками", тильдами и надстрочными точками.
При уменьшении размера окна может получиться так, что часть строк будет обрезана нижней или правой границей окна (рис. 4.6).
Рис. 4.6. Окно уменьшенного размера
Через некоторое время мы научимся добавлять к окну полосы просмотра, с помощью которых в окне практически любого размера можно просматривать сколь угодно много текстовых строк.