Как установить размер шрифта в текстовой ячейке, чтобы строка заполняла прямоугольник ячейки? - PullRequest
5 голосов
/ 09 марта 2010

У меня есть представление, которое содержит два NSTextFieldCell s. Размер, в котором нарисованы эти ячейки, определяется размером представления, и я хочу, чтобы текст в каждой ячейке был самым большим, который будет соответствовать производному размеру ячейки. Вот что у меня есть, которое не устанавливает размер шрифта:

- (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) соответствовал известным прямоугольным измерениям, а атрибуты, необходимые для достижения этого, были неизвестны. Как я могу найти нужный размер? Предположим, что я знаю, какой шрифт я буду использовать (потому что я знаю).

обновление Примечание: я не хочу усекать строку, я хочу увеличить или уменьшить так, чтобы все это с максимально возможным размером текста помещается в предоставленный прямоугольник.

Ответы [ 6 ]

5 голосов
/ 10 марта 2010

Я использую некоторый похожий код, но он обрабатывает разные шрифты, размером до 10000 и учитывает доступную высоту, а также ширину области, в которой отображается текст.

#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;
}
2 голосов
/ 09 марта 2010

Было рекомендовано вне диапазона попробовать выполнить бинарный поиск подходящего размера. Это очень ограниченный пример этого:

- (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) не важны для моих нужд, но, безусловно, оттолкнут некоторых людей, использующих этот метод. Я оставлю вопрос открытым и приму более здравый подход.

1 голос
/ 02 сентября 2011

Вот метод, который не угадывает и не проверяет. В зависимости от шрифта, для предотвращения переполнения может потребоваться небольшое заполнение (sizeWithAttributes не масштабируется идеально). Boom!

-(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];
}
0 голосов
/ 18 января 2016

Извините: прошло пять лет. Ширина текста больше не может быть главной проблемой в вашей бодрствующей жизни. Тем не менее, у меня есть ответ; может быть, другие выиграют.

Важнейшим ключом к точному измерению ширины текста (и это работает также для высоты текста) является осознание того, что ширина отображаемого текста, конечно, меняется - но линейно! - с настройкой атрибута font-size. Нет необходимости выполнять бинарный поиск или выбирать и проверять все возможные значения атрибута 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

/******************************************************************************************/
0 голосов
/ 15 сентября 2015

В моем случае я использую следующее:

- (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;


    }




}
0 голосов
/ 09 марта 2010

Две идеи. Один я пробовал, другой мог бы работать:

1) Как в этом вопросе: Как обрезать строку NSS на основе графической ширины? Т.е. просто попробовать разные размеры, пока она больше не будет соответствовать

2) Создайте ячейку, дайте ей максимальный прямоугольник и установите его так, чтобы он поместил ее текст в ячейку, затем спросите его об идеальном размере (там есть метод, который делает это), затем снова измените размер ячейки. Наконец, если я правильно понял вашу проблему.

...