在同一行上从不同字体绘制文本
字体系列中不同的类型样式可以具有不同的宽度。 例如,家庭的粗体和斜体样式始终比指定点大小的罗马样式宽。 在一行中显示或打印多个类型样式时,必须跟踪线条的宽度,以免字符相互显示或打印。
可以使用两个函数来检索当前字体中文本的宽度 (或范围) 。 GetTabbedTextExtent 函数计算字符串的宽度和高度。 如果字符串包含一个或多个制表符,则字符串的宽度基于指定的制表位位置数组。 GetTextExtentPoint32 函数计算文本行的宽度和高度。
必要时,系统通过更改字符位图来合成字体。 若要以粗体字体合成字符,系统会在起始点绘制字符两次,在起始点右侧再绘制一个像素。 为了以斜体字体合成字符,系统会在字符单元格底部绘制两行像素,向右移动起始点一个像素,绘制下两行,然后继续直到绘制字符。 通过移动像素,每个字符似乎都向右剪切。 剪切量是字符高度的函数。
编写包含多个字体的文本行的一种方法是在每次调用 TextOut 后使用 GetTextExtentPoint32 函数,并将长度添加到当前位置。 以下示例编写行“This is a sample string.”,使用粗体字符表示“This is a”,切换为“sample”的斜体字符,然后返回“string”的粗体字符。打印所有字符串后,它会还原系统默认字符。
int XIncrement;
int YStart;
TEXTMETRIC tm;
HFONT hfntDefault, hfntItalic, hfntBold;
SIZE sz;
LPSTR lpszString1 = "This is a ";
LPSTR lpszString2 = "sample ";
LPSTR lpszString3 = "string.";
HRESULT hr;
size_t * pcch;
// Create a bold and an italic logical font.
hfntItalic = MyCreateFont();
hfntBold = MyCreateFont();
// Select the bold font and draw the first string
// beginning at the specified point (XIncrement, YStart).
XIncrement = 10;
YStart = 50;
hfntDefault = SelectObject(hdc, hfntBold);
hr = StringCchLength(lpszString1, 11, pcch);
if (FAILED(hr))
{
// TODO: write error handler
}
TextOut(hdc, XIncrement, YStart, lpszString1, *pcch);
// Compute the length of the first string and add
// this value to the x-increment that is used for the
// text-output operation.
hr = StringCchLength(lpszString1, 11, pcch);
if (FAILED(hr))
{
// TODO: write error handler
}
GetTextExtentPoint32(hdc, lpszString1, *pcch, &sz);
XIncrement += sz.cx;
// Retrieve the overhang value from the TEXTMETRIC
// structure and subtract it from the x-increment.
// (This is only necessary for non-TrueType raster
// fonts.)
GetTextMetrics(hdc, &tm);
XIncrement -= tm.tmOverhang;
// Select an italic font and draw the second string
// beginning at the point (XIncrement, YStart).
hfntBold = SelectObject(hdc, hfntItalic);
GetTextMetrics(hdc, &tm);
XIncrement -= tm.tmOverhang;
hr = StringCchLength(lpszString2, 8, pcch);
if (FAILED(hr))
{
// TODO: write error handler
}
TextOut(hdc, XIncrement, YStart, lpszString2, *pcch);
// Compute the length of the second string and add
// this value to the x-increment that is used for the
// text-output operation.
hr = StringCchLength(lpszString2, 8, pcch);
if (FAILED(hr))
{
// TODO: write error handler
}
GetTextExtentPoint32(hdc, lpszString2, *pcch, &sz);
XIncrement += sz.cx;
// Reselect the bold font and draw the third string
// beginning at the point (XIncrement, YStart).
SelectObject(hdc, hfntBold);
hr = StringCchLength(lpszString3, 8, pcch);
if (FAILED(hr))
{
// TODO: write error handler
}
TextOut(hdc, XIncrement - tm.tmOverhang, YStart, lpszString3,
*pcch);
// Reselect the original font.
SelectObject(hdc, hfntDefault);
// Delete the bold and italic fonts.
DeleteObject(hfntItalic);
DeleteObject(hfntBold);
在此示例中, GetTextExtentPoint32 函数使用指定字符串的长度和高度初始化 SIZE 结构的成员。 GetTextMetrics 函数检索当前字体的悬垂。 由于如果字体为 TrueType 字体,则悬停为零,因此悬停值不会更改字符串位置。 但是,对于光栅字体,请务必使用悬垂值。
如果字体是光栅字体,则从粗体字符串中减去一次悬垂,以使后续字符更接近字符串末尾。 由于悬垂会影响光栅字体中斜体字符串的开头和结尾,因此字形从指定位置的右侧开始,在最后一个字符单元格的端点左侧结束。 (GetTextExtentPoint32 函数检索字符单元格的范围,而不是 glyphs.) 为了解释光栅斜体字符串中的悬垂,示例在放置字符串之前减去悬垂,并在放置后续字符之前再次减去它。
SetTextJustification 函数为文本行中的分隔符添加额外的空间。 可以使用 GetTextExtentPoint 函数来确定字符串的范围,然后从行应占用的总空间量中减去该范围,并使用 SetTextJustification 函数在字符串中的分隔符之间分配额外的空格。 SetTextCharacterExtra 函数为所选字体中的每个字符单元格添加额外的空间,包括分隔符。 (可以使用 GetTextCharacterExtra 函数来确定当前要添加到字符单元格的额外空间量;默认设置为 zero.)
通过使用 GetCharWidth32 或 GetCharABCWidths 函数检索字体中各个字符的宽度,可以更精确地放置字符。 GetCharABCWidths 函数比 GetCharWidth32 函数更准确,但它只能与 TrueType 字体一起使用。
ABC 间距还允许应用程序执行非常准确的文本对齐。 例如,当应用程序右对齐光栅罗马字体而不使用 ABC 间距时,前进宽度计算为字符宽度。 这意味着位图中标志符号右侧的空白是对齐的,而不是字形本身。 通过使用 ABC 宽度,应用程序在对齐文本时可以更灵活地放置和删除空格,因为它们具有可精细控制字符间间距的信息。