CodeNet / Платформы / Windows / MFC / Основы программирования с помощью библиотеки Microsoft Foundation Classes
Вывод текста и шрифты - MFC
В данной главе мы рассмотрим основы работы со шрифтами. В Windows есть практически неограниченные возможности по управлению выводом текста. Некоторые из функций сейчас утратили свое былое значение (или то, что было задумано, оказалось почти невозможным реализовать), другие же очень широко используются. Конечно же, большинство функций мы не сможем здесь рассмотреть. Но зато мы рассмотрим использование конкретных шрифтов для вывода текста в контекст устройства, учитывая сложившуюся сегодня практику использования шрифтов в Windows.
Небольшое введение
Любой шрифт, с которым мы имеем дело в Windows, характеризуется несколькими параметрами. Гарнитура (typeface) - это совокупность нескольких начертаний шрифта, объединенных стилевыми и другими признаками. Пример гарнитур: Arial, Times New Roman, MS Sans Serif. Размер шрифта - это высота прямоугольника, в который помещаются все символы шрифта, выражается в специальных единицах - пунктах. Пункт равен 1/72 части дюйма. Эта единица пришла из полиграфии. Начертание - это специфические характеристики шрифта. В Windows доступны четыре начертания: нормальное (normal), курсивное (italic), жирное (bold) и жирное курсивное (bold italic). Кроме того, шрифты могут быть моноширинные (fixed pitch), пример - Courier New, и пропорциональные (variable pitch), пример - Times New Roman.
Сейчас в Windows в основном используются шрифты двух групп: растровые (примеры - MS Sans Serif, Fixedsys) и контурные TrueType (примеры - Arial, Courier New). Первые представляют собой жестко определенные битовые матрицы для каждого символа и предназначены для отображения не очень крупного текста на экране. Вторые представляют собой очень сложные объекты. В них заданы контуры символов, которые закрашиваются по определенным правилам. Каждый шрифт TrueType - это программа на специальном языке, которая выполняется интерпретатором под названием растеризатор. Программа шрифта полностью определяет способ расчета конкретных битовых матриц символов на основе контуров. При использовании символов маленького размера (высотой приблизительно до 30 точек) модель контуров становится некорректной и символы сильно искажаются. Для борьбы с этим в качественных шрифтах используется разметка (хинты). Разметка шрифта - чрезвычайно сложный и долгий процесс, поэтому на рынке встречается немного качественно размеченных шрифтов. Поэтому использовать TrueType шрифты для вывода текста на экран нежелательно, за исключением стандартных шрифтов Windows (Times New Roman, Arial и Courier New), которые очень качественно размечены.
Координаты при выводе текста
За вывод текста в окно в MFC отвечает функция CDC::TextOut(). Ей нужно указать координаты для вывода строки. Эти координаты являются логическими координатами, то есть они измеряются в логических единицах. Это относится и к другим функциям вывода информации в окно. В процессе отображения информации логические координаты преобразуются в пиксели. По умолчанию логическими координатами являются пиксели, что для нас очень удобно.
Задание цвета текста и фона
При выводе текста с помощью функции TextOut() по умолчанию текст выводится черным цветом на текущем (обычно белом) фоне. Однако с помощью следующих функций эти параметры можно изменить:
virtual COLORREF CDC::SetTextColor(COLORREF Color); virtual COLORREF CDC::SetBkColor(COLORREF Color);Функции возвращают значение предыдущего цвета. Тип COLORREF представляет собой 32-разрядное беззнаковое целое число - представление цвета в виде красной, зеленой и синей компонент, каждая размером в 8 бит. Для формирования этого значения существует макрос RGB().
Задание режима отображения фона
C помощью функции SetBkMode() можно задать режим отображения фона. Прототип функции такой:
int CDC::SetBkMode(int Mode);Функция определяет, что происходит с текущим цветом фона (а также некоторых других элементов) при отображении текста. Режим может принимать одно из двух значений: OPAQUE и TRANSPARENT. В первом случае при выводе текста будет выводится также и текущий фон. Во втором случае фон выводится не будет (он будет "прозрачным"). По умолчанию используется режим OPAQUE.
Получение метрик текста
Большинство текстовых шрифтов являются пропорциональными, то есть ширина символов у них разная. Кроме того, расстояние между строками зависит как от шрифта, так и от его размера. Весь вывод текста в Windows осуществляется программно, поэтому необходимо учитывать все эти параметры.
Например, при выводе одной текстовой строки после другой предполагается, что известны высота шрифта и количество пикселей между строками. Получить эту информацию можно с помощью функции:
BOOL CDC::GetTextMetrics(LPTEXTMETRICS TextAtttrib) const;Параметр является указателем на структуру TEXTMETRIC, в которую будут записаны установки текущего шрифта контекста устройства. Структура имеет достаточно много полей. Наиболее важные поля следующие:
LONG tmHeight | Полная высота шрифта |
LONG tmAscent | Высота над базовой линией |
LONG tmDescent | Высота подстрочных элементов |
LONG tmInternalLeading | Пустое пространство над символами |
LONG tmExternalLeading | Пустой интервал между строками |
LONG tmMaxCharWidth | Максимальная ширина символов |
Для получения числа логических единиц по вертикали между строками нужно сложить значения tmHeight и tmExternalLeading. Это не то же самое, что и высота символов.
Изменение шрифтов
Предположим, что мы имеем готовый инициализированный экземпляр класса CFont, содержащий некоторый шрифт (как это сделать, мы рассмотрим чуть ниже). Для изменения шрифта в контексте устройства используется член-функция SelectObject(), которая в нашем случае принимает один параметр - указатель на объект шрифта. После выполнения этой функции текст будет выводится новым шрифтом. Хотя функция возвращает указатель на объект старого шрифта, сохранять его вовсе не обязательно.
Инициализация объекта шрифта: выбор шрифта
После того, как объект класса CFont создан, необходимо инициализировать его конкретным шрифтом из установленных в системе, с заданными параметрами. Это может быть как растровый, так и контурный шрифт.
Наверное, Вы справедливо ожидаете наличия функции, которая позволяет задать только гарнитуру шрифта, начертание и размер, после чего шрифт будет проинициализирован. К сожалению, ни в MFC, ни в Windows такой функции нет. Единственная функция, которая подходит для выполнения этой задачи, имеет такой прототип:
BOOL CFont::CreateFont(int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename);Функция крайне неудобна, и смысл многих ее параметров на сегодняшний день не актуален. Функция была создана давно, и тогда казалась увлекательной идея подстановки и замены шрифтов, суть которой заключалась в следующем: программист задает такие параметры шрифта, какие он хочет иметь, а Windows сама на основе имеющихся в системе шрифтов произведет необходимые трансформации и синтезирует требуемый шрифт из имеющихся. Впоследствии оказалось, что эта технология не может быть удовлетворительно работоспособной (так как практически это сложная задача из области искусственного интеллекта). Также функция позволяет проводить трансформации шрифта - растягивать и сжимать его, выводить текст под углом. Сейчас это используется редко. Графические пакеты используют свои алгоритмы трансформаций, и часто используют шрифты PostScript.
Суть же заключается в том, что качественно могут быть отображены только те шрифты и в тех начертаниях, которые присутствуют в системе, причем без трансформаций (то есть с использованием хинтов, которые отключаются при трансформациях). Для большинства современных программ требуется высокое качество отображения текста, поэтому никакие трансформации и подстановки неуместны. Практически, являются разумными лишь два варианта поведения: либо программа будет использовать точно тот шрифт, который запросила, в таком виде, в каком его создал разработчик, либо откажется работать при отсутствии шрифта.
Учитывая вышесказанное, мы не будем подробно рассматривать параметры функции CreateFont(). Вместо этого мы рассмотрим пример кода, который позволяет задать размер шрифта, начертание и гарнитуру, минимизировав возможные трансформации шрифта и синтеза при отсутствии заданного. Такие же параметры для функции CreateFont() Вы можете использовать в своих программах. Желательно также проверять наличие шрифта, если он не является стандартным. Вот код, который используется в примере программы (указатель на объект шрифта хранится в переменной m_pFont):
void CMainFrame::SetClientFont(CString Typeface, // Гарнитура int Size, // размер в пунктах BOOL Bold, // Признак жирного начертания BOOL Italic // Признак наклонного начертания ) { // Получим контекст окна CWindowDC winDC(this); // Узнаем, сколько пикселей в одном логическом дюйме int pixelsPerInch = winDC.GetDeviceCaps(LOGPIXELSY); // Узнаем высоту в пикселях шрифта размером Size пунктов int fontHeight = -MulDiv(Size, pixelsPerInch, 72); // Устанавливаем параметр жирности для функции CreateFont() int Weight = FW_NORMAL; if(Bold) Weight = FW_BOLD; // Удаляем предыдущий экземпляр шрифта -- нельзя дважды // инициализировать шрифт вызовом CreateFont(). delete m_pFont; m_pFont = new CFont; // Создание шрифта. Большинство параметров не используются. m_pFont->CreateFont(fontHeight, 0, 0, 0, Weight, Italic, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH | FF_DONTCARE, Typeface); }
Пример программы
В этом примере мы поместим декларации и описания объектов приложения и главного окна в разные файлы. Так почти всегда оформляются приложения на С++. Таким образом, у нас будет четыре исходных файла.
MainFrame.hpp #include "stdafx.h" class CMainFrame: public CFrameWnd { public: CMainFrame(); ~CMainFrame(); afx_msg void OnLButtonDown(UINT Flags, CPoint Loc); afx_msg void OnPaint(); virtual void SetClientFont(CString Typeface, int Size, BOOL Bold, BOOL Italic); private: CWinApp *pApp; CFont *m_pFont; COLORREF m_textColor; DECLARE_MESSAGE_MAP(); }; MainFrame.cpp #include "stdafx.h" #include "MainFrame.hpp" BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_PAINT() ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() CMainFrame::CMainFrame() { pApp = AfxGetApp(); this->Create(0, "Вывод текста и шрифты", WS_OVERLAPPEDWINDOW, rectDefault, 0, 0); m_pFont = new CFont; this->SetClientFont("Arial", 20, FALSE, FALSE); this->ShowWindow(SW_RESTORE); this->UpdateWindow(); } CMainFrame::~CMainFrame() { delete m_pFont; } afx_msg void CMainFrame::OnLButtonDown(UINT Flags, CPoint Loc) { // Получим структуру LOGFONT текущего шрифта, чтобы // узнать имя гарнитуры LOGFONT lf; m_pFont->GetLogFont(&lf); // Циклически меняем шрифт if(CString(lf.lfFaceName) == "Arial" && lf.lfWeight == FW_NORMAL && lf.lfItalic == 0) { this->SetClientFont("Arial", 20, FALSE, TRUE); // Arial Italic } else if(CString(lf.lfFaceName) == "Arial" && lf.lfWeight == FW_NORMAL && lf.lfItalic == 1) { this->SetClientFont("Arial", 20, TRUE, FALSE); // Arial Bold } else if(CString(lf.lfFaceName) == "Arial" && lf.lfWeight > FW_NORMAL) { // Times New Roman this->SetClientFont("Times New Roman", 20, FALSE, FALSE); } else if(CString(lf.lfFaceName) == "Times New Roman" && lf.lfWeight == FW_NORMAL && lf.lfItalic == 0) { // Times New Roman Italic this->SetClientFont("Times New Roman", 20, FALSE, TRUE); } else if(CString(lf.lfFaceName) == "Times New Roman" && lf.lfWeight == FW_NORMAL && lf.lfItalic == 1) { // Times New Roman Bold this->SetClientFont("Times New Roman", 20, TRUE, FALSE); } else if(CString(lf.lfFaceName) == "Times New Roman" && lf.lfWeight > FW_NORMAL) { this->SetClientFont("Arial", 20, FALSE, FALSE); // Arial } // Установим случайный RGB-цвет текста m_textColor = RGB(MulDiv(rand(), 255, RAND_MAX), MulDiv(rand(), 255, RAND_MAX), MulDiv(rand(), 255, RAND_MAX)); // Выведем информацию в окно новым шрифтом // (пошлем сообщение WM_PAINT) this->InvalidateRect(0); } void CMainFrame::SetClientFont(CString Typeface, int Size, BOOL Bold, BOOL Italic) { // Получим контекст окна CWindowDC winDC(this); // Узнаем, сколько пикселей в одном логическом дюйме int pixelsPerInch = winDC.GetDeviceCaps(LOGPIXELSY); // Узнаем высоту в пикселях шрифта размером Size пунктов int fontHeight = -MulDiv(Size, pixelsPerInch, 72); int Weight = FW_NORMAL; if(Bold) Weight = FW_BOLD; // Удаляем предыдущий экземпляр шрифта -- нельзя дважды // инициализировать шрифт вызовом CreateFont(). delete m_pFont; m_pFont = new CFont; m_pFont->CreateFont(fontHeight, 0, 0, 0, Weight, Italic, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH | FF_DONTCARE, Typeface); } afx_msg void CMainFrame::OnPaint() { CPaintDC paintDC(this); COLORREF oldColor = paintDC.SetTextColor(m_textColor); LOGFONT lf; m_pFont->GetLogFont(&lf); paintDC.SelectObject(m_pFont); TEXTMETRIC tm; paintDC.GetTextMetrics(&tm); int currY = 2; CString s = CString("Font: ") + lf.lfFaceName; paintDC.TextOut(2, currY, s); currY += (tm.tmHeight + tm.tmExternalLeading); CSize size = paintDC.GetTextExtent(s); s.Format("Previous string is %i units long", size.cx); paintDC.TextOut(2, currY, s); paintDC.SetTextColor(oldColor); } text_and_fonts.hpp #include "stdafx.h" class CApp: public CWinApp { public: BOOL InitInstance(); }; text_and_fonts.cpp #include "stdafx.h" #include "Text_And_Fonts.hpp" #include "MainFrame.hpp" CApp App; BOOL CApp::InitInstance() { m_pMainWnd = new CMainFrame(); return TRUE; }
Рис. 15. Программа, отображающая в окне стандартные TrueType шрифты
При нажатии левой кнопки мыши в клиентской области программа циклически меняет шрифт из набора всех начертаний двух стандартных TrueType-шрифтов, Arial и Times New Roman. При этом случайным образом устанавливается цвет символов. Выводится длина первой строки в логических единицах. Попробуйте изменить программу так, чтобы вместо TrueType шрифтов использовались растровые шрифты, например, MS Serif и MS Sans Serif. Посмотрите, что произойдет, если задать несуществующий в системе размер шрифта, или несуществующий шрифт.