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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/util/manual_text_framer.mm
diff --git a/ios/chrome/browser/ui/util/manual_text_framer.mm b/ios/chrome/browser/ui/util/manual_text_framer.mm
new file mode 100644
index 0000000000000000000000000000000000000000..74cdb7b12d2e57a2935d6c4dd1915e1328c65443
--- /dev/null
+++ b/ios/chrome/browser/ui/util/manual_text_framer.mm
@@ -0,0 +1,636 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/util/manual_text_framer.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/ui/util/core_text_util.h"
+#import "ios/chrome/browser/ui/util/text_frame.h"
+#import "ios/chrome/browser/ui/util/unicode_util.h"
+
+// NOTE: When RTL text is laid out into glyph runs, the glyphs appear in the
+// visual order in which they appear on screen. In other words, the glyphs are
+// arranged in the reverse order of their corresponding characters in the
+// original string.
+
+namespace {
+// Aligns |value| to the nearest pixel value, rounding by the function indicated
+// by |function|. AlignmentFunction::CEIL should be used to align size values,
+// while AlignmentFunction::FLOOR should be used to align location values.
+enum class AlignmentFunction : short { CEIL = 0, FLOOR };
+CGFloat AlignValueToPixel(CGFloat value, AlignmentFunction function) {
+ static CGFloat scale = [[UIScreen mainScreen] scale];
+ return function == AlignmentFunction::CEIL ? ceil(value * scale) / scale
+ : floor(value * scale) / scale;
+}
+
+// Returns an NSArray of NSAttributedStrings corresponding to newline-separated
+// paragraphs within |string|.
+NSArray* GetParagraphStringsForString(NSAttributedString* string) {
+ NSMutableArray* paragraph_strings = [NSMutableArray array];
+ NSCharacterSet* newline_char_set = [NSCharacterSet newlineCharacterSet];
+ NSUInteger string_length = string.string.length;
+ NSRange remaining_range = NSMakeRange(0, string_length);
+ while (remaining_range.location < string_length) {
+ NSRange newline_range =
+ [string.string rangeOfCharacterFromSet:newline_char_set
+ options:0
+ range:remaining_range];
+ NSRange paragraph_range = NSMakeRange(0, 0);
+ if (newline_range.location == NSNotFound) {
+ // There's no newline in the remaining portion of the string.
+ paragraph_range = remaining_range;
+ remaining_range = NSMakeRange(string_length, 0);
+ } else {
+ // A newline character was encountered. Compute approximate text lines
+ // for the substring within |remaining_range| up to the newline.
+ NSUInteger newline_end = newline_range.location + newline_range.length;
+ paragraph_range = NSMakeRange(remaining_range.location,
+ newline_end - remaining_range.location);
+ remaining_range.location = newline_end;
+ remaining_range.length = string_length - remaining_range.location;
+ }
+ // Create an attributed substring for the current paragraph and add it to
+ // |paragraphs|.
+ [paragraph_strings
+ addObject:[string attributedSubstringFromRange:paragraph_range]];
+ }
+ return paragraph_strings;
+}
+} // namespace
+
+#pragma mark - ManualTextFrame
+
+// A TextFrame implementation that is manually created by ManualTextFramer.
+@interface ManualTextFrame : NSObject<TextFrame> {
+ // Backing objects for properties of the same name.
+ base::scoped_nsobject<NSAttributedString> _string;
+ base::scoped_nsobject<NSMutableArray> _lines;
+}
+
+// Designated initializer.
+- (instancetype)initWithString:(NSAttributedString*)string
+ inBounds:(CGRect)bounds NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
+// Creates a FramedLine out of |line|, |stringRange|, and |origin|, then adds it
+// to |lines|.
+- (void)addFramedLineWithLine:(CTLineRef)line
+ stringRange:(NSRange)stringRange
+ origin:(CGPoint)origin;
+
+// Redefine property as readwrite.
+@property(nonatomic, readwrite) NSRange framedRange;
+
+@end
+
+@implementation ManualTextFrame
+
+@synthesize framedRange = _framedRange;
+@synthesize bounds = _bounds;
+
+- (instancetype)initWithString:(NSAttributedString*)string
+ inBounds:(CGRect)bounds {
+ if ((self = [super init])) {
+ DCHECK(string.string.length);
+ _string.reset([string retain]);
+ _bounds = bounds;
+ _lines.reset([[NSMutableArray alloc] init]);
+ }
+ return self;
+}
+
+#pragma mark Accessors
+
+- (NSAttributedString*)string {
+ return _string.get();
+}
+
+- (NSArray*)lines {
+ return _lines.get();
+}
+
+#pragma mark Private
+
+- (void)addFramedLineWithLine:(CTLineRef)line
+ stringRange:(NSRange)stringRange
+ origin:(CGPoint)origin {
+ base::scoped_nsobject<FramedLine> framedLine([[FramedLine alloc]
+ initWithLine:line
+ stringRange:stringRange
+ origin:origin]);
+ [_lines addObject:framedLine];
+}
+
+@end
+
+#pragma mark - ManualTextFramer Private Interface
+
+@interface ManualTextFramer () {
+ // Backing objects for properties of the same name.
+ base::scoped_nsobject<NSAttributedString> _string;
+ base::scoped_nsobject<ManualTextFrame> _manualTextFrame;
+}
+
+// The string passed upon initialization.
+@property(nonatomic, readonly) NSAttributedString* string;
+
+// The bounds passed upon initialization.
+@property(nonatomic, readonly) CGRect bounds;
+
+// The width of the bounds passed upon initialization.
+@property(nonatomic, readonly) CGFloat boundingWidth;
+
+// The remaining height into which text can be framed.
+@property(nonatomic, assign) CGFloat remainingHeight;
+
+// The text frame constructed by |-frameText|.
+@property(nonatomic, readonly) ManualTextFrame* manualTextFrame;
+
+// Creates a ManualTextFrame and assigns it to |_manualTextFrame|. Returns YES
+// if a new text frame was successfully created.
+- (BOOL)setupManualTextFrame;
+
+@end
+
+#pragma mark - ParagraphFramer
+
+// ManualTextFramer subclass that frames a single paragraph. A paragraph is
+// defined as an NSAttributedString which contains either zero newlines or one
+// newline as its last character.
+@interface ParagraphFramer : ManualTextFramer {
+ // Backing objects for properties of the same name.
+ base::ScopedCFTypeRef<CTLineRef> _line;
+ base::scoped_nsobject<NSCharacterSet> _lineEndSet;
+}
+
+// The CTLine created from |string|.
+@property(nonatomic, readonly) CTLineRef line;
+
+// The effective text alignment for |line|.
+@property(nonatomic, readonly) NSTextAlignment effectiveAlignment;
+
+// Character set containing characters that are appropriate for line endings.
+// These characters include whitespaces and newlines (denoting a word boundary),
+// in addition to line-ending characters like hyphens, em dashes, and en dashes.
+@property(nonatomic, readonly) NSCharacterSet* lineEndSet;
+
+// The index of the current run that is being framed. Setting |runIdx| also
+// updates |currentRun| and |currentGlyphCount|.
+@property(nonatomic, assign) CFIndex runIdx;
+
+// The CTRun corresponding with |runIdx| in |line|.
+@property(nonatomic, readonly) CTRunRef currentRun;
+
+// The glyph count in |currentRun|.
+@property(nonatomic, readonly) CFIndex currentGlyphCount;
+
+// The number of glyphs in |currentRun| that have been successfully framed.
+@property(nonatomic, assign) CFIndex framedGlyphCount;
+
+// The range in |string| that has successfully been framed for the current line.
+@property(nonatomic, assign) NSRange currentLineRange;
+
+// The width of the typographic bounds for the glyphs framed on the current
+// line. This is the width of the substring of |string| corresponding to
+// |currentLineRange|.
+@property(nonatomic, assign) CGFloat currentLineWidth;
+
+// The width of the trailing whitespace for the current line. This whitespace
+// is not counted against the line width if it's the end of the line, but needs
+// to be added in if non-whitespace characters from subsequent runs fit on the
+// same line.
+@property(nonatomic, assign) CGFloat currentWhitespaceWidth;
+
+// Whether the paragraph's writing direction is in RTL.
+@property(nonatomic, readonly) BOOL isRTL;
+
+// Either 1 or -1 depending on |isRTL|.
+@property(nonatomic, readonly) CFIndex incrementAmount;
+
+// Glyphs are laid out differently for RTL and LTR languages (see note at top of
+// file). These functions return a range with |range|'s length incremented or
+// decremented and an updated location that would include the next glyph in the
+// trailing direction.
+- (CFRange)incrementRange:(CFRange)range byAmount:(CFIndex)amount;
+- (CFRange)incrementRange:(CFRange)range;
+- (CFRange)decrementRange:(CFRange)range;
+
+// Updates |range| such that its trailing glyph index is |trailingGlyphIdx|.
+- (CFRange)updateRange:(CFRange)range
+ forTrailingGlyphIdx:(CFIndex)trailingGlyphIdx;
+
+// Returns the index of the trailing glyph in |range| for |currentRun|.
+- (CFIndex)trailingGlyphIdxForRange:(CFRange)range;
+
+// Returns the index of the leading or trailing glyph in |currentRun|.
+- (CFIndex)trailingGlyphIdxForCurrentRun;
+
+// Manually frames the glyphs in |currentRun| following |framedGlyphCount|.
+// This function updates |framedGlyphCount|, |currentLineWidth|, and
+// |currentLineRange|.
+- (void)frameCurrentRun;
+
+// Returns the character associated with the glyph at |glyphIdx| in
+// |currentRun|.
+- (unichar)charForGlyphAtIdx:(CFIndex)glyphIdx;
+
+// Returns the index within the original string corresponding to the glyph at
+// |glyphIdx| in |currentRun|.
+- (CFIndex)stringIdxForGlyphAtIdx:(CFIndex)glyphIdx;
+
+// Returns YES if |runIdx| is within the range of |line|'s glyph runs array.
+- (BOOL)runIdxIsValid:(CFIndex)runIdx;
+
+// Returns YES if |glyphIdx| is within [0, |currentGlyphCount|).
+- (BOOL)glyphIdxIsValid:(CFIndex)glyphIdx;
+
+// Creates a line from |currentLineRange| and adds it to |lines|.
+- (void)addCurrentLine;
+
+// Returns the baselines origin for the current line. This function depends on
+// |currentLineRange|, |currentLineWidth|, and |remainingHeight|, and must be
+// called before updating those bookkeeping variables when adding the line.
+- (CGPoint)originForCurrentLine;
+@end
+
+@implementation ParagraphFramer
+
+@synthesize effectiveAlignment = _effectiveTextAlignment;
+@synthesize runIdx = _runIdx;
+@synthesize currentRun = _currentRun;
+@synthesize currentGlyphCount = _currentGlyphCount;
+@synthesize framedGlyphCount = _framedGlyphCount;
+@synthesize currentLineRange = _currentLineRange;
+@synthesize currentLineWidth = _currentLineWidth;
+@synthesize currentWhitespaceWidth = _currentWhitespaceWidth;
+@synthesize isRTL = _isRTL;
+
+- (instancetype)initWithString:(NSAttributedString*)string
+ inBounds:(CGRect)bounds {
+ if ((self = [super initWithString:string inBounds:bounds])) {
+ NSRange newlineRange = [string.string
+ rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]];
+ DCHECK(newlineRange.location == NSNotFound ||
+ newlineRange.location == string.string.length - 1);
+ CTLineRef line =
+ CTLineCreateWithAttributedString(base::mac::NSToCFCast(string));
+ _line.reset(line);
+ _effectiveTextAlignment = core_text_util::GetEffectiveTextAlignment(string);
+ NSWritingDirection direction =
+ core_text_util::GetEffectiveWritingDirection(string);
+ _isRTL = direction == NSWritingDirectionRightToLeft;
+ }
+ return self;
+}
+
+- (void)frameText {
+ if (![self setupManualTextFrame])
+ return;
+ self.runIdx =
+ self.isRTL ? CFArrayGetCount(CTLineGetGlyphRuns(self.line)) - 1 : 0;
+ while (self.currentRun) {
+ NSRange runStringRange =
+ core_text_util::GetStringRangeForRun(self.currentRun);
+ DCHECK_NE(runStringRange.location, static_cast<NSUInteger>(NSNotFound));
+ DCHECK_NE(runStringRange.length, 0U);
+ CGFloat runLineHeight =
+ core_text_util::GetLineHeight(self.string, runStringRange);
+ // Count of the number of times the framing process (-frameCurrentRun) has
+ // "stalled" -- run without changing the total number of glyphs framed. In
+ // some cases the process may stall once at the end of a run, but if it
+ // stalls twice, it won't make any further progress and should halt.
+ NSUInteger stallCount = 0;
+ // Loop as long as framed glyph count is less that the total glyph count,
+ // and the framer is making progress.
+ while (self.framedGlyphCount < self.currentGlyphCount && stallCount < 2U) {
+ // Stop framing glyphs if there is not enough vertical space for the run.
+ if (self.remainingHeight < runLineHeight)
+ break;
+ CFIndex initialFramedGlyphCount = self.framedGlyphCount;
+ [self frameCurrentRun];
+ if (self.framedGlyphCount == initialFramedGlyphCount)
+ stallCount++;
+ if (self.framedGlyphCount < self.currentGlyphCount) {
+ // The entire run didn't fit onto the current line, so create a CTLine
+ // from |currentLineRange| and add it to |lines|.
+ [self addCurrentLine];
+ }
+ }
+ self.runIdx += self.incrementAmount;
+ }
+ // Add the final line.
+ [self addCurrentLine];
+ // Update |manualTextFrame|'s |framedRange|.
+ self.manualTextFrame.framedRange =
+ NSMakeRange(0, self.currentLineRange.location);
+}
+
+#pragma mark Accessors
+
+- (CTLineRef)line {
+ return _line.get();
+}
+
+- (NSCharacterSet*)lineEndSet {
+ if (!_lineEndSet) {
+ NSMutableCharacterSet* lineEndSet =
+ [NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
+ [lineEndSet addCharactersInString:@"-\u2013\u2014"];
+ _lineEndSet.reset([lineEndSet retain]);
+ }
+ return _lineEndSet;
+}
+
+- (void)setRunIdx:(CFIndex)runIdx {
+ _runIdx = runIdx;
+ self.framedGlyphCount = 0;
+ if ([self runIdxIsValid:runIdx]) {
+ NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(self.line));
+ _currentRun = static_cast<CTRunRef>(runs[_runIdx]);
+ _currentGlyphCount = CTRunGetGlyphCount(self.currentRun);
+ } else {
+ _currentRun = nullptr;
+ _currentGlyphCount = 0;
+ }
+}
+
+- (CFIndex)incrementAmount {
+ return self.isRTL ? -1 : 1;
+}
+
+#pragma mark Private
+
+- (CFRange)incrementRange:(CFRange)range byAmount:(CFIndex)amount {
+ CFRange incrementedRange = range;
+ incrementedRange.length += amount;
+ if (self.isRTL)
+ incrementedRange.location += self.incrementAmount * amount;
+ return incrementedRange;
+}
+
+- (CFRange)incrementRange:(CFRange)range {
+ return [self incrementRange:range byAmount:1];
+}
+
+- (CFRange)decrementRange:(CFRange)range {
+ return [self incrementRange:range byAmount:-1];
+}
+
+- (CFRange)updateRange:(CFRange)range
+ forTrailingGlyphIdx:(CFIndex)trailingGlyphIdx {
+ DCHECK(self.isRTL ? trailingGlyphIdx <= range.location + range.length
+ : trailingGlyphIdx >= range.location);
+ DCHECK([self glyphIdxIsValid:trailingGlyphIdx]);
+ CFIndex currentTrailingGlyphIdx = [self trailingGlyphIdxForRange:range];
+ CFIndex updateAmount = self.isRTL
+ ? currentTrailingGlyphIdx - trailingGlyphIdx
+ : trailingGlyphIdx - currentTrailingGlyphIdx;
+ return [self incrementRange:range byAmount:updateAmount];
+}
+
+- (CFIndex)trailingGlyphIdxForRange:(CFRange)range {
+ if (self.isRTL)
+ return range.location;
+ return range.location + range.length - 1;
+}
+
+- (CFIndex)trailingGlyphIdxForCurrentRun {
+ return self.isRTL ? 0 : self.currentGlyphCount - 1;
+}
+
+- (void)frameCurrentRun {
+ DCHECK(self.currentRun);
+ DCHECK_LT(self.framedGlyphCount, self.currentGlyphCount);
+ DCHECK_LT(self.currentLineWidth, self.boundingWidth);
+
+ // Calculate the range that will fit in the remaining portion of the line.
+ NSCharacterSet* whitespaceSet =
+ [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ CFIndex startGlyphIdx = self.isRTL
+ ? self.currentGlyphCount - self.framedGlyphCount
+ : self.framedGlyphCount;
+ CFRange remainingRunRange =
+ CFRangeMake(self.isRTL ? 0 : startGlyphIdx,
+ self.currentGlyphCount - self.framedGlyphCount);
+ CFRange range = CFRangeMake(startGlyphIdx, 0);
+ while (remainingRunRange.length > 0) {
+ // Find the range for the next word that can be added to the line. If no
+ // delimiters were found, frame the rest of the run.
+ CFIndex delimIdx = core_text_util::GetGlyphIdxForCharInSet(
+ self.currentRun, remainingRunRange, self.string, self.lineEndSet);
+ if (delimIdx == kCFNotFound)
+ delimIdx = [self trailingGlyphIdxForCurrentRun];
+ CFRange wordGlyphRange =
+ [self updateRange:remainingRunRange forTrailingGlyphIdx:delimIdx];
+ CFIndex wordFramedGlyphCount = wordGlyphRange.length;
+ // Trim any whitespace and record its width.
+ CGFloat wordTrailingWhitespaceWidth = 0.0;
+ if ([whitespaceSet characterIsMember:[self charForGlyphAtIdx:delimIdx]]) {
+ wordTrailingWhitespaceWidth =
+ core_text_util::GetGlyphWidth(self.currentRun, delimIdx);
+ wordGlyphRange = [self decrementRange:wordGlyphRange];
+ }
+ // Check if the word will fit on the line.
+ CGFloat wordWidth =
+ core_text_util::GetRunWidthWithRange(self.currentRun, wordGlyphRange);
+ CGFloat cumulativeLineWidth =
+ self.currentLineWidth + self.currentWhitespaceWidth + wordWidth;
+ if (cumulativeLineWidth <= self.boundingWidth) {
+ // The word at |wordGlyphRange| fits on the line.
+ self.currentLineWidth = cumulativeLineWidth;
+ self.framedGlyphCount += wordFramedGlyphCount;
+ self.currentWhitespaceWidth = wordTrailingWhitespaceWidth;
+ remainingRunRange.length -= wordFramedGlyphCount;
+ if (!self.isRTL)
+ remainingRunRange.location += wordFramedGlyphCount;
+ range = [self incrementRange:range byAmount:wordFramedGlyphCount];
+ } else {
+ break;
+ }
+ }
+ // Early return if no glyphs were framed.
+ if (!range.length)
+ return;
+ // Use the string index of the next glyph to determine the string range for
+ // the current line, since a glyph may correspond with multiple characters
+ // when ligatures are used.
+ CFIndex nextGlyphIdx =
+ [self trailingGlyphIdxForRange:range] + self.incrementAmount;
+ CFIndex nextGlyphStringIdx;
+ if ([self glyphIdxIsValid:nextGlyphIdx]) {
+ nextGlyphStringIdx = [self stringIdxForGlyphAtIdx:nextGlyphIdx];
+ } else {
+ CFRange runStringRange = CTRunGetStringRange(self.currentRun);
+ nextGlyphStringIdx = runStringRange.location + runStringRange.length;
+ }
+ self.currentLineRange =
+ NSMakeRange(self.currentLineRange.location,
+ nextGlyphStringIdx - self.currentLineRange.location);
+}
+
+- (unichar)charForGlyphAtIdx:(CFIndex)glyphIdx {
+ DCHECK([self glyphIdxIsValid:glyphIdx]);
+ return [self.string.string
+ characterAtIndex:[self stringIdxForGlyphAtIdx:glyphIdx]];
+}
+
+- (CFIndex)stringIdxForGlyphAtIdx:(CFIndex)glyphIdx {
+ DCHECK([self glyphIdxIsValid:glyphIdx]);
+ CFIndex stringIdx = 0;
+ CTRunGetStringIndices(self.currentRun, CFRangeMake(glyphIdx, 1), &stringIdx);
+ return stringIdx;
+}
+
+- (BOOL)runIdxIsValid:(CFIndex)runIdx {
+ NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(self.line));
+ return runIdx >= 0 && runIdx < static_cast<CFIndex>(runs.count);
+}
+
+- (BOOL)glyphIdxIsValid:(CFIndex)glyphIdx {
+ return glyphIdx >= 0 && glyphIdx < self.currentGlyphCount;
+}
+
+- (void)addCurrentLine {
+ // Don't attempt to add a line if |currentLineRange| is empty.
+ if (!self.currentLineRange.length)
+ return;
+ // Add the new line and its corresponding string range and baseline origin.
+ NSAttributedString* currentLineString =
+ [self.string attributedSubstringFromRange:self.currentLineRange];
+ CTLineRef currentLine = CTLineCreateWithAttributedString(
+ base::mac::NSToCFCast(currentLineString));
+ [self.manualTextFrame addFramedLineWithLine:currentLine
+ stringRange:self.currentLineRange
+ origin:[self originForCurrentLine]];
+ CFRelease(currentLine);
+ // Update bookkeeping variables for next line.
+ CGFloat usedHeight =
+ core_text_util::GetLineHeight(self.string, self.currentLineRange) +
+ core_text_util::GetLineSpacing(self.string, self.currentLineRange);
+ self.currentLineRange = NSMakeRange(
+ self.currentLineRange.location + self.currentLineRange.length, 0);
+ self.currentLineWidth = 0;
+ self.currentWhitespaceWidth = 0;
+ self.remainingHeight -= usedHeight;
+}
+
+- (CGPoint)originForCurrentLine {
+ CGPoint origin = CGPointZero;
+ CGFloat alignedWidth =
+ AlignValueToPixel(self.currentLineWidth, AlignmentFunction::CEIL);
+ switch (self.effectiveAlignment) {
+ case NSTextAlignmentLeft:
+ // Left-aligned lines begin at 0.0.
+ break;
+ case NSTextAlignmentRight:
+ origin.x = AlignValueToPixel(self.boundingWidth - alignedWidth,
+ AlignmentFunction::FLOOR);
+ break;
+ case NSTextAlignmentCenter:
+ origin.x = AlignValueToPixel((self.boundingWidth - alignedWidth) / 2.0,
+ AlignmentFunction::FLOOR);
+ break;
+ default:
+ // Only left, right, and center effective alignment is supported.
+ NOTREACHED();
+ break;
+ }
+ UIFont* font = [self.string attribute:NSFontAttributeName
+ atIndex:self.currentLineRange.location
+ effectiveRange:nullptr];
+ CGFloat lineHeight =
+ core_text_util::GetLineHeight(self.string, self.currentLineRange);
+ origin.y =
+ AlignValueToPixel(self.remainingHeight - lineHeight - font.descender,
+ AlignmentFunction::FLOOR);
+ return origin;
+}
+
+@end
+
+#pragma mark - ManualTextFramer
+
+@implementation ManualTextFramer
+
+@synthesize bounds = _bounds;
+@synthesize boundingWidth = _boundingWidth;
+@synthesize remainingHeight = _remainingHeight;
+
+- (instancetype)initWithString:(NSAttributedString*)string
+ inBounds:(CGRect)bounds {
+ if ((self = [super init])) {
+ DCHECK(string.string.length);
+ _string.reset([string retain]);
+ _bounds = bounds;
+ _boundingWidth = CGRectGetWidth(bounds);
+ _remainingHeight = CGRectGetHeight(bounds);
+ }
+ return self;
+}
+
+- (void)frameText {
+ if (![self setupManualTextFrame])
+ return;
+ NSRange framedRange = NSMakeRange(0, 0);
+ NSArray* paragraphs = GetParagraphStringsForString(self.string);
+ NSUInteger stringRangeOffset = 0;
+ for (NSAttributedString* paragraph in paragraphs) {
+ // Frame each paragraph using a ParagraphFramer, then update bookkeeping
+ // variables for the top-level ManualTextFramer.
+ CGRect remainingBounds =
+ CGRectMake(0, 0, self.boundingWidth, self.remainingHeight);
+ base::scoped_nsobject<ParagraphFramer> framer([[ParagraphFramer alloc]
+ initWithString:paragraph
+ inBounds:remainingBounds]);
+ [framer frameText];
+ id<TextFrame> frame = [framer textFrame];
+ DCHECK(frame);
+ framedRange.length += frame.framedRange.length;
+ CGFloat paragraphHeight = 0.0;
+ for (FramedLine* line in frame.lines) {
+ NSRange lineRange = line.stringRange;
+ lineRange.location += stringRangeOffset;
+ [self.manualTextFrame addFramedLineWithLine:line.line
+ stringRange:lineRange
+ origin:line.origin];
+ paragraphHeight += core_text_util::GetLineHeight(self.string, lineRange) +
+ core_text_util::GetLineSpacing(self.string, lineRange);
+ }
+ self.remainingHeight -= paragraphHeight;
+ stringRangeOffset += paragraph.string.length;
+ }
+ self.manualTextFrame.framedRange = framedRange;
+}
+
+#pragma mark Accessors
+
+- (NSAttributedString*)string {
+ return _string.get();
+}
+
+- (ManualTextFrame*)manualTextFrame {
+ return _manualTextFrame.get();
+}
+
+- (id<TextFrame>)textFrame {
+ return _manualTextFrame.get();
+}
+
+#pragma mark Private
+
+- (BOOL)setupManualTextFrame {
+ if (_manualTextFrame)
+ return NO;
+ _manualTextFrame.reset([[ManualTextFrame alloc] initWithString:self.string
+ inBounds:self.bounds]);
+ return YES;
+}
+
+@end
« 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