5

我有一个包含两个NSTextFieldCells 的视图。绘制这些单元格的大小来自视图的大小,我希望每个单元格中的文本最大,以适合单元格的派生大小。这是我所拥有的,它没有设置字体大小:

- (void)drawRect:(NSRect)dirtyRect {
    /*
     * Observant readers will notice that I update the whole view here. If
     * there is a perceived performance problem, then I'll switch to just
     * updating the dirty rect.
     */
    NSRect boundsRect = self.bounds;
    const CGFloat monthHeight = 0.25 * boundsRect.size.height;
    NSRect monthRect = NSMakeRect(boundsRect.origin.x,
                                  boundsRect.origin.y + boundsRect.size.height
                                  - monthHeight,
                                  boundsRect.size.width,
                                  monthHeight);
    [monthCell drawWithFrame: monthRect inView: self];

    NSRect dayRect = NSMakeRect(boundsRect.origin.x,
                                boundsRect.origin.y,
                                boundsRect.size.width,
                                boundsRect.size.height - monthHeight);
    [dayCell drawWithFrame: dayRect inView: self];

    [[NSColor blackColor] set];
    [NSBezierPath strokeRect: boundsRect];
}

所以我知道我可以问一个字符串对于给定的属性需要多大的大小,我知道我可以让一个控件改变它的大小以适应它的内容。这些似乎都不适用:我希望内容(在本例中为单元格stringValue)的大小以适合已知的矩形尺寸,而实现这一目标所需的属性是未知的。我怎样才能找到所需的尺寸?假设我知道我将使用什么字体(因为我知道)。

更新注意:我不想截断字符串,我想扩大缩小它,以便整个内容以尽可能大的文本大小适合提供的矩形。

4

6 回答 6

5

我使用了一些类似的代码,但它处理不同的字体,最大大小为 10,000,并考虑了可用高度以及文本显示区域的宽度。

#define kMaxFontSize    10000

- (CGFloat)fontSizeForAreaSize:(NSSize)areaSize withString:(NSString *)stringToSize usingFont:(NSString *)fontName;
{
    NSFont * displayFont = nil;
    NSSize stringSize = NSZeroSize;
    NSMutableDictionary * fontAttributes = [[NSMutableDictionary alloc] init];

    if (areaSize.width == 0.0 || areaSize.height == 0.0) {
        return 0.0;
    }

    NSUInteger fontLoop = 0;
    for (fontLoop = 1; fontLoop <= kMaxFontSize; fontLoop++) {
        displayFont = [[NSFontManager sharedFontManager] convertWeight:YES ofFont:[NSFont fontWithName:fontName size:fontLoop]];
        [fontAttributes setObject:displayFont forKey:NSFontAttributeName];
        stringSize = [stringToSize sizeWithAttributes:fontAttributes];

        if (stringSize.width > areaSize.width)
            break;
        if (stringSize.height > areaSize.height)
            break;
    }

    [fontAttributes release], fontAttributes = nil;

    return (CGFloat)fontLoop - 1.0;
}
于 2010-03-09T21:08:58.723 回答
2

带外建议我尝试对合适的大小进行二分搜索。这是一个非常有限的例子:

- (NSFont *)labelFontForText: (NSString *)text inRect: (NSRect)rect {
    CGFloat prevSize = 0.0, guessSize = 16.0, tempSize;
    NSFont *guessFont = nil;
    while (fabs(guessSize - prevSize) > 0.125) {
        guessFont = [NSFont labelFontOfSize: guessSize];
        NSSize textSize = [text sizeWithAttributes: 
                            [NSDictionary dictionaryWithObject: guessFont
                                                        forKey: NSFontAttributeName]];
        if (textSize.width > rect.size.width || 
            textSize.height > rect.size.height) {
            tempSize = guessSize - (guessSize - prevSize) / 2.0;
        }
        else {
            tempSize = guessSize + (guessSize - prevSize) / 2.0;
        }
        prevSize = guessSize;
        guessSize = tempSize;
    }
    return [[guessFont retain] autorelease];
}

