blob: e937bd5f33c70829451f2be7c149c791e1af81c3 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h"
#include <stddef.h>
#include <algorithm>
#include <cmath>
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "base/mac/foundation_util.h"
#include "base/mac/objc_release_properties.h"
#include "base/mac/scoped_nsobject.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
#include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#include "chrome/grit/generated_resources.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_popup_model.h"
#include "components/omnibox/browser/suggestion_answer.h"
#include "skia/ext/skia_utils_mac.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/font.h"
namespace {
// Extra padding beyond the vertical text padding.
constexpr CGFloat kMaterialExtraVerticalImagePadding = 2.0;
constexpr CGFloat kMaterialTextStartOffset = 27.0;
constexpr CGFloat kMaterialImageXOffset = 6.0;
constexpr CGFloat kDefaultVerticalMargin = 3.0;
constexpr CGFloat kDefaultTextHeight = 19;
// Returns the margin that should appear at the top and bottom of the result.
CGFloat GetVerticalMargin() {
return base::GetFieldTrialParamByFeatureAsInt(
omnibox::kUIExperimentVerticalMargin,
OmniboxFieldTrial::kUIVerticalMarginParam, kDefaultVerticalMargin);
}
// Flips the given |rect| in context of the given |frame|.
NSRect FlipIfRTL(NSRect rect, NSRect frame) {
DCHECK_LE(NSMinX(frame), NSMinX(rect));
DCHECK_GE(NSMaxX(frame), NSMaxX(rect));
if (base::i18n::IsRTL()) {
NSRect result = rect;
result.origin.x = NSMinX(frame) + (NSMaxX(frame) - NSMaxX(rect));
return result;
}
return rect;
}
NSColor* SelectedBackgroundColor(BOOL is_dark_theme) {
return is_dark_theme
? skia::SkColorToSRGBNSColor(SkColorSetA(SK_ColorWHITE, 0x14))
: skia::SkColorToSRGBNSColor(SkColorSetA(SK_ColorBLACK, 0x14));
}
NSColor* HoveredBackgroundColor(BOOL is_dark_theme) {
return is_dark_theme
? skia::SkColorToSRGBNSColor(SkColorSetA(SK_ColorWHITE, 0x0D))
: [NSColor controlHighlightColor];
}
NSColor* ContentTextColor(BOOL is_dark_theme) {
return is_dark_theme ? [NSColor whiteColor] : [NSColor blackColor];
}
NSColor* DimTextColor(BOOL is_dark_theme) {
return is_dark_theme
? skia::SkColorToSRGBNSColor(SkColorSetA(SK_ColorWHITE, 0x7F))
: skia::SkColorToSRGBNSColor(SkColorSetRGB(0x64, 0x64, 0x64));
}
NSColor* InvisibleTextColor() {
return skia::SkColorToSRGBNSColor(SK_ColorTRANSPARENT);
}
NSColor* PositiveTextColor() {
return skia::SkColorToSRGBNSColor(SkColorSetRGB(0x3d, 0x94, 0x00));
}
NSColor* NegativeTextColor() {
return skia::SkColorToSRGBNSColor(SkColorSetRGB(0xdd, 0x4b, 0x39));
}
NSColor* URLTextColor(BOOL is_dark_theme) {
return is_dark_theme ? skia::SkColorToSRGBNSColor(gfx::kGoogleBlue300)
: skia::SkColorToSRGBNSColor(gfx::kGoogleBlue700);
}
NSFont* FieldFont() {
return OmniboxViewMac::GetNormalFieldFont();
}
NSFont* BoldFieldFont() {
return OmniboxViewMac::GetBoldFieldFont();
}
NSFont* LargeFont() {
return OmniboxViewMac::GetLargeFont();
}
NSFont* LargeSuperscriptFont() {
NSFont* font = OmniboxViewMac::GetLargeFont();
// Calculate a slightly smaller font. The ratio here is somewhat arbitrary.
// Proportions from 5/9 to 5/7 all look pretty good.
CGFloat size = [font pointSize] * 5.0 / 9.0;
NSFontDescriptor* descriptor = [font fontDescriptor];
return [NSFont fontWithDescriptor:descriptor size:size];
}
// Sets the writing direction to |direction| for a given |range| of
// |attributedString|.
void SetTextDirectionForRange(NSMutableAttributedString* attributedString,
NSWritingDirection direction,
NSRange range) {
[attributedString
enumerateAttribute:NSParagraphStyleAttributeName
inRange:range
options:0
usingBlock:^(id paragraph_style, NSRange range, BOOL* stop) {
[paragraph_style setBaseWritingDirection:direction];
}];
}
NSAttributedString* CreateAnswerStringHelper(const base::string16& text,
NSInteger style_type,
bool is_bold,
BOOL is_dark_theme) {
NSDictionary* answer_style = nil;
NSFont* answer_font = nil;
switch (style_type) {
case SuggestionAnswer::TOP_ALIGNED:
answer_style = @{
NSForegroundColorAttributeName : DimTextColor(is_dark_theme),
NSFontAttributeName : LargeSuperscriptFont(),
NSSuperscriptAttributeName : @1
};
break;
case SuggestionAnswer::DESCRIPTION_NEGATIVE:
answer_style = @{
NSForegroundColorAttributeName : NegativeTextColor(),
NSFontAttributeName : LargeSuperscriptFont()
};
break;
case SuggestionAnswer::DESCRIPTION_POSITIVE:
answer_style = @{
NSForegroundColorAttributeName : PositiveTextColor(),
NSFontAttributeName : LargeSuperscriptFont()
};
break;
case SuggestionAnswer::PERSONALIZED_SUGGESTION:
answer_style = @{
NSForegroundColorAttributeName : ContentTextColor(is_dark_theme),
NSFontAttributeName : FieldFont()
};
break;
case SuggestionAnswer::ANSWER_TEXT_MEDIUM:
answer_style = @{
NSForegroundColorAttributeName : DimTextColor(is_dark_theme),
NSFontAttributeName : FieldFont()
};
break;
case SuggestionAnswer::ANSWER_TEXT_LARGE:
answer_style = @{
NSForegroundColorAttributeName : DimTextColor(is_dark_theme),
NSFontAttributeName : LargeFont()
};
break;
case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_SMALL:
answer_font = FieldFont();
answer_style = @{
NSForegroundColorAttributeName : DimTextColor(is_dark_theme),
NSFontAttributeName : answer_font
};
break;
case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_MEDIUM:
answer_font = LargeSuperscriptFont();
answer_style = @{
NSForegroundColorAttributeName : DimTextColor(is_dark_theme),
NSFontAttributeName : answer_font
};
break;
case SuggestionAnswer::SUGGESTION: // Fall through.
default:
answer_style = @{
NSForegroundColorAttributeName : ContentTextColor(is_dark_theme),
NSFontAttributeName : FieldFont()
};
break;
}
if (is_bold) {
NSMutableDictionary* bold_style = [answer_style mutableCopy];
// TODO(dschuyler): Account for bolding fonts other than FieldFont.
// Field font is the only one currently necessary to bold.
[bold_style setObject:BoldFieldFont() forKey:NSFontAttributeName];
answer_style = bold_style;
}
return [[[NSAttributedString alloc]
initWithString:base::SysUTF16ToNSString(text)
attributes:answer_style] autorelease];
}
NSAttributedString* CreateAnswerString(const base::string16& text,
NSInteger style_type,
BOOL is_dark_theme) {
// TODO(dschuyler): make this better. Right now this only supports unnested
// bold tags. In the future we'll need to flag unexpected tags while adding
// support for b, i, u, sub, and sup. We'll also need to support HTML
// entities (&lt; for '<', etc.).
const base::string16 begin_tag = base::ASCIIToUTF16("<b>");
const base::string16 end_tag = base::ASCIIToUTF16("</b>");
size_t begin = 0;
base::scoped_nsobject<NSMutableAttributedString> result(
[[NSMutableAttributedString alloc] init]);
while (true) {
size_t end = text.find(begin_tag, begin);
if (end == base::string16::npos) {
[result appendAttributedString:CreateAnswerStringHelper(
text.substr(begin), style_type, false,
is_dark_theme)];
break;
}
[result appendAttributedString:CreateAnswerStringHelper(
text.substr(begin, end - begin),
style_type, false, is_dark_theme)];
begin = end + begin_tag.length();
end = text.find(end_tag, begin);
if (end == base::string16::npos)
break;
[result appendAttributedString:CreateAnswerStringHelper(
text.substr(begin, end - begin),
style_type, true, is_dark_theme)];
begin = end + end_tag.length();
}
return result.autorelease();
}
NSAttributedString* CreateAnswerLine(const SuggestionAnswer::ImageLine& line,
BOOL is_dark_theme) {
base::scoped_nsobject<NSMutableAttributedString> answer_string(
[[NSMutableAttributedString alloc] init]);
DCHECK(!line.text_fields().empty());
for (const SuggestionAnswer::TextField& text_field : line.text_fields()) {
[answer_string appendAttributedString:CreateAnswerString(text_field.text(),
text_field.type(),
is_dark_theme)];
}
const base::string16 space(base::ASCIIToUTF16(" "));
const SuggestionAnswer::TextField* text_field = line.additional_text();
if (text_field) {
[answer_string
appendAttributedString:CreateAnswerString(space + text_field->text(),
text_field->type(),
is_dark_theme)];
}
text_field = line.status_text();
if (text_field) {
[answer_string
appendAttributedString:CreateAnswerString(space + text_field->text(),
text_field->type(),
is_dark_theme)];
}
base::scoped_nsobject<NSMutableParagraphStyle> style(
[[NSMutableParagraphStyle alloc] init]);
[style setTighteningFactorForTruncation:0.0];
[answer_string addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, [answer_string length])];
return answer_string.autorelease();
}
NSMutableAttributedString* CreateAttributedString(
const base::string16& text,
NSColor* text_color,
NSTextAlignment textAlignment) {
// Start out with a string using the default style info.
NSString* s = base::SysUTF16ToNSString(text);
NSDictionary* attributes = @{
NSFontAttributeName : FieldFont(),
NSForegroundColorAttributeName : text_color
};
NSMutableAttributedString* attributedString = [[
[NSMutableAttributedString alloc] initWithString:s
attributes:attributes] autorelease];
NSMutableParagraphStyle* style =
[[[NSMutableParagraphStyle alloc] init] autorelease];
[style setTighteningFactorForTruncation:0.0];
[style setAlignment:textAlignment];
if (@available(macOS 10.11, *))
[style setAllowsDefaultTighteningForTruncation:NO];
[attributedString addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, [attributedString length])];
return attributedString;
}
NSMutableAttributedString* CreateAttributedString(
const base::string16& text,
NSColor* text_color) {
return CreateAttributedString(text, text_color, NSNaturalTextAlignment);
}
NSAttributedString* CreateClassifiedAttributedString(
const base::string16& text,
NSColor* text_color,
const ACMatchClassifications& classifications,
BOOL is_dark_theme) {
NSMutableAttributedString* attributedString =
CreateAttributedString(text, text_color);
NSUInteger match_length = [attributedString length];
// Mark up the runs which differ from the default.
for (ACMatchClassifications::const_iterator i = classifications.begin();
i != classifications.end(); ++i) {
const bool is_last = ((i + 1) == classifications.end());
const NSUInteger next_offset =
(is_last ? match_length : static_cast<NSUInteger>((i + 1)->offset));
const NSUInteger location = static_cast<NSUInteger>(i->offset);
const NSUInteger length = next_offset - static_cast<NSUInteger>(i->offset);
// Guard against bad, off-the-end classification ranges.
if (location >= match_length || length <= 0)
break;
const NSRange range =
NSMakeRange(location, std::min(length, match_length - location));
if (0 != (i->style & ACMatchClassification::MATCH)) {
[attributedString addAttribute:NSFontAttributeName
value:BoldFieldFont()
range:range];
}
if (0 != (i->style & ACMatchClassification::URL)) {
// URLs have their text direction set to to LTR (avoids RTL characters
// making the URL render from right to left, as per RFC 3987 Section 4.1).
SetTextDirectionForRange(attributedString, NSWritingDirectionLeftToRight,
range);
[attributedString addAttribute:NSForegroundColorAttributeName
value:URLTextColor(is_dark_theme)
range:range];
} else if (0 != (i->style & ACMatchClassification::DIM)) {
[attributedString addAttribute:NSForegroundColorAttributeName
value:DimTextColor(is_dark_theme)
range:range];
} else if (0 != (i->style & ACMatchClassification::INVISIBLE)) {
[attributedString addAttribute:NSForegroundColorAttributeName
value:InvisibleTextColor()
range:range];
}
}
return attributedString;
}
} // namespace
@interface OmniboxPopupCellData ()
@end
@interface OmniboxPopupCell ()
- (CGFloat)drawMatchPart:(NSAttributedString*)attributedString
withFrame:(NSRect)cellFrame
origin:(NSPoint)origin
withMaxWidth:(int)maxWidth
forDarkTheme:(BOOL)isDarkTheme;
- (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
@end
@implementation OmniboxPopupCellData
@synthesize contents = contents_;
@synthesize description = description_;
@synthesize prefix = prefix_;
@synthesize image = image_;
@synthesize answerImage = answerImage_;
@synthesize isContentsRTL = isContentsRTL_;
@synthesize isAnswer = isAnswer_;
@synthesize matchType = matchType_;
@synthesize maxLines = maxLines_;
- (instancetype)initWithMatch:(const AutocompleteMatch&)match
image:(NSImage*)image
answerImage:(NSImage*)answerImage
forDarkTheme:(BOOL)isDarkTheme {
if ((self = [super init])) {
image_ = [image retain];
answerImage_ = [answerImage retain];
isContentsRTL_ =
(base::i18n::RIGHT_TO_LEFT ==
base::i18n::GetFirstStrongCharacterDirection(match.contents));
matchType_ = match.type;
// Prefix may not have any characters with strong directionality, and may
// take the UI directionality. But prefix needs to appear in continuation
// of the contents so we force the directionality.
NSTextAlignment textAlignment =
isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment;
prefix_ = [CreateAttributedString(
base::UTF8ToUTF16(
match.GetAdditionalInfo(kACMatchPropertyContentsPrefix)),
ContentTextColor(isDarkTheme), textAlignment) retain];
isAnswer_ = !!match.answer;
if (isAnswer_) {
contents_ =
[CreateAnswerLine(match.answer->first_line(), isDarkTheme) retain];
description_ =
[CreateAnswerLine(match.answer->second_line(), isDarkTheme) retain];
maxLines_ = match.answer->second_line().num_text_lines();
} else {
contents_ = [CreateClassifiedAttributedString(
match.contents, ContentTextColor(isDarkTheme), match.contents_class,
isDarkTheme) retain];
if (!match.description.empty()) {
// Swap the contents and description of non-search suggestions in
// vertical layouts.
BOOL swapMatchText = (base::FeatureList::IsEnabled(
omnibox::kUIExperimentVerticalLayout) ||
base::FeatureList::IsEnabled(
omnibox::kUIExperimentSwapTitleAndUrl)) &&
!AutocompleteMatch::IsSearchType(match.type);
description_ = [CreateClassifiedAttributedString(
match.description,
swapMatchText ? ContentTextColor(isDarkTheme)
: DimTextColor(isDarkTheme),
match.description_class, isDarkTheme) retain];
if (swapMatchText)
std::swap(contents_, description_);
}
}
}
return self;
}
- (void)dealloc {
base::mac::ReleaseProperties(self);
[super dealloc];
}
- (instancetype)copyWithZone:(NSZone*)zone {
return [self retain];
}
- (CGFloat)getMatchContentsWidth {
return [contents_ size].width;
}
@end
@implementation OmniboxPopupCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
OmniboxPopupMatrix* matrix =
base::mac::ObjCCastStrict<OmniboxPopupMatrix>(controlView);
BOOL isDarkTheme = [matrix hasDarkTheme];
if ([self state] == NSOnState || [self isHighlighted]) {
if ([self state] == NSOnState) {
[SelectedBackgroundColor(isDarkTheme) set];
} else {
[HoveredBackgroundColor(isDarkTheme) set];
}
NSRectFillUsingOperation(cellFrame, NSCompositeSourceOver);
}
[self drawMatchWithFrame:cellFrame inView:controlView];
}
- (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
bool isVerticalLayout =
base::FeatureList::IsEnabled(omnibox::kUIExperimentVerticalLayout);
OmniboxPopupCellData* cellData =
base::mac::ObjCCastStrict<OmniboxPopupCellData>([self objectValue]);
OmniboxPopupMatrix* tableView =
base::mac::ObjCCastStrict<OmniboxPopupMatrix>(controlView);
CGFloat remainingWidth =
[OmniboxPopupCell getTextContentAreaWidth:[tableView contentMaxWidth]];
CGFloat contentsWidth = [cellData getMatchContentsWidth];
CGFloat separatorWidth = [[tableView separator] size].width;
CGFloat descriptionWidth =
[cellData description] ? [[cellData description] size].width : 0;
int contentsMaxWidth, descriptionMaxWidth;
OmniboxPopupModel::ComputeMatchMaxWidths(
ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth),
ceilf(remainingWidth), [cellData isAnswer] || isVerticalLayout,
!AutocompleteMatch::IsSearchType([cellData matchType]), &contentsMaxWidth,
&descriptionMaxWidth);
CGFloat halfLineHeight = (kDefaultTextHeight + kDefaultVerticalMargin) / 2;
NSWindow* parentWindow = [[controlView window] parentWindow];
BOOL isDarkTheme = [parentWindow hasDarkTheme];
NSRect imageRect = cellFrame;
imageRect.size = [[cellData image] size];
imageRect.origin.x += kMaterialImageXOffset + [tableView contentLeftPadding];
imageRect.origin.y +=
GetVerticalMargin() + kMaterialExtraVerticalImagePadding;
if (isVerticalLayout)
imageRect.origin.y += halfLineHeight;
[[cellData image] drawInRect:FlipIfRTL(imageRect, cellFrame)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
CGFloat left = kMaterialTextStartOffset + [tableView contentLeftPadding];
NSPoint origin = NSMakePoint(left, GetVerticalMargin());
// For matches lacking description in vertical layout, center vertically.
if (isVerticalLayout && descriptionMaxWidth == 0)
origin.y += halfLineHeight;
origin.x += [self drawMatchPart:[cellData contents]
withFrame:cellFrame
origin:origin
withMaxWidth:contentsMaxWidth
forDarkTheme:isDarkTheme];
if (descriptionMaxWidth > 0) {
if ([cellData isAnswer]) {
origin = NSMakePoint(
left, [OmniboxPopupCell getContentTextHeightForDoubleLine:NO] -
GetVerticalMargin());
CGFloat imageSize = [tableView answerLineHeight];
NSRect imageRect =
NSMakeRect(NSMinX(cellFrame) + origin.x, NSMinY(cellFrame) + origin.y,
imageSize, imageSize);
[[cellData answerImage] drawInRect:FlipIfRTL(imageRect, cellFrame)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
if ([cellData answerImage]) {
origin.x += imageSize + kMaterialImageXOffset;
// Have to nudge the baseline down 1pt in Material Design for the text
// that follows, so that it's the same as the bottom of the image.
origin.y += 1;
}
} else {
if (isVerticalLayout) {
origin.x = left;
origin.y += halfLineHeight * 2;
} else {
origin.x += [self drawMatchPart:[tableView separator]
withFrame:cellFrame
origin:origin
withMaxWidth:separatorWidth
forDarkTheme:isDarkTheme];
}
}
[self drawMatchPart:[cellData description]
withFrame:cellFrame
origin:origin
withMaxWidth:descriptionMaxWidth
forDarkTheme:isDarkTheme];
}
}
- (CGFloat)drawMatchPart:(NSAttributedString*)attributedString
withFrame:(NSRect)cellFrame
origin:(NSPoint)origin
withMaxWidth:(int)maxWidth
forDarkTheme:(BOOL)isDarkTheme {
NSRect renderRect = NSIntersectionRect(
cellFrame, NSOffsetRect(cellFrame, origin.x, origin.y));
renderRect.size.width =
std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth));
renderRect.size.height =
std::min(NSHeight(renderRect), [attributedString size].height);
if (!NSIsEmptyRect(renderRect)) {
[attributedString drawWithRect:FlipIfRTL(renderRect, cellFrame)
options:NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingTruncatesLastVisibleLine];
}
return NSWidth(renderRect);
}
+ (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match {
const base::string16& inputText = base::UTF8ToUTF16(
match.GetAdditionalInfo(kACMatchPropertySuggestionText));
int contentsStartIndex = 0;
base::StringToInt(
match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex),
&contentsStartIndex);
// Ignore invalid state.
if (!base::StartsWith(match.fill_into_edit, inputText,
base::CompareCase::SENSITIVE) ||
!base::EndsWith(match.fill_into_edit, match.contents,
base::CompareCase::SENSITIVE) ||
((size_t)contentsStartIndex >= inputText.length())) {
return 0;
}
bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT ==
base::i18n::GetFirstStrongCharacterDirection(match.contents));
// Color does not matter.
NSAttributedString* attributedString =
CreateAttributedString(inputText, DimTextColor(false));
base::scoped_nsobject<NSTextStorage> textStorage(
[[NSTextStorage alloc] initWithAttributedString:attributedString]);
base::scoped_nsobject<NSLayoutManager> layoutManager(
[[NSLayoutManager alloc] init]);
base::scoped_nsobject<NSTextContainer> textContainer(
[[NSTextContainer alloc] init]);
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex);
NSUInteger glyphIndex =
[layoutManager glyphIndexForCharacterAtIndex:charIndex];
// This offset is computed from the left edge of the glyph always from the
// left edge of the string, irrespective of the directionality of UI or text.
CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x;
CGFloat inputWidth = [attributedString size].width;
// The offset obtained above may need to be corrected because the left-most
// glyph may not have 0 offset. So we find the offset of left-most glyph, and
// subtract it from the offset of the glyph we obtained above.
CGFloat minOffset = glyphOffset;
// If content is RTL, we are interested in the right-edge of the glyph.
// Unfortunately the bounding rect computation methods from NSLayoutManager or
// NSFont don't work correctly with bidirectional text. So we compute the
// glyph width by finding the closest glyph offset to the right of the glyph
// we are looking for.
CGFloat glyphWidth = inputWidth;
for (NSUInteger i = 0; i < [attributedString length]; i++) {
if (i == charIndex) continue;
glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i];
CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x;
minOffset = std::min(minOffset, offset);
if (offset > glyphOffset)
glyphWidth = std::min(glyphWidth, offset - glyphOffset);
}
glyphOffset -= minOffset;
if (glyphWidth == 0)
glyphWidth = inputWidth - glyphOffset;
if (isContentsRTL)
glyphOffset += glyphWidth;
return base::i18n::IsRTL() ? (inputWidth - glyphOffset) : glyphOffset;
}
+ (NSAttributedString*)createSeparatorStringForDarkTheme:(BOOL)isDarkTheme {
base::string16 raw_separator =
l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
return CreateAttributedString(raw_separator, DimTextColor(isDarkTheme));
}
+ (CGFloat)getTextContentAreaWidth:(CGFloat)cellContentMaxWidth {
return cellContentMaxWidth - kMaterialTextStartOffset;
}
+ (CGFloat)getContentTextHeightForDoubleLine:(BOOL)isDoubleLine {
CGFloat height = kDefaultTextHeight + 2 * GetVerticalMargin();
if (isDoubleLine)
height += kDefaultTextHeight + kDefaultVerticalMargin;
return height;
}
@end