Core Textgithub.com/anyvoid/CoreTextDemo
Константин КиселёвВедущий разработчик
Содержание
๏Основы Core Text
• Маркируем области в тексте
• Два столбца• Обтекание текстом
3
Рецепты Юлии Высоцкой
4
Рецепты Юлии Высоцкой
При нажатии на ссылку со временем открывается установка таймера
5
Рецепты Юлии Высоцкой
6
Бар Депозит – Управление Баром
Во время набора имени в поиске участки совпадения
подсвечиваются в выпадающем меню
7
Рендеринг текста
UIKit
Core Text
Quartz Core
8
UIKit
• Ничего сложного
• UIFont
• UILabel, UITextView, UIWebView
• Минимальные возможности настройки отображения
• в iOS 6 можно задавать NSAttributedString !
9
Рендеринг текста
UIKit
Core Text
Quartz Core
10
Quartz Core
• Непосредственная отрисовка глифов• CGFontRef
• Фиксированная информация о шрифте
• Использование — корректировка отображения
11
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename( [fontPath UTF8String] );
CGFontRef font = CGFontCreateWithDataProvider( fontDataProvider );
CGGlyph glyphs[glyphCount];
for ( size_t i = 0; i < glyphCount; ++i ) { ... glyphs[i] = CGFontGetGlyphWithGlyphName( font, glyphName ); ... }
CGContextSetFont( context, font ); CGContextSetFontSize( context, fontSize );
CGContextShowGlyphsAtPoint( context, p.x, p.y, glyphs, glyphCount );
Quartz Core
12
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename( [fontPath UTF8String] );
CGFontRef font = CGFontCreateWithDataProvider( fontDataProvider );
CGGlyph glyphs[glyphCount];
for ( size_t i = 0; i < glyphCount; ++i ) { ... glyphs[i] = CGFontGetGlyphWithGlyphName( font, glyphName ); ... }
CGContextSetFont( context, font ); CGContextSetFontSize( context, fontSize );
CGContextShowGlyphsAtPoint( context, p.x, p.y, glyphs, glyphCount );
Quartz Core
13
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename( [fontPath UTF8String] );
CGFontRef font = CGFontCreateWithDataProvider( fontDataProvider );
CGGlyph glyphs[glyphCount];
for ( size_t i = 0; i < glyphCount; ++i ) { ... glyphs[i] = CGFontGetGlyphWithGlyphName( font, glyphName ); ... }
CGContextSetFont( context, font ); CGContextSetFontSize( context, fontSize );
CGContextShowGlyphsAtPoint( context, p.x, p.y, glyphs, glyphCount );
Quartz Core
14
Рендеринг текста
UIKit
Core Text
Quartz Core
15
Core Text
• Оптимальное сложность/возможности• CTFontRef
• CTFrame, CTLine, CTRun, CTGlyph
• Можно использовать в нескольких потоках, но с ограничениями. . .
16
Core Text
• Все функции
• Объекты, не задающие размещение текста:
- CTFont
- CTFontDescriptor
- ...
• Объекты, задающие размещение:
- CTFrameSetter
- CTTypeSetter
- CTFrame
- CTRun
- CTLine
- ...
Потокобезопасно Непотокобезопасно
17
Core Text
СFAttributedString CTFrameSetter
CTTypeSetter CTFrame
CGPath CTLine
CTLine
CTRun
CTRun
CTGlyph CTGlyph
18
Core Text
• CTFrame – область вывода всего текста
• CTLine – одна строка
• CTRun – последовательность символов с одинаковыми атрибутами
• CTGlyph – один символ шрифта
19
Core Text
CTFrame
CTLine
This is CTRun Next CTRun The End.
20
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext();
// Делаем текущую матрицу единичной CGContextSetTextMatrix( context, CGAffineTransformIdentity ); // Добавляем к матрице перенос по вертикали CGContextTranslateCTM( context, 0, rect.size.height ); // Добавляем отражение по вертикали CGContextScaleCTM( context, 1, -1 ); // ...
Core Text
• Отраженная и сдвинутая по вертикали система координат
21
CGFloat width = rect.size.width, height = rect.size.height; CGFloat rect = CGRectMake(0, 0, width, height);
CGMutablePathRef mutablePath = CGPathCreateMutable(); CGPathAddRect( mutablePath, NULL, rect ); CTFrameRef frame = CTFramesetterCreateFrame(
frameSetter, frameRange, mutablePath, NULL );
• Создаем CTFrameSetter из CFAttributedString
• Далее получаем CTFrame, используя CGPath
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString( attrStr );
Core Text
22
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString( attrStr );
CGFloat width = rect.size.width, height = rect.size.height; CGFloat rect = CGRectMake(0, 0, width, height);
CGMutablePathRef mutablePath = CGPathCreateMutable(); CGPathAddRect( mutablePath, NULL, rect ); CTFrameRef frame = CTFramesetterCreateFrame(
frameSetter, frameRange, mutablePath, NULL );
• Создаем CTFrameSetter из CFAttributedString
• Далее получаем CTFrame, используя CGPath
Core Text
23
• Полученный CTFrame является моделью, по которой отрисовывается текст в графическом контексте после вызова
• Аналогичные функции для отрисовки отдельных CTLine, CTRun
Core Text
CTFrameDraw( frame, context );
CGContextSetTextPosition(context, p.x, p.y); CTLineDraw(line, context); CTRunDraw(run, context, CFRangeMake(0, 0));
24
• Полученный CTFrame является моделью, по которой отрисовывается текст в графическом контексте после вызова
• Аналогичные функции для отрисовки отдельных CTLine, CTRun
CTFrameDraw( frame, context );
Core Text
CGContextSetTextPosition(context, p.x, p.y); CTLineDraw(line, context); CTRunDraw(run, context, CFRangeMake(0, 0));
25
CTFrameDraw( frame, context );
• Полученный CTFrame является моделью, по которой отрисовывается текст в графическом контексте после вызова
• Аналогичные функции для отрисовки отдельных CTLine, CTRun
Core Text
CGContextSetTextPosition(context, p.x, p.y); CTLineDraw(line, context); CTRunDraw(run, context, CFRangeMake(0, 0));
26
• Проход по всем участкам фрейма: NSArray* lines = (__bridge NSArray *)CTFrameGetLines( frame ); for ( int lineIndex = 0; lineIndex < length; ++lineIndex ) { CTLineRef line =
(__bridge CTLineRef)[lines objectAtIndex: lineIndex];
NSArray* runs = (__bridge NSArray *)CTLineGetGlyphRuns( line );
for ( int runIndex = 0; runIndex < runs.count; ++runIndex ) { CTRunRef run =
(__bridge CTRunRef)[runs objectAtIndex: runIndex]; // ... } }
Core Text
27
• Проход по всем участкам фрейма: NSArray* lines = (__bridge NSArray *)CTFrameGetLines( frame ); for ( int lineIndex = 0; lineIndex < length; ++lineIndex ) { CTLineRef line =
(__bridge CTLineRef)[lines objectAtIndex: lineIndex];
NSArray* runs = (__bridge NSArray *)CTLineGetGlyphRuns( line );
for ( int runIndex = 0; runIndex < runs.count; ++runIndex ) { CTRunRef run =
(__bridge CTRunRef)[runs objectAtIndex: runIndex]; // ... } }
Core Text
28
• Проход по всем участкам фрейма: NSArray* lines = (__bridge NSArray *)CTFrameGetLines( frame ); for ( int lineIndex = 0; lineIndex < length; ++lineIndex ) { CTLineRef line =
(__bridge CTLineRef)[lines objectAtIndex: lineIndex];
NSArray* runs = (__bridge NSArray *)CTLineGetGlyphRuns( line );
for ( int runIndex = 0; runIndex < runs.count; ++runIndex ) { CTRunRef run =
(__bridge CTRunRef)[runs objectAtIndex: runIndex]; // ... } }
Core Text
29
• Проход по всем участкам фрейма: NSArray* lines = (__bridge NSArray *)CTFrameGetLines( frame ); for ( int lineIndex = 0; lineIndex < length; ++lineIndex ) { CTLineRef line =
(__bridge CTLineRef)[lines objectAtIndex: lineIndex];
NSArray* runs = (__bridge NSArray *)CTLineGetGlyphRuns( line );
for ( int runIndex = 0; runIndex < runs.count; ++runIndex ) { CTRunRef run =
(__bridge CTRunRef)[runs objectAtIndex: runIndex]; // ... } }
Core Text
30
Core Texty
x
При определенных
условиях вещество
трансформирует межядерный
фонон
origin
origin + ascent
origin – descent
31
• Получаем origin-ы для строк:
• Получаем frame строки:
• Получаем frame для CTRun:
lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
lineHeight = ascent + descent + leading;
runWidth = CTRunGetTypographicBounds( run, CFRangeMake(0, 0),&runAscent, &runDescent, &runLeading );
topY = lineOrigins[lineIndex].y + ascent;leftX = CTLineGetOffsetForStringIndex(line, runRange.location, NULL);runHeight = runAscent + runDescent + runLeading;
CGPoint lineOrigins[lines.count];CTFrameGetLineOrigins(
frame, CFRangeMake(0, lines.count), lineOrigins);
Core Text
32
• Получаем origin-ы для строк:
• Получаем frame строки:
• Получаем frame для CTRun:
lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
lineHeight = ascent + descent + leading;
runWidth = CTRunGetTypographicBounds( run, CFRangeMake(0, 0),&runAscent, &runDescent, &runLeading );
topY = lineOrigins[lineIndex].y + ascent;leftX = CTLineGetOffsetForStringIndex(line, runRange.location, NULL);runHeight = runAscent + runDescent + runLeading;
CGPoint lineOrigins[lines.count];CTFrameGetLineOrigins(
frame, CFRangeMake(0, lines.count), lineOrigins);
Core Text
33
• Получаем origin-ы для строк:
• Получаем frame строки:
• Получаем frame для CTRun:
lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
lineHeight = ascent + descent + leading;
runWidth = CTRunGetTypographicBounds( run, CFRangeMake(0, 0),&runAscent, &runDescent, &runLeading );
topY = lineOrigins[lineIndex].y + ascent;leftX = CTLineGetOffsetForStringIndex(line, runRange.location, NULL);runHeight = runAscent + runDescent + runLeading;
CGPoint lineOrigins[lines.count];CTFrameGetLineOrigins(
frame, CFRangeMake(0, lines.count), lineOrigins);
Core Text
34
runWidth = CTRunGetTypographicBounds( run, CFRangeMake(0, 0),&runAscent, &runDescent, &runLeading );
topY = lineOrigins[lineIndex].y + ascent;leftX = CTLineGetOffsetForStringIndex(line, runRange.location, NULL);runHeight = runAscent + runDescent + runLeading;
• Получаем origin-ы для строк:
• Получаем frame строки:
• Получаем frame для CTRun:
lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
lineHeight = ascent + descent + leading;
CGPoint lineOrigins[lines.count];CTFrameGetLineOrigins(
frame, CFRangeMake(0, lines.count), lineOrigins);
Core Text
35
• Получаем origin-ы для строк:
• Получаем frame строки:
• Получаем frame для CTRun:
lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
lineHeight = ascent + descent + leading;
runWidth = CTRunGetTypographicBounds( run, CFRangeMake(0, 0),&runAscent, &runDescent, &runLeading );
topY = lineOrigins[lineIndex].y + ascent;leftX = CTLineGetOffsetForStringIndex(line, runRange.location, NULL);runHeight = runAscent + runDescent + runLeading;
CGPoint lineOrigins[lines.count];CTFrameGetLineOrigins(
frame, CFRangeMake(0, lines.count), lineOrigins);
Core Text
36
Core Text
• Полезные свойства параграфов:• межстрочный интервал
• выравнивание по горизонтали
• line break mode
• отступ красной строки
• . . .37
Core Text
• CTParagraphStyleSetting
• CTParagraphStyleRef
CTParagraphStyleSetting parSet[] = { {kCTParagraphStyleSpecifierAlignment, sizeof(value), &value}, {kCTParagraphStyleSpecifierLineSpacing, ...}, {kCTParagraphStyleSpecifierLineBreakMode, ...} ...};
NSInteger optsNum = sizeof(parSet) / sizeof(CTParagraphStyleSetting);
CTParagraphStyleRef parStyle = CTParagraphStyleCreate( parSet, optsNum);
38
Core Text
• Установка свойств параграфов доступна:- iOS < 6: ТОЛЬКО при инициализации
NSAttributedString / NSMutableAttributedString
- iOS 6: можно делать addAttribute у NSMutableAttributedString
39
• Неприятности:
- выделения пишем сами
- анализ текста (html, rtf и чего угодно своего, например, с разбивкой на параграфы) тоже пишем сами
Core Text
40
• Приятности:
✓ огромные возможности по кастомизации вывода текста
✓ очень удобно и быстро, когда нужно использовать UILabel, но цветной или с маркированными участками
✓ Достаточно быстр
Core Text
41
• Тест на скорость:‣ UITableView, 5000 ячеек
‣ В каждой ячейке label с кастомным шрифтом MetaPro-Normal, цвет текста с прозрачностью
‣ iPhone 4, Xcode Instruments -> CA Instrument
Core Text
42
• Тест на скорость:‣ UITableView, 5000 ячеек
‣ В каждой ячейке label с кастомным шрифтом MetaPro-Normal, цвет текста с прозрачностью
‣ iPhone 4, Xcode Instruments -> CA Instrument
Core Text
~ 55–56 fpsUILabel
~ 60 fpsCore Text
43
• Тест на скорость:‣ UITableView, 5000 ячеек
‣ В каждой ячейке label с кастомным шрифтом MetaPro-Normal, цвет текста с прозрачностью
‣ iPhone 4, Xcode Instruments -> CA Instrument
Core Text
~ 55–56 fpsUILabel
~ 60 fpsCore TextCore Text точно не
медленнее44
Содержание
• Основы Core Text
๏Маркируем области в тексте
• Два столбца• Обтекание текстом
45
Маркировка
• Результаты поиска
• Кастомные ссылки в UILabel
➡И все это без UIWebView !
http://anyvoid.ru
46
Маркировка
47
Маркировка
• Создаём наследник UIView:
- selectWithPattern:(NSString *)pattern - метод, который будет создавать модель для маркировки указанного текста
- drawRect:(CGRect)rect - здесь будет непосредственная отрисовка текста с маркировкой
48
Маркировка
[regexp enumerateMatchesInString: ... usingBlock:^(NSTextCheckingResult *result, ...) { // При совпадении куска текста с шаблоном:
// 1. Добавляем участок в список совпадений [ranges addObject: [NSValue valueWithRange: [result range]]];
// 2. Помечаем участок цветом в отдельной атрибутированной строке [attrTextMutable addAttribute: kCTForegroundColorAttributeName value: [UIColor redColor].CGColor range: [result range]]; }];
• Добавляем какой-нибудь атрибут к участкам, которые хотим выделить и сохраняем их в в self.ranges
49
Маркировка
[regexp enumerateMatchesInString: ... usingBlock:^(NSTextCheckingResult *result, ...) { // При совпадении куска текста с шаблоном:
// 1. Добавляем участок в список совпадений [ranges addObject: [NSValue valueWithRange: [result range]]];
// 2. Помечаем участок цветом в отдельной атрибутированной строке [attrTextMutable addAttribute: kCTForegroundColorAttributeName value: [UIColor redColor].CGColor range: [result range]]; }];
• Добавляем какой-нибудь атрибут к участкам, которые хотим выделить, и сохраняем их в в self.ranges
50
Маркировка
• В drawRect проходим по всем CTRun каждой строки и получаем их frame, если они входят в сохраненные участки
➡ При проверке подходит ли run обращаем внимание на то, что конец строки может разорвать CTRun => нужно проверять включение, а не совпадение
51
Маркировка
CFRange runCFRange = CTRunGetStringRange( run );
NSValue* parentRangeValue = [self parentForRange: runRange];if ( parentRangeValue ) {
// CTRun подходит, можно получить его frame и использовать это...
}
• Получаем frame рана с помощью CTRunGetTypographicBounds
• Осталось только что-нибудь добавить для выделения. . . .
• Маркированным участкам теперь соответствуют отдельные CTRun
52
Маркировка• Добавим кнопку, frame которой будет выглядеть следующим образом:
CGRect buttonFrame = CGRectMake( leftX - leftPadding, topY - leftPadding, runWidth + leftPadding + rightPadding, runHeight + bottomPadding + topPadding );
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, rect.size.height);
transform = CGAffineTransformScale(1, -1);buttonFrame = CGRectApplyAffineTransform(transform, buttonFrame);
• Не забываем, что мы работали в преобразованной системе координат:
53
МаркировкаМожно использовать нажатие кнопки для чего-нибудь
54
Содержание
• Основы Core Text
• Маркируем области в тексте
๏Два столбца
• Обтекание текстом
55
• Два столбца делаются просто - выводим текст в нескольких CTFrame
- задаем CGPath для границ столбца
- чтобы определить, сколько текста вывели в предыдущем столбце, используем:
CFRange prevColTextRange = CTFrameGetVisibleStringRange(colFrames[columnIdx - 1]);
Два столбца
CGRect colRect = CGRectMake(originX, originY, colWidth, colHeight);CGPathAddRect(rectPath, NULL, colRect);
56
• Два столбца делаются просто - выводим текст в нескольких CTFrame
- задаем CGPath для границ столбца
- чтобы определить, сколько текста вывели в предыдущем столбце, используем:
CFRange prevColTextRange = CTFrameGetVisibleStringRange(colFrames[columnIdx - 1]);
Два столбца
CGRect colRect = CGRectMake(originX, originY, colWidth, colHeight);CGPathAddRect(rectPath, NULL, colRect);
57
Содержание
• Основы Core Text
• Выделяем области в тексте
• Два столбца๏Обтекание текстом
58
Обтекание• Чтобы текст обтекал shape, нужно вырезать последний из основного фрейма
- Не нужно пытаться делать это через Core Graphics
- В Core Text есть специальные опции, которые задают вырезаемые области
59
• Каждый вырезаемый CGPath задается через CFDictionaryRef
Обтекание
- (CFDictionaryRef)createClippingPathWithPath:(CGPathRef)pathRef { NSDictionary* ret = [NSDictionary dictionaryWithObject: (__bridge id)(pathRef) forKey: kCTFramePathClippingPathAttributeName]; return (__bridge CFDictionaryRef)(ret);}
60
• Затем массив из CDictionaryRef ставится значением опции и передается в CTFrameSetterCreateFrame
frameOptionsDict = [NSDictionary dictionaryWithObject: clippingPaths
forKey: kCTFrameClippingPathsAttributeName];
Обтекание
CTFramesetterCreateFrame(..., (__bridge CFDictionaryRef)frameOptionsDict);
61
Apple Font Tool Suitehttps://developer.apple.com/fonts/
ftxdumperfuser -t hhea -A d Font.otf
ftxdumperfuser -t hhea -A f Font.otf
ascender="716"descender="-212"lineGap="219"
ascender="975"descender="-217"lineGap="29"
Custom font Helvetica Neue
62
Кофе-брейк
Top Related