Разметка отдельных глифов с помощью основного текста

В настоящее время я пишу приложение для iOS 6.1 SDK. Я знаю, что некоторые вещи в iOS 7 могут устранить необходимость в решении моего вопроса, но в интересах обучения я все равно спрошу.

Приложение будет состоять из табличного представления и настраиваемых ячеек табличного представления. Я бы хотел, чтобы единственным подвидом содержимого ячейки было настраиваемое представление с NSAttributedString, нарисованным с использованием основного текста. Поскольку строки каждой ячейки будут разными, расположение глифов должно зависеть от количества глифов (т. е. в более длинных строках будет меньше видимого пространства между глифами). Размер шрифта и физические границы должны оставаться прежними, отличаться будет только расположение глифа.

У меня есть следующий код, который по какой-то причине не делает то, что я ожидаю.

Вот .h для BMPTeamNameView — пользовательское представление (подпредставление contentView)

@interface BMPTeamNameView : UIView

-(id)initWithFrame:(CGRect)frame text:(NSString *)text textInset:(UIEdgeInsets)insets font:(UIFont *)font;

@property (nonatomic, copy) NSAttributedString *attributedString;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, assign) UIEdgeInsets insets;

@end

Новый назначенный инициализатор теперь установит фрейм, текст, который будет использоваться для строки с атрибутами, вставки для определения прямоугольника текста по отношению к прямоугольнику contentView и используемый шрифт.

Первоначально в моем пользовательском drawRect: я использовал CTFramesetterRef, однако CTFramesetterRef создаст неизменяемый фрейм, который (может быть?) ограничивал размещение отдельных глифов. В этой реализации я использую CTTypesetterRef для создания CTLineRef. Использование CTFrame приводит к другому поведению рисования при сравнении CTLineDraw() и CTFrameDraw(), но это уже другой вопрос. Мой drawRect: выглядит следующим образом:

- (void)drawRect:(CGRect)rect
{        
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Flips the coordinates so that drawing will be right side up
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);        
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // Path that will hold the textRect
    CGMutablePathRef path = CGPathCreateMutable();

    // rectForTextInsets: returns a rect based on the insets with respect to cell contentView
    self.textRect = [self rectForTextWithInsets:self.insets];

    // Path adding / sets color for drawing
    CGPathAddRect(path, NULL, self.textRect);

    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);

    CGContextAddPath(context, path);

    CGContextFillPath(context);

    // convenience method to return dictionary of attributes for string
    NSDictionary *attributes = [self attributesForAttributedString];

    // convenience method returns "Hello World" with attributes
    // typesetter property is set in the custom setAttributedString:
    self.attributedString = [self attributedStringWithAttributes:attributes];

    CTTypesetterRef typesetter = self.typesetter;

    // Creates the line for the attributed string
    CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, 0));

    CGPoint *positions = NULL;
    CGGlyph *glyphs = NULL;
    CGPoint *positionsBuffer = NULL;
    CGGlyph *glyphsBuffer = NULL;

    // We will only have one glyph run 
    CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
    CTRunRef glyphRun = CFArrayGetValueAtIndex(glyphRuns, 0);

    // Get the count of all the glyphs
    NSUInteger glyphCount = CTRunGetGlyphCount(glyphRun);

    // This function gets the ptr to the glyphs, may return NULL
    glyphs = (CGGlyph *)CTRunGetGlyphsPtr(glyphRun);

    if (glyphs == NULL) {

        // if glyphs is NULL allocate a buffer for them
        // store them in the buffer
        // set the glyphs ptr to the buffer
        size_t glyphsBufferSize = sizeof(CGGlyph) * glyphCount;

        CGGlyph *glyphsBuffer = malloc(glyphsBufferSize);

        CTRunGetGlyphs(glyphRun, CFRangeMake(0, 0), glyphsBuffer);

        glyphs = glyphsBuffer;

    }

    // This function gets the ptr to the positions, may return NULL
    positions = (CGPoint *)CTRunGetPositionsPtr(glyphRun);

    if (positions == NULL) {

        // if positions is NULL allocate a buffer for them
        // store them in the buffer
        // set the positions ptr to the buffer
        size_t positionsBufferSize = sizeof(CGPoint) * glyphCount;

        CGPoint *positionsBuffer = malloc(positionsBufferSize);

        CTRunGetPositions(glyphRun, CFRangeMake(0, 0), positionsBuffer);

        positions = positionsBuffer;
    }

    // Changes each x by 15 and then sets new value at array index
    for (int i = 0; i < glyphCount; i++) {

        NSLog(@"positionAtIndex: %@", NSStringFromCGPoint(positions[i]));
        CGPoint oldPosition = positions[i];
        CGPoint newPosition = CGPointZero;

        NSLog(@"oldPosition = %@", NSStringFromCGPoint(oldPosition));

        newPosition.x = oldPosition.x + 15.0f;
        newPosition.y = oldPosition.y;

        NSLog(@"newPosition = %@", NSStringFromCGPoint(newPosition));

        positions[i] = newPosition;

        NSLog(@"positionAtIndex: %@", NSStringFromCGPoint(positions[i]));
    }

    // When CTLineDraw is commented out this will not display the glyphs on the screen
    CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount);

    // When CGContextShowGlyphsAtPositions is commented out...
    // This will draw the string however it aligns the text to the view's lower left corner
    // CTFrameDraw would draw the text properly in the view's upper left corner
    // This is the difference I was speaking of and I'm not sure why it is
    CTLineDraw(line, context);


    // Make sure to release any CF objects and release allocated buffers
    CFRelease(path);
    free(positionsBuffer);
    free(glyphsBuffer);
}

