对不起:已经五年了。文本宽度可能不再是您清醒生活中最关心的问题。但是,我有答案;也许其他人会受益。
准确的 text-width-sizing (这也适用于 text-height)的关键是要认识到渲染文本的宽度当然会发生变化 - 但是是线性的!- 使用 font-size 属性的设置。当一个函数具有线性函数时,不需要二分查找,也不需要挑选和测试所有可能的字体大小属性值;只需确定图表上的两个点。
准备时,不要绘制字符串,而是计算渲染字符串的宽度,例如,文本大小为 20 和文本大小为 40。这为您提供了线性函数“渲染字符串宽度作为函数的两个数据点”文本大小属性”。然后,推断以使字符串适合您当前需要的任何渲染宽度。
我发现这种方法可以均匀地产生良好和快速的结果。当然,随着字体的变化,有时您可能会获得在边界框边缘悬挂两个或三个像素的字符 - 但这是字体设计的产物。精心设计的字体会很好地工作,即使是疯狂的字体,通常也只需要提供几个像素的边界余地。
这是我上个月遇到这个问题时使用的例程。随意使用此代码。
/******************************************************************************************/
//
// text.m
//
/******************************************************************************************/
@interface drawtext : NSObject {
// name of the font to be used
NSString *fontname;
// instantiations of that font, at size 20 and at size 40, and at the currently-best size
NSFont *font20, *font40, *font;
// first sizing function: rendered string height as a function of the font-size attribute
CGFloat mh, bh;
// second sizing function: rendered string width as a function of the font-size attribute
CGFloat mw, bw;
}
@end
/******************************************************************************************/
@implementation drawtext
/******************************************************************************************/
// CLASS METHODS
/******************************************************************************************/
// The caller specifies the text string (all capitals! no descenders!) to be drawn, the
// name of the font to use, the box in which to draw the text, and a border if desired.
//
// The routine returns the fontsize to be used, and the origin to be used for the
// "drawAtPoint" message. This will result in the largest rendition of the text string
// which meets the constraints.
+ (void) sizeText: (NSString *) captext // the string of text to evaluate for font size
usingFontName: (NSString *) fontname // the string name of the font to be employed
inBox: (NSRect) box // the containing box on the screen
withBorder: (NSSize) border // the # of pixels to leave blank as X & Y borders
usingFontSize: (CGFloat *) fontsize // (returned) what font-size to use
atOrigin: (NSPoint *) origin // (returned) where to execute the drawAtPoint
{
// let's start by redefining the containing box to presume the borders
NSRect newBox;
newBox.origin.x = box.origin.x + border.width;
newBox.origin.y = box.origin.y + border.height;
newBox.size.width = box.size.width - 2.0 * border.width;
newBox.size.height = box.size.height - 2.0 * border.height;
// find out dimensions at font size = 20, then at font size = 40, to use for extrapolation
NSSize s20, s40;
NSFont *f20 = [NSFont fontWithName:fontname size:20];
NSMutableAttributedString *mtext20 = [[NSMutableAttributedString alloc] initWithString:captext];
[mtext20 addAttribute:NSFontAttributeName value:f20 range:NSMakeRange(0,[mtext20 length])];
s20.width = mtext20.size.width;
s20.height = f20.capHeight;
NSFont *f40 = [NSFont fontWithName:fontname size:40];
NSMutableAttributedString *mtext40 = [[NSMutableAttributedString alloc] initWithString:captext];
[mtext40 addAttribute:NSFontAttributeName value:f40 range:NSMakeRange(0,[mtext40 length])];
s40.width = mtext40.size.width;
s40.height = f40.capHeight;
// hsize is "font size to cause height of rendered string to match box height"
// wsize is "font size to cause width of rendered string to match box width"
CGFloat x1, x2, y1, y2, m, b, hsize, wsize;
// cap height as function of text size, in y = mx + b format
x1 = 20;
y1 = s20.height;
x2 = 40;
y2 = s40.height;
m = ( y2 - y1 ) / ( x2 - x1 );
b = y1 - ( m * x1 );
hsize = ( newBox.size.height - b ) / m;
// string len as function of text size, y = mx + b format
x1 = 20;
y1 = s20.width;
x2 = 40;
y2 = s40.width;
m = ( y2 - y1 ) / ( x2 - x1 );
b = y1 - ( m * x1 );
wsize = ( newBox.size.width - b ) / m;
// choose the lesser of the two extrapolated font-sizes to fit the string into the box,
// and at the same time, find the origin point at which to render the string
//
// if ( hsize < wsize ) { // there will be east-west spaces
// else { // there will be north-south spaces
*fontsize = fmin( hsize, wsize );
NSSize textSize;
NSMutableAttributedString *mtext = [[NSMutableAttributedString alloc] initWithString:captext];
NSFont *f = [NSFont fontWithName:fontname size:*fontsize];
[mtext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[mtext length])];
textSize.width = mtext.size.width;
textSize.height = f.capHeight;
// don't forget "descender", as this is an all-caps string (strings with descenders are
// left as an extra credit exercise for the reader :)
origin->y = newBox.origin.y + f.descender + ( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
origin->x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );
}
/******************************************************************************************/
// Like the previous routine, except the font size is specified by the caller (this is
// employed in the case it is desired that various text strings, in different containing
// boxes, are to be drawn in the same font size).
+ (void) placeText: (NSString *) captext // the string of text to evaluate for positioning
usingFontName: (NSString *) fontname // the string name of the font to be employed
inBox: (NSRect) box // the containing box on the screen
withBorder: (NSSize) border // the # of pixels to leave blank as X & Y borders
usingFontSize: (CGFloat) fontsize // (passed) what font-size to use
atOrigin: (NSPoint *) origin // (returned) where to execute the drawAtPoint
{
NSRect newBox;
newBox.origin.x = box.origin.x + border.width;
newBox.origin.y = box.origin.y + border.height;
newBox.size.width = box.size.width - 2.0 * border.width;
newBox.size.height = box.size.height - 2.0 * border.height;
NSSize textSize;
NSMutableAttributedString *mtext = [[NSMutableAttributedString alloc] initWithString:captext];
NSFont *f = [NSFont fontWithName:fontname size:fontsize];
[mtext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[mtext length])];
textSize.width = mtext.size.width;
textSize.height = f.capHeight;
// don't forget "descender", as this is an all-caps string
origin->y = newBox.origin.y + f.descender + ( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
origin->x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );
}
/******************************************************************************************/
// This routine actually draws the text (the previous routines only determine how it
// should be drawn).
//
// The second routine can be used to draw a string with attributes such as color (i.e.,
// attributes which don't affect the size of the rendered string).
+ (void) drawText: (NSString *) captext // the string of text to be drawn
usingFontName: (NSString *) fontname // the string name of the font to be employed
andFontSize: (CGFloat) fontsize // what font-size to use
atOrigin: (NSPoint) origin // where to execute the drawAtPoint
{
NSMutableAttributedString *mtext = [[NSMutableAttributedString alloc] initWithString:captext];
NSFont *f = [NSFont fontWithName:fontname size:fontsize];
[mtext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[mtext length])];
[mtext drawAtPoint:origin];
}
+ (void) drawMText: (NSMutableAttributedString *) captext // the string of Mtext to be drawn
usingFontName: (NSString *) fontname // the string name of the font to be employed
andFontSize: (CGFloat) fontsize // what font-size to use
atOrigin: (NSPoint) origin // where to execute the drawAtPoint
{
NSFont *f = [NSFont fontWithName:fontname size:fontsize];
[captext addAttribute:NSFontAttributeName value:f range:NSMakeRange(0,[captext length])];
[captext drawAtPoint:origin];
}
/******************************************************************************************/
// INSTANCE METHODS
/******************************************************************************************/
// When you instantiate the object, you set the font; from this, you can elucidate the
// first of the two sizing functions: rendered string height as a function of the
// font-size attribute. The function is stored in the instance variables of the object,
// in the variables { mh, bh }, to be used with the classic "y(x) = mx + b" format, where:
//
// y is rendered string height
// m is mh
// x is font size attribute
// b is bh
- (id) initUsingFontName: (NSString *) fname // string name of font to be employed
{
if ( !self ) self = [super init];
fontname = [[NSString alloc] initWithString:fname];
font20 = [NSFont fontWithName:fontname size:20];
font40 = [NSFont fontWithName:fontname size:40];
// "cap height as function of text size", in y = mx + b format (mh is m, bh is b)
CGFloat x1, x2, y1, y2;
x1 = 20;
y1 = font20.capHeight;
x2 = 40;
y2 = font40.capHeight;
mh = ( y2 - y1 ) / ( x2 - x1 );
bh = y1 - ( mh * x1 );
return self;
}
/******************************************************************************************/
// After initializing the object, you size a text string; this stores a second sizing
// function: rendered string width as a function of the font-size attribute, in { mw, bw }.
- (void) sizeString: (NSString *) captext // one string of text to evaluate for font size
{
CGFloat x1, x2, y1, y2;
NSMutableAttributedString *mtext =
[[NSMutableAttributedString alloc] initWithString:captext];
[mtext addAttribute:NSFontAttributeName
value:font20
range:NSMakeRange(0,[mtext length])];
x1 = 20;
y1 = mtext.size.width;
[mtext addAttribute:NSFontAttributeName
value:font40
range:NSMakeRange(0,[mtext length])];
x2 = 40;
y2 = mtext.size.width;
// "string width as function of text size", in y = mx + b format (mw is m, bw is b)
mw = ( y2 - y1 ) / ( x2 - x1 );
bw = y1 - ( mw * x1 );
}
/******************************************************************************************/
// Then to draw the text string in a box, you use this routine, which will draw it at the
// largest size possible given all the constraints, including the provided box and border.
//
// A similar routine is provided following this one, to draw a mutable string which may
// contain attributes, such as color, which do not affect the size of the rendered string.
- (void) drawString: (NSString *) captext // string of text to be drawn
inBox: (NSRect) box // containing box on the screen
withBorder: (NSSize) border // # of pixels to leave blank as X & Y borders
{
NSRect newBox;
newBox.origin.x = box.origin.x + border.width;
newBox.origin.y = box.origin.y + border.height;
newBox.size.width = box.size.width - 2.0 * border.width;
newBox.size.height = box.size.height - 2.0 * border.height;
// solve linear sizing functions for text size, and choose the smaller text size
//
// if ( hsize < wsize ) there will be east-west spaces
// if ( wsize < hsize ) there will be north-south spaces
CGFloat hsize, wsize, fontsize;
hsize = ( newBox.size.height - bh ) / mh;
wsize = ( newBox.size.width - bw ) / mw;
fontsize = fmin( hsize, wsize );
font = [NSFont fontWithName:fontname size:fontsize];
NSMutableAttributedString *mtext =
[[NSMutableAttributedString alloc] initWithString:captext];
[mtext addAttribute:NSFontAttributeName value:font range:NSMakeRange(0,[mtext length])];
// find the origin-point at which to render the given string,
// so that the text is centered in the box
NSSize textSize;
textSize.width = mtext.size.width;
textSize.height = font.capHeight;
NSPoint origin;
origin.y = newBox.origin.y + font.descender +
( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
origin.x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );
[mtext drawAtPoint:origin];
}
/******************************************************************************************/
// To draw a mutable text string in a box (a string containing attributes e.g. color, which
// do not affect the sizing of the rendered string), use this routine.
- (void) drawMString: (NSMutableAttributedString *) captext // the M-string to be drawn
inBox: (NSRect) box // containing box on the screen
withBorder: (NSSize) border // # of pixels to leave blank as X & Y borders
{
NSRect newBox;
newBox.origin.x = box.origin.x + border.width;
newBox.origin.y = box.origin.y + border.height;
newBox.size.width = box.size.width - 2.0 * border.width;
newBox.size.height = box.size.height - 2.0 * border.height;
// solve linear sizing functions for text size, and choose the smaller text size
//
// if ( hsize < wsize ) there will be east-west spaces
// if ( wsize < hsize ) there will be north-south spaces
CGFloat hsize, wsize, fontsize;
hsize = ( newBox.size.height - bh ) / mh;
wsize = ( newBox.size.width - bw ) / mw;
fontsize = fmin( hsize, wsize );
font = [NSFont fontWithName:fontname size:fontsize];
[captext addAttribute:NSFontAttributeName
value:font
range:NSMakeRange(0,[captext length])];
// find the origin-point at which to render the given string,
// so that the text is centered in the box
NSSize textSize;
textSize.width = captext.size.width;
textSize.height = font.capHeight;
NSPoint origin;
origin.y = newBox.origin.y + font.descender +
( ( newBox.size.height / 2.0 ) - ( textSize.height / 2.0 ) );
origin.x = ( newBox.origin.x + ( newBox.size.width / 2.0 ) ) - ( textSize.width / 2.0 );
[captext drawAtPoint:origin];
}
/******************************************************************************************/
@end
/******************************************************************************************/