在同一行上从不同字体绘制文本

字体系列中不同的类型样式可以具有不同的宽度。 例如,家庭的粗体和斜体样式始终比指定点大小的罗马样式宽。 在一行中显示或打印多个类型样式时,必须跟踪线条的宽度,以免字符相互显示或打印。

可以使用两个函数来检索当前字体中文本的宽度 (或范围) 。 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.)

通过使用 GetCharWidth32GetCharABCWidths 函数检索字体中各个字符的宽度,可以更精确地放置字符。 GetCharABCWidths 函数比 GetCharWidth32 函数更准确,但它只能与 TrueType 字体一起使用。

ABC 间距还允许应用程序执行非常准确的文本对齐。 例如,当应用程序右对齐光栅罗马字体而不使用 ABC 间距时,前进宽度计算为字符宽度。 这意味着位图中标志符号右侧的空白是对齐的,而不是字形本身。 通过使用 ABC 宽度,应用程序在对齐文本时可以更灵活地放置和删除空格,因为它们具有可精细控制字符间间距的信息。