Я точно не знаю, почему CGContextShowGlyphsAtPositions() неправильно отображает глифы или почему CTLineDraw() не будет использовать новые позиции глифов. Я неправильно обрабатываю распределение этих позиций и глифов? Отладка пещерного человека показывает, что глифы соответствуют ожиданиям, а позиции меняются. Я знаю, что мой код не совсем соответствовал тому, что я искал (я менял положение глифа на 15.0f, а не на основе строки), однако, где я ошибаюсь в расположении этих глифов?


person Brian Palma    schedule 13.09.2013    source источник
comment
Кстати, вы вызываете CTRunGetPositionsPtr, но сами освобождаете указатель, если он не нулевой. Это не разрешено. Просто подсказка для всех, кто копирует и вставляет.   -  person Lothar    schedule 13.10.2019


Ответы (2)


Во-первых, если вы хотите просто уменьшить или уменьшить расстояние между символами, вам не нужен основной текст для этого. Просто прикрепите NSKernAttributeName к той части атрибутированной строки, которую вы хотите настроить. Положительный, чтобы ослабить, отрицательный, чтобы затянуть. (Ноль означает «без кернинга», что отличается от «кернинга по умолчанию». Чтобы получить кернинг по умолчанию, не устанавливайте этот атрибут.) Вы можете использовать size на NSAttributedString, чтобы попробовать разные интервалы, пока не получите нужный размер.

«Позиции» работают не так, как вы думаете. Позиции не являются экранными координатами. Они относятся к текущему источнику текста, который является нижним левым углом текущей строки. (Помните, что координаты CoreText перевернуты относительно координат UIKit.) Вам нужно вызвать CGContextSetTextPosition() перед вызовом CGContextShowGlyphsAtPositions(). CTLineDraw() — это оболочка вокруг CGContextShowGlyphsAtPositions(). Вот почему CTLineDraw() рисуется в левом нижнем углу (0,0). CTFrameDraw рисует в левом верхнем углу, потому что правильно настраивает начало текста.

Когда вы вызываете CGContextShowGlyphsAtPositions() напрямую, похоже, ничего не рисуется. ответ catlan касается этого. Перед рисованием необходимо установить шрифт и цвет контекста.

Ваш код репозиции на самом деле не делает ничего полезного. Он перемещает весь текст на 15 пунктов вправо, но фактически не меняет расстояние между ними. (Если это все, что вам нужно, вы можете просто нарисовать строку на 15 пунктов вправо.)

В вашем текущем коде происходит утечка памяти. Вы выделяете positionsBuffer и glyphsBuffer, но они затеняют ранее объявленные версии. Итак, вы всегда звоните free(NULL) в конце.

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

person Rob Napier    schedule 05.02.2016

CTLineDraw будет использовать информацию о шрифте и цвете из файла CFAttributedString.

CGContextShowGlyphsAtPositions, с другой стороны, нужно, чтобы они были установлены на CGContext:

CGFontRef cgFont = CTFontCopyGraphicsFont(font, NULL);
CGContextSetFont(context, cgFont);
CGContextSetFontSize(context, CTFontGetSize(font));
CGContextSetFillColorWithColor(context, fillColor);

CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount);

CFRelease(cgFont)
person catlan    schedule 05.02.2016
comment
Не забудьте позвонить CFRelease(cgFont). CTFontCopyGraphicsFont включает Копию, поэтому вы несете ответственность за ее выпуск. - person Rob Napier; 05.02.2016
comment
Пример исправленного кода. Сегодня мы уделили этому двухлетнему вопросу некоторую активность :) - person catlan; 05.02.2016