限制(你最好不需要 32pt 或更大的字体,或者任何不是 Lucida Grande 的字体)对我的需要并不重要,但肯定会让一些人不使用这种方法。我将保留这个问题,并接受更强大的方法。

于 2010-03-09T16:59:32.497 回答
1

这是一种不进行猜测和检查的方法。根据字体,可能需要一些填充以防止溢出(sizeWithAttributes 不能完美缩放)。繁荣!

-(float)scaleToAspectFit:(CGSize)source into:(CGSize)into padding:(float)padding
{
    return MIN((into.width-padding) / source.width, (into.height-padding) / source.height);
}

-(NSFont*)fontSizedForAreaSize:(NSSize)size withString:(NSString*)string usingFont:(NSFont*)font;
{
    NSFont* sampleFont = [NSFont fontWithDescriptor:font.fontDescriptor size:12.];//use standard size to prevent error accrual
    CGSize sampleSize = [string sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:sampleFont, NSFontAttributeName, nil]];
    float scale = [self scaleToAspectFit:sampleSize into:size padding:10];
    return [NSFont fontWithDescriptor:font.fontDescriptor size:scale * sampleFont.pointSize];
}
于 2011-09-01T23:39:52.930 回答
0

两个想法。一个我试过了,另一个可能有用:

1)喜欢这个问题:How to truncate an NSString based on the graphics width? 即尝试不同的尺寸,直到它不再适合为止

2)创建单元格,给它最大的矩形并将其设置为适合其文本到单元格中,然后询问它的理想大小(那里有一种方法可以做到这一点)然后再次调整单元格的大小。最后,如果我正确理解了您的问题。

于 2010-03-09T16:03:41.457 回答
0

就我而言,我使用以下内容:

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{

    //Create attributes
    NSColor *text_color = nil;
    NSFont *font = [self font];
    NSString *fontName = [font fontName];
    double fontSize = [font pointSize];

    NSInteger text_size = (int) fontSize;

    if([self isHighlighted])
        text_color = [NSColor colorWithCalibratedRed:1 green:1 blue:1 alpha:1];
    else
        text_color = [NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:1];


    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSFont fontWithName:fontName size:fontSize], NSFontAttributeName,
                                text_color, NSForegroundColorAttributeName,
                                nil];


    NSAttributedString * currentText=[[NSAttributedString alloc] initWithString:[self title] attributes: attributes];

    NSSize attrSize = [currentText size];

    while (attrSize.width > cellFrame.size.width && --text_size > 0) {


        attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                      [NSFont fontWithName:fontName size:text_size], NSFontAttributeName,
                      text_color, NSForegroundColorAttributeName,
                      nil];

        currentText=[[NSAttributedString alloc] initWithString:[self title] attributes: attributes];

        attrSize = [currentText size];

    }

    switch ([self alignment]) {
        default:
        case NSLeftTextAlignment:
            [currentText drawAtPoint:NSMakePoint( cellFrame.origin.x,
                                                 cellFrame.origin.y + (cellFrame.size.height/2) - (attrSize.height/2))];
            break;

        case NSRightTextAlignment:
            [currentText drawAtPoint:NSMakePoint( cellFrame.origin.x + (cellFrame.size.width) - (attrSize.width),
                                                 cellFrame.origin.y + (cellFrame.size.height/2) - (attrSize.height/2))];
            break;

        case NSCenterTextAlignment:
            [currentText drawAtPoint:NSMakePoint( cellFrame.origin.x + (cellFrame.size.width /2) - (attrSize.width/2),
                                                 cellFrame.origin.y + (cellFrame.size.height/2) - (attrSize.height/2))];
            break;


    }




}
于 2015-09-15T16:16:48.033 回答
0

对不起:已经五年了。文本宽度可能不再是您清醒生活中最关心的问题。但是,我有答案;也许其他人会受益。

准确的 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

/******************************************************************************************/
于 2016-01-18T04:21:32.090 回答