Index: ios/chrome/browser/ui/util/core_text_util.mm |
diff --git a/ios/chrome/browser/ui/util/core_text_util.mm b/ios/chrome/browser/ui/util/core_text_util.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7dec519b065d4acf46d45c901060b58b5b089562 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/util/core_text_util.mm |
@@ -0,0 +1,226 @@ |
+// 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/core_text_util.h" |
+ |
+#import <UIKit/UIKit.h> |
+ |
+#include "base/i18n/rtl.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#import "ios/chrome/browser/ui/util/manual_text_framer.h" |
+#import "ios/chrome/browser/ui/util/text_frame.h" |
+#import "ios/chrome/browser/ui/util/unicode_util.h" |
+ |
+namespace core_text_util { |
+ |
+namespace { |
+// Returns the range within |string| for which |text_frame|'s glyph information |
+// is non-repeating. |
+CFRange GetValidRangeForTextFrame(CTFrameRef text_frame, |
+ NSAttributedString* string) { |
+ DCHECK(text_frame); |
+ DCHECK(string.length); |
+ CFRange range = CFRangeMake(0, 0); |
+ bool is_rtl = |
+ GetEffectiveWritingDirection(string) == NSWritingDirectionRightToLeft; |
+ for (id line_id in base::mac::CFToNSCast(CTFrameGetLines(text_frame))) { |
+ CTLineRef line = static_cast<CTLineRef>(line_id); |
+ NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(line)); |
+ NSInteger run_idx = is_rtl ? runs.count - 1 : 0; |
+ while (run_idx >= 0 && run_idx < static_cast<NSInteger>(runs.count)) { |
+ CTRunRef run = static_cast<CTRunRef>(runs[run_idx]); |
+ CFRange run_range = CTRunGetStringRange(run); |
+ if (run_range.location == range.location + range.length) |
+ range.length += run_range.length; |
+ else |
+ break; |
+ run_idx += is_rtl ? -1 : 1; |
+ } |
+ } |
+ return range; |
+} |
+} // namespace |
+ |
+base::ScopedCFTypeRef<CTFrameRef> CreateTextFrameForStringInBounds( |
+ NSAttributedString* string, |
+ CGRect bounds) { |
+ base::ScopedCFTypeRef<CTFramesetterRef> frame_setter( |
+ CTFramesetterCreateWithAttributedString(base::mac::NSToCFCast(string))); |
+ DCHECK(frame_setter); |
+ base::ScopedCFTypeRef<CGPathRef> path(CGPathCreateWithRect(bounds, nullptr)); |
+ DCHECK(path); |
+ base::ScopedCFTypeRef<CTFrameRef> text_frame( |
+ CTFramesetterCreateFrame(frame_setter, CFRangeMake(0, 0), path, nullptr)); |
+ DCHECK(text_frame); |
+ return text_frame; |
+} |
+ |
+bool IsTextFrameValid(CTFrameRef text_frame, |
+ ManualTextFramer* manual_framer, |
+ NSAttributedString* string) { |
+ DCHECK(text_frame); |
+ DCHECK(manual_framer); |
+ CFRange visible_range = CTFrameGetVisibleStringRange(text_frame); |
+ CFRange valid_range = GetValidRangeForTextFrame(text_frame, string); |
+ NSRange unsigned_visible_range; |
+ // If |visible_range| has invalid values, return early. |
+ if (!base::mac::CFRangeToNSRange(visible_range, &unsigned_visible_range)) |
+ return false; |
+ NSRange manual_range = manual_framer.textFrame.framedRange; |
+ return visible_range.location == valid_range.location && |
+ visible_range.length == valid_range.length && |
+ manual_range.length == unsigned_visible_range.length; |
+} |
+ |
+CGFloat GetTrimmedLineWidth(CTLineRef line) { |
+ DCHECK(line); |
+ return CTLineGetTypographicBounds(line, nullptr, nullptr, nullptr) - |
+ CTLineGetTrailingWhitespaceWidth(line); |
+} |
+ |
+CGFloat GetRunWidthWithRange(CTRunRef run, CFRange range) { |
+ DCHECK(run); |
+ CFIndex glyph_count = CTRunGetGlyphCount(run); |
+ if (range.location < 0 || range.location >= glyph_count || |
+ range.location + range.length > glyph_count || !range.length) { |
+ return 0; |
+ } |
+ return CTRunGetTypographicBounds(run, range, nullptr, nullptr, nullptr); |
+} |
+ |
+CGFloat GetGlyphWidth(CTRunRef run, CFIndex glyph_idx) { |
+ return GetRunWidthWithRange(run, CFRangeMake(glyph_idx, 1)); |
+} |
+ |
+CFIndex GetGlyphIdxForCharInSet(CTRunRef run, |
+ CFRange range, |
+ NSAttributedString* string, |
+ NSCharacterSet* set) { |
+ DCHECK(run); |
+ CFIndex glyph_count = CTRunGetGlyphCount(run); |
+ DCHECK_LT(range.location, glyph_count); |
+ DCHECK_LE(range.location + range.length, glyph_count); |
+ DCHECK(string.length); |
+ DCHECK(set); |
+ BOOL isRTL = |
+ GetEffectiveWritingDirection(string) == NSWritingDirectionRightToLeft; |
+ CFIndex glyph_idx = |
+ isRTL ? range.location + range.length - 1 : range.location; |
+ CFIndex string_idx = GetStringIdxForGlyphIdx(run, glyph_idx); |
+ while (![set characterIsMember:[string.string characterAtIndex:string_idx]]) { |
+ glyph_idx += isRTL ? -1 : 1; |
+ string_idx = GetStringIdxForGlyphIdx(run, glyph_idx); |
+ if (string_idx == NSNotFound) |
+ return kCFNotFound; |
+ } |
+ return glyph_idx; |
+} |
+ |
+NSUInteger GetStringIdxForGlyphIdx(CTRunRef run, CFIndex glyph_idx) { |
+ DCHECK(run); |
+ if (glyph_idx < 0 || glyph_idx >= CTRunGetGlyphCount(run)) |
+ return NSNotFound; |
+ CFIndex string_idx; |
+ CTRunGetStringIndices(run, CFRangeMake(glyph_idx, 1), &string_idx); |
+ return static_cast<NSUInteger>(string_idx); |
+} |
+ |
+NSRange GetStringRangeForRun(CTRunRef run) { |
+ DCHECK(run); |
+ NSRange stringRange; |
+ if (base::mac::CFRangeToNSRange(CTRunGetStringRange(run), &stringRange)) |
+ return stringRange; |
+ return NSMakeRange(NSNotFound, 0); |
+} |
+ |
+void EnumerateAttributesInString(NSAttributedString* string, |
+ NSRange range, |
+ AttributesBlock block) { |
+ DCHECK(string.length); |
+ DCHECK_LT(range.location, string.length); |
+ DCHECK_LE(range.location + range.length, string.length); |
+ DCHECK(block); |
+ NSUInteger char_idx = range.location; |
+ NSRange effectiveRange = NSMakeRange(0, 0); |
+ while (char_idx < range.location + range.length) { |
+ NSDictionary* attributes = |
+ [string attributesAtIndex:char_idx effectiveRange:&effectiveRange]; |
+ block(attributes); |
+ char_idx += range.length; |
+ } |
+} |
+ |
+CGFloat GetLineHeight(NSAttributedString* string, NSRange range) { |
+ __block CGFloat line_height = 0; |
+ AttributesBlock block = ^(NSDictionary* attributes) { |
+ NSParagraphStyle* style = attributes[NSParagraphStyleAttributeName]; |
+ CGFloat run_line_height = 0; |
+ UIFont* font = attributes[NSFontAttributeName]; |
+ if (!font) |
+ font = [UIFont systemFontOfSize:[UIFont systemFontSize]]; |
+ DCHECK(font); |
+ run_line_height = font.ascender - font.descender; |
+ if (style.lineHeightMultiple > 0) |
+ run_line_height *= style.lineHeightMultiple; |
+ if (style.minimumLineHeight > 0) |
+ run_line_height = std::max(run_line_height, style.minimumLineHeight); |
+ if (style.maximumLineHeight > 0) |
+ run_line_height = std::min(run_line_height, style.maximumLineHeight); |
+ line_height = std::max(line_height, run_line_height); |
+ }; |
+ EnumerateAttributesInString(string, range, block); |
+ return line_height; |
+} |
+ |
+CGFloat GetLineSpacing(NSAttributedString* string, NSRange range) { |
+ __block CGFloat line_spacing = 0; |
+ AttributesBlock block = ^(NSDictionary* attributes) { |
+ NSParagraphStyle* style = attributes[NSParagraphStyleAttributeName]; |
+ line_spacing = std::max(line_spacing, style.lineSpacing); |
+ }; |
+ EnumerateAttributesInString(string, range, block); |
+ return line_spacing; |
+} |
+ |
+NSTextAlignment GetEffectiveTextAlignment(NSAttributedString* string) { |
+ DCHECK(string.length); |
+ NSTextAlignment alignment = NSTextAlignmentLeft; |
+ NSParagraphStyle* style = [string attribute:NSParagraphStyleAttributeName |
+ atIndex:0 |
+ effectiveRange:nullptr]; |
+ if (style) { |
+ alignment = style.alignment; |
+ if (alignment == NSTextAlignmentNatural || |
+ alignment == NSTextAlignmentJustified) { |
+ NSWritingDirection direction = GetEffectiveWritingDirection(string); |
+ alignment = direction == NSWritingDirectionRightToLeft |
+ ? NSTextAlignmentRight |
+ : NSTextAlignmentLeft; |
+ } |
+ } |
+ return alignment; |
+} |
+ |
+NSWritingDirection GetEffectiveWritingDirection(NSAttributedString* string) { |
+ DCHECK(string.length); |
+ // Search for unicode bidirectionality characters within |string|. |
+ NSWritingDirection direction = |
+ unicode_util::UnicodeWritingDirectionForString(string.string); |
+ if (direction == NSWritingDirectionNatural) { |
+ // If there are no characters with bidirectionality information, default to |
+ // NSWritingDirectionLeftToRight. |
+ direction = NSWritingDirectionLeftToRight; |
+ } |
+ NSParagraphStyle* style = [string attribute:NSParagraphStyleAttributeName |
+ atIndex:0 |
+ effectiveRange:nullptr]; |
+ if (style && style.baseWritingDirection != NSWritingDirectionNatural) { |
+ // Use the NSParagraphStyle's writing direction if specified. |
+ direction = style.baseWritingDirection; |
+ } |
+ return direction; |
+} |
+ |
+} // namespace core_text_util |