Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Side by Side Diff: ios/chrome/browser/ui/util/manual_text_framer.mm

Issue 2580333003: Upstream Chrome on iOS source code [10/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/util/manual_text_framer.h"
6
7 #import <UIKit/UIKit.h>
8
9 #include "base/i18n/rtl.h"
10 #include "base/logging.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/scoped_nsobject.h"
13 #import "ios/chrome/browser/ui/util/core_text_util.h"
14 #import "ios/chrome/browser/ui/util/text_frame.h"
15 #import "ios/chrome/browser/ui/util/unicode_util.h"
16
17 // NOTE: When RTL text is laid out into glyph runs, the glyphs appear in the
18 // visual order in which they appear on screen. In other words, the glyphs are
19 // arranged in the reverse order of their corresponding characters in the
20 // original string.
21
22 namespace {
23 // Aligns |value| to the nearest pixel value, rounding by the function indicated
24 // by |function|. AlignmentFunction::CEIL should be used to align size values,
25 // while AlignmentFunction::FLOOR should be used to align location values.
26 enum class AlignmentFunction : short { CEIL = 0, FLOOR };
27 CGFloat AlignValueToPixel(CGFloat value, AlignmentFunction function) {
28 static CGFloat scale = [[UIScreen mainScreen] scale];
29 return function == AlignmentFunction::CEIL ? ceil(value * scale) / scale
30 : floor(value * scale) / scale;
31 }
32
33 // Returns an NSArray of NSAttributedStrings corresponding to newline-separated
34 // paragraphs within |string|.
35 NSArray* GetParagraphStringsForString(NSAttributedString* string) {
36 NSMutableArray* paragraph_strings = [NSMutableArray array];
37 NSCharacterSet* newline_char_set = [NSCharacterSet newlineCharacterSet];
38 NSUInteger string_length = string.string.length;
39 NSRange remaining_range = NSMakeRange(0, string_length);
40 while (remaining_range.location < string_length) {
41 NSRange newline_range =
42 [string.string rangeOfCharacterFromSet:newline_char_set
43 options:0
44 range:remaining_range];
45 NSRange paragraph_range = NSMakeRange(0, 0);
46 if (newline_range.location == NSNotFound) {
47 // There's no newline in the remaining portion of the string.
48 paragraph_range = remaining_range;
49 remaining_range = NSMakeRange(string_length, 0);
50 } else {
51 // A newline character was encountered. Compute approximate text lines
52 // for the substring within |remaining_range| up to the newline.
53 NSUInteger newline_end = newline_range.location + newline_range.length;
54 paragraph_range = NSMakeRange(remaining_range.location,
55 newline_end - remaining_range.location);
56 remaining_range.location = newline_end;
57 remaining_range.length = string_length - remaining_range.location;
58 }
59 // Create an attributed substring for the current paragraph and add it to
60 // |paragraphs|.
61 [paragraph_strings
62 addObject:[string attributedSubstringFromRange:paragraph_range]];
63 }
64 return paragraph_strings;
65 }
66 } // namespace
67
68 #pragma mark - ManualTextFrame
69
70 // A TextFrame implementation that is manually created by ManualTextFramer.
71 @interface ManualTextFrame : NSObject<TextFrame> {
72 // Backing objects for properties of the same name.
73 base::scoped_nsobject<NSAttributedString> _string;
74 base::scoped_nsobject<NSMutableArray> _lines;
75 }
76
77 // Designated initializer.
78 - (instancetype)initWithString:(NSAttributedString*)string
79 inBounds:(CGRect)bounds NS_DESIGNATED_INITIALIZER;
80 - (instancetype)init NS_UNAVAILABLE;
81
82 // Creates a FramedLine out of |line|, |stringRange|, and |origin|, then adds it
83 // to |lines|.
84 - (void)addFramedLineWithLine:(CTLineRef)line
85 stringRange:(NSRange)stringRange
86 origin:(CGPoint)origin;
87
88 // Redefine property as readwrite.
89 @property(nonatomic, readwrite) NSRange framedRange;
90
91 @end
92
93 @implementation ManualTextFrame
94
95 @synthesize framedRange = _framedRange;
96 @synthesize bounds = _bounds;
97
98 - (instancetype)initWithString:(NSAttributedString*)string
99 inBounds:(CGRect)bounds {
100 if ((self = [super init])) {
101 DCHECK(string.string.length);
102 _string.reset([string retain]);
103 _bounds = bounds;
104 _lines.reset([[NSMutableArray alloc] init]);
105 }
106 return self;
107 }
108
109 #pragma mark Accessors
110
111 - (NSAttributedString*)string {
112 return _string.get();
113 }
114
115 - (NSArray*)lines {
116 return _lines.get();
117 }
118
119 #pragma mark Private
120
121 - (void)addFramedLineWithLine:(CTLineRef)line
122 stringRange:(NSRange)stringRange
123 origin:(CGPoint)origin {
124 base::scoped_nsobject<FramedLine> framedLine([[FramedLine alloc]
125 initWithLine:line
126 stringRange:stringRange
127 origin:origin]);
128 [_lines addObject:framedLine];
129 }
130
131 @end
132
133 #pragma mark - ManualTextFramer Private Interface
134
135 @interface ManualTextFramer () {
136 // Backing objects for properties of the same name.
137 base::scoped_nsobject<NSAttributedString> _string;
138 base::scoped_nsobject<ManualTextFrame> _manualTextFrame;
139 }
140
141 // The string passed upon initialization.
142 @property(nonatomic, readonly) NSAttributedString* string;
143
144 // The bounds passed upon initialization.
145 @property(nonatomic, readonly) CGRect bounds;
146
147 // The width of the bounds passed upon initialization.
148 @property(nonatomic, readonly) CGFloat boundingWidth;
149
150 // The remaining height into which text can be framed.
151 @property(nonatomic, assign) CGFloat remainingHeight;
152
153 // The text frame constructed by |-frameText|.
154 @property(nonatomic, readonly) ManualTextFrame* manualTextFrame;
155
156 // Creates a ManualTextFrame and assigns it to |_manualTextFrame|. Returns YES
157 // if a new text frame was successfully created.
158 - (BOOL)setupManualTextFrame;
159
160 @end
161
162 #pragma mark - ParagraphFramer
163
164 // ManualTextFramer subclass that frames a single paragraph. A paragraph is
165 // defined as an NSAttributedString which contains either zero newlines or one
166 // newline as its last character.
167 @interface ParagraphFramer : ManualTextFramer {
168 // Backing objects for properties of the same name.
169 base::ScopedCFTypeRef<CTLineRef> _line;
170 base::scoped_nsobject<NSCharacterSet> _lineEndSet;
171 }
172
173 // The CTLine created from |string|.
174 @property(nonatomic, readonly) CTLineRef line;
175
176 // The effective text alignment for |line|.
177 @property(nonatomic, readonly) NSTextAlignment effectiveAlignment;
178
179 // Character set containing characters that are appropriate for line endings.
180 // These characters include whitespaces and newlines (denoting a word boundary),
181 // in addition to line-ending characters like hyphens, em dashes, and en dashes.
182 @property(nonatomic, readonly) NSCharacterSet* lineEndSet;
183
184 // The index of the current run that is being framed. Setting |runIdx| also
185 // updates |currentRun| and |currentGlyphCount|.
186 @property(nonatomic, assign) CFIndex runIdx;
187
188 // The CTRun corresponding with |runIdx| in |line|.
189 @property(nonatomic, readonly) CTRunRef currentRun;
190
191 // The glyph count in |currentRun|.
192 @property(nonatomic, readonly) CFIndex currentGlyphCount;
193
194 // The number of glyphs in |currentRun| that have been successfully framed.
195 @property(nonatomic, assign) CFIndex framedGlyphCount;
196
197 // The range in |string| that has successfully been framed for the current line.
198 @property(nonatomic, assign) NSRange currentLineRange;
199
200 // The width of the typographic bounds for the glyphs framed on the current
201 // line. This is the width of the substring of |string| corresponding to
202 // |currentLineRange|.
203 @property(nonatomic, assign) CGFloat currentLineWidth;
204
205 // The width of the trailing whitespace for the current line. This whitespace
206 // is not counted against the line width if it's the end of the line, but needs
207 // to be added in if non-whitespace characters from subsequent runs fit on the
208 // same line.
209 @property(nonatomic, assign) CGFloat currentWhitespaceWidth;
210
211 // Whether the paragraph's writing direction is in RTL.
212 @property(nonatomic, readonly) BOOL isRTL;
213
214 // Either 1 or -1 depending on |isRTL|.
215 @property(nonatomic, readonly) CFIndex incrementAmount;
216
217 // Glyphs are laid out differently for RTL and LTR languages (see note at top of
218 // file). These functions return a range with |range|'s length incremented or
219 // decremented and an updated location that would include the next glyph in the
220 // trailing direction.
221 - (CFRange)incrementRange:(CFRange)range byAmount:(CFIndex)amount;
222 - (CFRange)incrementRange:(CFRange)range;
223 - (CFRange)decrementRange:(CFRange)range;
224
225 // Updates |range| such that its trailing glyph index is |trailingGlyphIdx|.
226 - (CFRange)updateRange:(CFRange)range
227 forTrailingGlyphIdx:(CFIndex)trailingGlyphIdx;
228
229 // Returns the index of the trailing glyph in |range| for |currentRun|.
230 - (CFIndex)trailingGlyphIdxForRange:(CFRange)range;
231
232 // Returns the index of the leading or trailing glyph in |currentRun|.
233 - (CFIndex)trailingGlyphIdxForCurrentRun;
234
235 // Manually frames the glyphs in |currentRun| following |framedGlyphCount|.
236 // This function updates |framedGlyphCount|, |currentLineWidth|, and
237 // |currentLineRange|.
238 - (void)frameCurrentRun;
239
240 // Returns the character associated with the glyph at |glyphIdx| in
241 // |currentRun|.
242 - (unichar)charForGlyphAtIdx:(CFIndex)glyphIdx;
243
244 // Returns the index within the original string corresponding to the glyph at
245 // |glyphIdx| in |currentRun|.
246 - (CFIndex)stringIdxForGlyphAtIdx:(CFIndex)glyphIdx;
247
248 // Returns YES if |runIdx| is within the range of |line|'s glyph runs array.
249 - (BOOL)runIdxIsValid:(CFIndex)runIdx;
250
251 // Returns YES if |glyphIdx| is within [0, |currentGlyphCount|).
252 - (BOOL)glyphIdxIsValid:(CFIndex)glyphIdx;
253
254 // Creates a line from |currentLineRange| and adds it to |lines|.
255 - (void)addCurrentLine;
256
257 // Returns the baselines origin for the current line. This function depends on
258 // |currentLineRange|, |currentLineWidth|, and |remainingHeight|, and must be
259 // called before updating those bookkeeping variables when adding the line.
260 - (CGPoint)originForCurrentLine;
261 @end
262
263 @implementation ParagraphFramer
264
265 @synthesize effectiveAlignment = _effectiveTextAlignment;
266 @synthesize runIdx = _runIdx;
267 @synthesize currentRun = _currentRun;
268 @synthesize currentGlyphCount = _currentGlyphCount;
269 @synthesize framedGlyphCount = _framedGlyphCount;
270 @synthesize currentLineRange = _currentLineRange;
271 @synthesize currentLineWidth = _currentLineWidth;
272 @synthesize currentWhitespaceWidth = _currentWhitespaceWidth;
273 @synthesize isRTL = _isRTL;
274
275 - (instancetype)initWithString:(NSAttributedString*)string
276 inBounds:(CGRect)bounds {
277 if ((self = [super initWithString:string inBounds:bounds])) {
278 NSRange newlineRange = [string.string
279 rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]];
280 DCHECK(newlineRange.location == NSNotFound ||
281 newlineRange.location == string.string.length - 1);
282 CTLineRef line =
283 CTLineCreateWithAttributedString(base::mac::NSToCFCast(string));
284 _line.reset(line);
285 _effectiveTextAlignment = core_text_util::GetEffectiveTextAlignment(string);
286 NSWritingDirection direction =
287 core_text_util::GetEffectiveWritingDirection(string);
288 _isRTL = direction == NSWritingDirectionRightToLeft;
289 }
290 return self;
291 }
292
293 - (void)frameText {
294 if (![self setupManualTextFrame])
295 return;
296 self.runIdx =
297 self.isRTL ? CFArrayGetCount(CTLineGetGlyphRuns(self.line)) - 1 : 0;
298 while (self.currentRun) {
299 NSRange runStringRange =
300 core_text_util::GetStringRangeForRun(self.currentRun);
301 DCHECK_NE(runStringRange.location, static_cast<NSUInteger>(NSNotFound));
302 DCHECK_NE(runStringRange.length, 0U);
303 CGFloat runLineHeight =
304 core_text_util::GetLineHeight(self.string, runStringRange);
305 // Count of the number of times the framing process (-frameCurrentRun) has
306 // "stalled" -- run without changing the total number of glyphs framed. In
307 // some cases the process may stall once at the end of a run, but if it
308 // stalls twice, it won't make any further progress and should halt.
309 NSUInteger stallCount = 0;
310 // Loop as long as framed glyph count is less that the total glyph count,
311 // and the framer is making progress.
312 while (self.framedGlyphCount < self.currentGlyphCount && stallCount < 2U) {
313 // Stop framing glyphs if there is not enough vertical space for the run.
314 if (self.remainingHeight < runLineHeight)
315 break;
316 CFIndex initialFramedGlyphCount = self.framedGlyphCount;
317 [self frameCurrentRun];
318 if (self.framedGlyphCount == initialFramedGlyphCount)
319 stallCount++;
320 if (self.framedGlyphCount < self.currentGlyphCount) {
321 // The entire run didn't fit onto the current line, so create a CTLine
322 // from |currentLineRange| and add it to |lines|.
323 [self addCurrentLine];
324 }
325 }
326 self.runIdx += self.incrementAmount;
327 }
328 // Add the final line.
329 [self addCurrentLine];
330 // Update |manualTextFrame|'s |framedRange|.
331 self.manualTextFrame.framedRange =
332 NSMakeRange(0, self.currentLineRange.location);
333 }
334
335 #pragma mark Accessors
336
337 - (CTLineRef)line {
338 return _line.get();
339 }
340
341 - (NSCharacterSet*)lineEndSet {
342 if (!_lineEndSet) {
343 NSMutableCharacterSet* lineEndSet =
344 [NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
345 [lineEndSet addCharactersInString:@"-\u2013\u2014"];
346 _lineEndSet.reset([lineEndSet retain]);
347 }
348 return _lineEndSet;
349 }
350
351 - (void)setRunIdx:(CFIndex)runIdx {
352 _runIdx = runIdx;
353 self.framedGlyphCount = 0;
354 if ([self runIdxIsValid:runIdx]) {
355 NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(self.line));
356 _currentRun = static_cast<CTRunRef>(runs[_runIdx]);
357 _currentGlyphCount = CTRunGetGlyphCount(self.currentRun);
358 } else {
359 _currentRun = nullptr;
360 _currentGlyphCount = 0;
361 }
362 }
363
364 - (CFIndex)incrementAmount {
365 return self.isRTL ? -1 : 1;
366 }
367
368 #pragma mark Private
369
370 - (CFRange)incrementRange:(CFRange)range byAmount:(CFIndex)amount {
371 CFRange incrementedRange = range;
372 incrementedRange.length += amount;
373 if (self.isRTL)
374 incrementedRange.location += self.incrementAmount * amount;
375 return incrementedRange;
376 }
377
378 - (CFRange)incrementRange:(CFRange)range {
379 return [self incrementRange:range byAmount:1];
380 }
381
382 - (CFRange)decrementRange:(CFRange)range {
383 return [self incrementRange:range byAmount:-1];
384 }
385
386 - (CFRange)updateRange:(CFRange)range
387 forTrailingGlyphIdx:(CFIndex)trailingGlyphIdx {
388 DCHECK(self.isRTL ? trailingGlyphIdx <= range.location + range.length
389 : trailingGlyphIdx >= range.location);
390 DCHECK([self glyphIdxIsValid:trailingGlyphIdx]);
391 CFIndex currentTrailingGlyphIdx = [self trailingGlyphIdxForRange:range];
392 CFIndex updateAmount = self.isRTL
393 ? currentTrailingGlyphIdx - trailingGlyphIdx
394 : trailingGlyphIdx - currentTrailingGlyphIdx;
395 return [self incrementRange:range byAmount:updateAmount];
396 }
397
398 - (CFIndex)trailingGlyphIdxForRange:(CFRange)range {
399 if (self.isRTL)
400 return range.location;
401 return range.location + range.length - 1;
402 }
403
404 - (CFIndex)trailingGlyphIdxForCurrentRun {
405 return self.isRTL ? 0 : self.currentGlyphCount - 1;
406 }
407
408 - (void)frameCurrentRun {
409 DCHECK(self.currentRun);
410 DCHECK_LT(self.framedGlyphCount, self.currentGlyphCount);
411 DCHECK_LT(self.currentLineWidth, self.boundingWidth);
412
413 // Calculate the range that will fit in the remaining portion of the line.
414 NSCharacterSet* whitespaceSet =
415 [NSCharacterSet whitespaceAndNewlineCharacterSet];
416 CFIndex startGlyphIdx = self.isRTL
417 ? self.currentGlyphCount - self.framedGlyphCount
418 : self.framedGlyphCount;
419 CFRange remainingRunRange =
420 CFRangeMake(self.isRTL ? 0 : startGlyphIdx,
421 self.currentGlyphCount - self.framedGlyphCount);
422 CFRange range = CFRangeMake(startGlyphIdx, 0);
423 while (remainingRunRange.length > 0) {
424 // Find the range for the next word that can be added to the line. If no
425 // delimiters were found, frame the rest of the run.
426 CFIndex delimIdx = core_text_util::GetGlyphIdxForCharInSet(
427 self.currentRun, remainingRunRange, self.string, self.lineEndSet);
428 if (delimIdx == kCFNotFound)
429 delimIdx = [self trailingGlyphIdxForCurrentRun];
430 CFRange wordGlyphRange =
431 [self updateRange:remainingRunRange forTrailingGlyphIdx:delimIdx];
432 CFIndex wordFramedGlyphCount = wordGlyphRange.length;
433 // Trim any whitespace and record its width.
434 CGFloat wordTrailingWhitespaceWidth = 0.0;
435 if ([whitespaceSet characterIsMember:[self charForGlyphAtIdx:delimIdx]]) {
436 wordTrailingWhitespaceWidth =
437 core_text_util::GetGlyphWidth(self.currentRun, delimIdx);
438 wordGlyphRange = [self decrementRange:wordGlyphRange];
439 }
440 // Check if the word will fit on the line.
441 CGFloat wordWidth =
442 core_text_util::GetRunWidthWithRange(self.currentRun, wordGlyphRange);
443 CGFloat cumulativeLineWidth =
444 self.currentLineWidth + self.currentWhitespaceWidth + wordWidth;
445 if (cumulativeLineWidth <= self.boundingWidth) {
446 // The word at |wordGlyphRange| fits on the line.
447 self.currentLineWidth = cumulativeLineWidth;
448 self.framedGlyphCount += wordFramedGlyphCount;
449 self.currentWhitespaceWidth = wordTrailingWhitespaceWidth;
450 remainingRunRange.length -= wordFramedGlyphCount;
451 if (!self.isRTL)
452 remainingRunRange.location += wordFramedGlyphCount;
453 range = [self incrementRange:range byAmount:wordFramedGlyphCount];
454 } else {
455 break;
456 }
457 }
458 // Early return if no glyphs were framed.
459 if (!range.length)
460 return;
461 // Use the string index of the next glyph to determine the string range for
462 // the current line, since a glyph may correspond with multiple characters
463 // when ligatures are used.
464 CFIndex nextGlyphIdx =
465 [self trailingGlyphIdxForRange:range] + self.incrementAmount;
466 CFIndex nextGlyphStringIdx;
467 if ([self glyphIdxIsValid:nextGlyphIdx]) {
468 nextGlyphStringIdx = [self stringIdxForGlyphAtIdx:nextGlyphIdx];
469 } else {
470 CFRange runStringRange = CTRunGetStringRange(self.currentRun);
471 nextGlyphStringIdx = runStringRange.location + runStringRange.length;
472 }
473 self.currentLineRange =
474 NSMakeRange(self.currentLineRange.location,
475 nextGlyphStringIdx - self.currentLineRange.location);
476 }
477
478 - (unichar)charForGlyphAtIdx:(CFIndex)glyphIdx {
479 DCHECK([self glyphIdxIsValid:glyphIdx]);
480 return [self.string.string
481 characterAtIndex:[self stringIdxForGlyphAtIdx:glyphIdx]];
482 }
483
484 - (CFIndex)stringIdxForGlyphAtIdx:(CFIndex)glyphIdx {
485 DCHECK([self glyphIdxIsValid:glyphIdx]);
486 CFIndex stringIdx = 0;
487 CTRunGetStringIndices(self.currentRun, CFRangeMake(glyphIdx, 1), &stringIdx);
488 return stringIdx;
489 }
490
491 - (BOOL)runIdxIsValid:(CFIndex)runIdx {
492 NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(self.line));
493 return runIdx >= 0 && runIdx < static_cast<CFIndex>(runs.count);
494 }
495
496 - (BOOL)glyphIdxIsValid:(CFIndex)glyphIdx {
497 return glyphIdx >= 0 && glyphIdx < self.currentGlyphCount;
498 }
499
500 - (void)addCurrentLine {
501 // Don't attempt to add a line if |currentLineRange| is empty.
502 if (!self.currentLineRange.length)
503 return;
504 // Add the new line and its corresponding string range and baseline origin.
505 NSAttributedString* currentLineString =
506 [self.string attributedSubstringFromRange:self.currentLineRange];
507 CTLineRef currentLine = CTLineCreateWithAttributedString(
508 base::mac::NSToCFCast(currentLineString));
509 [self.manualTextFrame addFramedLineWithLine:currentLine
510 stringRange:self.currentLineRange
511 origin:[self originForCurrentLine]];
512 CFRelease(currentLine);
513 // Update bookkeeping variables for next line.
514 CGFloat usedHeight =
515 core_text_util::GetLineHeight(self.string, self.currentLineRange) +
516 core_text_util::GetLineSpacing(self.string, self.currentLineRange);
517 self.currentLineRange = NSMakeRange(
518 self.currentLineRange.location + self.currentLineRange.length, 0);
519 self.currentLineWidth = 0;
520 self.currentWhitespaceWidth = 0;
521 self.remainingHeight -= usedHeight;
522 }
523
524 - (CGPoint)originForCurrentLine {
525 CGPoint origin = CGPointZero;
526 CGFloat alignedWidth =
527 AlignValueToPixel(self.currentLineWidth, AlignmentFunction::CEIL);
528 switch (self.effectiveAlignment) {
529 case NSTextAlignmentLeft:
530 // Left-aligned lines begin at 0.0.
531 break;
532 case NSTextAlignmentRight:
533 origin.x = AlignValueToPixel(self.boundingWidth - alignedWidth,
534 AlignmentFunction::FLOOR);
535 break;
536 case NSTextAlignmentCenter:
537 origin.x = AlignValueToPixel((self.boundingWidth - alignedWidth) / 2.0,
538 AlignmentFunction::FLOOR);
539 break;
540 default:
541 // Only left, right, and center effective alignment is supported.
542 NOTREACHED();
543 break;
544 }
545 UIFont* font = [self.string attribute:NSFontAttributeName
546 atIndex:self.currentLineRange.location
547 effectiveRange:nullptr];
548 CGFloat lineHeight =
549 core_text_util::GetLineHeight(self.string, self.currentLineRange);
550 origin.y =
551 AlignValueToPixel(self.remainingHeight - lineHeight - font.descender,
552 AlignmentFunction::FLOOR);
553 return origin;
554 }
555
556 @end
557
558 #pragma mark - ManualTextFramer
559
560 @implementation ManualTextFramer
561
562 @synthesize bounds = _bounds;
563 @synthesize boundingWidth = _boundingWidth;
564 @synthesize remainingHeight = _remainingHeight;
565
566 - (instancetype)initWithString:(NSAttributedString*)string
567 inBounds:(CGRect)bounds {
568 if ((self = [super init])) {
569 DCHECK(string.string.length);
570 _string.reset([string retain]);
571 _bounds = bounds;
572 _boundingWidth = CGRectGetWidth(bounds);
573 _remainingHeight = CGRectGetHeight(bounds);
574 }
575 return self;
576 }
577
578 - (void)frameText {
579 if (![self setupManualTextFrame])
580 return;
581 NSRange framedRange = NSMakeRange(0, 0);
582 NSArray* paragraphs = GetParagraphStringsForString(self.string);
583 NSUInteger stringRangeOffset = 0;
584 for (NSAttributedString* paragraph in paragraphs) {
585 // Frame each paragraph using a ParagraphFramer, then update bookkeeping
586 // variables for the top-level ManualTextFramer.
587 CGRect remainingBounds =
588 CGRectMake(0, 0, self.boundingWidth, self.remainingHeight);
589 base::scoped_nsobject<ParagraphFramer> framer([[ParagraphFramer alloc]
590 initWithString:paragraph
591 inBounds:remainingBounds]);
592 [framer frameText];
593 id<TextFrame> frame = [framer textFrame];
594 DCHECK(frame);
595 framedRange.length += frame.framedRange.length;
596 CGFloat paragraphHeight = 0.0;
597 for (FramedLine* line in frame.lines) {
598 NSRange lineRange = line.stringRange;
599 lineRange.location += stringRangeOffset;
600 [self.manualTextFrame addFramedLineWithLine:line.line
601 stringRange:lineRange
602 origin:line.origin];
603 paragraphHeight += core_text_util::GetLineHeight(self.string, lineRange) +
604 core_text_util::GetLineSpacing(self.string, lineRange);
605 }
606 self.remainingHeight -= paragraphHeight;
607 stringRangeOffset += paragraph.string.length;
608 }
609 self.manualTextFrame.framedRange = framedRange;
610 }
611
612 #pragma mark Accessors
613
614 - (NSAttributedString*)string {
615 return _string.get();
616 }
617
618 - (ManualTextFrame*)manualTextFrame {
619 return _manualTextFrame.get();
620 }
621
622 - (id<TextFrame>)textFrame {
623 return _manualTextFrame.get();
624 }
625
626 #pragma mark Private
627
628 - (BOOL)setupManualTextFrame {
629 if (_manualTextFrame)
630 return NO;
631 _manualTextFrame.reset([[ManualTextFrame alloc] initWithString:self.string
632 inBounds:self.bounds]);
633 return YES;
634 }
635
636 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/util/manual_text_framer.h ('k') | ios/chrome/browser/ui/util/manual_text_framer_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698