cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / omnibox / omnibox_popup_cell.mm
blob6a06929781dcb160c9d7a9acfcb69f9932210a47
1 // Copyright 2013 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.
5 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h"
7 #include <algorithm>
8 #include <cmath>
10 #include "base/i18n/rtl.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/scoped_nsobject.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
18 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/omnibox/browser/omnibox_popup_model.h"
21 #include "components/omnibox/browser/suggestion_answer.h"
22 #include "skia/ext/skia_utils_mac.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/gfx/font.h"
26 namespace {
28 // How far to offset image column from the left.
29 const CGFloat kImageXOffset = 5.0;
31 // How far to offset image and text.
32 const CGFloat kPaddingOffset = 3.0;
34 // How far to offset the text column from the left.
35 const CGFloat kTextStartOffset = 28.0;
37 // Rounding radius of selection and hover background on popup items.
38 const CGFloat kCellRoundingRadius = 2.0;
40 // Flips the given |rect| in context of the given |frame|.
41 NSRect FlipIfRTL(NSRect rect, NSRect frame) {
42   DCHECK_LE(NSMinX(frame), NSMinX(rect));
43   DCHECK_GE(NSMaxX(frame), NSMaxX(rect));
44   if (base::i18n::IsRTL()) {
45     NSRect result = rect;
46     result.origin.x = NSMinX(frame) + (NSMaxX(frame) - NSMaxX(rect));
47     return result;
48   }
49   return rect;
52 NSColor* SelectedBackgroundColor() {
53   return [NSColor selectedControlColor];
55 NSColor* HoveredBackgroundColor() {
56   return [NSColor controlHighlightColor];
59 NSColor* ContentTextColor() {
60   return [NSColor blackColor];
62 NSColor* DimTextColor() {
63   return [NSColor darkGrayColor];
65 NSColor* PositiveTextColor() {
66   return gfx::SkColorToCalibratedNSColor(SkColorSetRGB(0x0b, 0x80, 0x43));
68 NSColor* NegativeTextColor() {
69   return gfx::SkColorToCalibratedNSColor(SkColorSetRGB(0xc5, 0x39, 0x29));
71 NSColor* URLTextColor() {
72   return [NSColor colorWithCalibratedRed:0.0 green:0.55 blue:0.0 alpha:1.0];
75 NSFont* FieldFont() {
76   return OmniboxViewMac::GetFieldFont(gfx::Font::NORMAL);
78 NSFont* BoldFieldFont() {
79   return OmniboxViewMac::GetFieldFont(gfx::Font::BOLD);
81 NSFont* LargeFont() {
82   return OmniboxViewMac::GetLargeFont(gfx::Font::NORMAL);
84 NSFont* LargeSuperscriptFont() {
85   NSFont* font = OmniboxViewMac::GetLargeFont(gfx::Font::NORMAL);
86   // Calculate a slightly smaller font. The ratio here is somewhat arbitrary.
87   // Proportions from 5/9 to 5/7 all look pretty good.
88   CGFloat size = [font pointSize] * 5.0 / 9.0;
89   NSFontDescriptor* descriptor = [font fontDescriptor];
90   return [NSFont fontWithDescriptor:descriptor size:size];
92 NSFont* SmallFont() {
93   return OmniboxViewMac::GetSmallFont(gfx::Font::NORMAL);
96 CGFloat GetContentAreaWidth(NSRect cellFrame) {
97   return NSWidth(cellFrame) - kTextStartOffset;
100 NSAttributedString* CreateAnswerStringHelper(const base::string16& text,
101                                              NSInteger style_type,
102                                              bool is_bold) {
103   NSDictionary* answer_style = nil;
104   switch (style_type) {
105     case SuggestionAnswer::ANSWER:
106       answer_style = @{
107         NSForegroundColorAttributeName : ContentTextColor(),
108         NSFontAttributeName : LargeFont()
109       };
110       break;
111     case SuggestionAnswer::HEADLINE:
112       answer_style = @{
113         NSForegroundColorAttributeName : DimTextColor(),
114         NSFontAttributeName : LargeFont()
115       };
116       break;
117     case SuggestionAnswer::TOP_ALIGNED:
118       answer_style = @{
119         NSForegroundColorAttributeName : DimTextColor(),
120         NSFontAttributeName : LargeSuperscriptFont(),
121         NSSuperscriptAttributeName : @1
122       };
123       break;
124     case SuggestionAnswer::DESCRIPTION:
125       answer_style = @{
126         NSForegroundColorAttributeName : DimTextColor(),
127         NSFontAttributeName : FieldFont()
128       };
129       break;
130     case SuggestionAnswer::DESCRIPTION_NEGATIVE:
131       answer_style = @{
132         NSForegroundColorAttributeName : NegativeTextColor(),
133         NSFontAttributeName : LargeSuperscriptFont()
134       };
135       break;
136     case SuggestionAnswer::DESCRIPTION_POSITIVE:
137       answer_style = @{
138         NSForegroundColorAttributeName : PositiveTextColor(),
139         NSFontAttributeName : LargeSuperscriptFont()
140       };
141       break;
142     case SuggestionAnswer::MORE_INFO:
143       answer_style = @{
144         NSForegroundColorAttributeName : DimTextColor(),
145         NSFontAttributeName : SmallFont()
146       };
147       break;
148     case SuggestionAnswer::SUGGESTION:
149       answer_style = @{
150         NSForegroundColorAttributeName : ContentTextColor(),
151         NSFontAttributeName : FieldFont()
152       };
153       break;
154     case SuggestionAnswer::SUGGESTION_POSITIVE:
155       answer_style = @{
156         NSForegroundColorAttributeName : PositiveTextColor(),
157         NSFontAttributeName : FieldFont()
158       };
159       break;
160     case SuggestionAnswer::SUGGESTION_NEGATIVE:
161       answer_style = @{
162         NSForegroundColorAttributeName : NegativeTextColor(),
163         NSFontAttributeName : FieldFont()
164       };
165       break;
166     case SuggestionAnswer::SUGGESTION_LINK:
167       answer_style = @{
168         NSForegroundColorAttributeName : URLTextColor(),
169         NSFontAttributeName : FieldFont()
170       };
171       break;
172     case SuggestionAnswer::STATUS:
173       answer_style = @{
174         NSForegroundColorAttributeName : DimTextColor(),
175         NSFontAttributeName : LargeSuperscriptFont()
176       };
177       break;
178     case SuggestionAnswer::PERSONALIZED_SUGGESTION:
179       answer_style = @{
180         NSForegroundColorAttributeName : ContentTextColor(),
181         NSFontAttributeName : FieldFont()
182       };
183       break;
184   }
186   if (is_bold) {
187     NSMutableDictionary* bold_style = [answer_style mutableCopy];
188     // TODO(dschuyler): Account for bolding fonts other than FieldFont.
189     // Field font is the only one currently necessary to bold.
190     [bold_style setObject:BoldFieldFont() forKey:NSFontAttributeName];
191     answer_style = bold_style;
192   }
194   return [[[NSAttributedString alloc]
195       initWithString:base::SysUTF16ToNSString(text)
196           attributes:answer_style] autorelease];
199 NSAttributedString* CreateAnswerString(const base::string16& text,
200                                        NSInteger style_type) {
201   // TODO(dschuyler): make this better.  Right now this only supports unnested
202   // bold tags.  In the future we'll need to flag unexpected tags while adding
203   // support for b, i, u, sub, and sup.  We'll also need to support HTML
204   // entities (&lt; for '<', etc.).
205   const base::string16 begin_tag = base::ASCIIToUTF16("<b>");
206   const base::string16 end_tag = base::ASCIIToUTF16("</b>");
207   size_t begin = 0;
208   base::scoped_nsobject<NSMutableAttributedString> result(
209       [[NSMutableAttributedString alloc] init]);
210   while (true) {
211     size_t end = text.find(begin_tag, begin);
212     if (end == base::string16::npos) {
213       [result
214           appendAttributedString:CreateAnswerStringHelper(
215                                          text.substr(begin),
216                                          style_type, false)];
217       break;
218     }
219     [result appendAttributedString:CreateAnswerStringHelper(
220                                        text.substr(begin, end - begin),
221                                        style_type, false)];
222     begin = end + begin_tag.length();
223     end = text.find(end_tag, begin);
224     if (end == base::string16::npos)
225       break;
226     [result appendAttributedString:CreateAnswerStringHelper(
227                                        text.substr(begin, end - begin),
228                                        style_type, true)];
229     begin = end + end_tag.length();
230   }
231   return result.autorelease();
234 NSAttributedString* CreateAnswerLine(const SuggestionAnswer::ImageLine& line) {
235   base::scoped_nsobject<NSMutableAttributedString> answer_string(
236       [[NSMutableAttributedString alloc] init]);
237   DCHECK(!line.text_fields().empty());
238   for (const SuggestionAnswer::TextField& text_field : line.text_fields()) {
239     [answer_string
240         appendAttributedString:CreateAnswerString(text_field.text(),
241                                                   text_field.type())];
242   }
243   const base::string16 space(base::ASCIIToUTF16(" "));
244   const SuggestionAnswer::TextField* text_field = line.additional_text();
245   if (text_field) {
246     [answer_string
247         appendAttributedString:CreateAnswerString(space + text_field->text(),
248                                                   text_field->type())];
249   }
250   text_field = line.status_text();
251   if (text_field) {
252     [answer_string
253         appendAttributedString:CreateAnswerString(space + text_field->text(),
254                                                   text_field->type())];
255   }
256   base::scoped_nsobject<NSMutableParagraphStyle> style(
257       [[NSMutableParagraphStyle alloc] init]);
258   [style setLineBreakMode:NSLineBreakByTruncatingTail];
259   [style setTighteningFactorForTruncation:0.0];
260   [answer_string addAttribute:NSParagraphStyleAttributeName
261                             value:style
262                             range:NSMakeRange(0, [answer_string length])];
263   return answer_string.autorelease();
266 NSMutableAttributedString* CreateAttributedString(
267     const base::string16& text,
268     NSColor* text_color,
269     NSTextAlignment textAlignment) {
270   // Start out with a string using the default style info.
271   NSString* s = base::SysUTF16ToNSString(text);
272   NSDictionary* attributes = @{
273       NSFontAttributeName : FieldFont(),
274       NSForegroundColorAttributeName : text_color
275   };
276   NSMutableAttributedString* attributedString = [[
277       [NSMutableAttributedString alloc] initWithString:s
278                                             attributes:attributes] autorelease];
280   NSMutableParagraphStyle* style =
281       [[[NSMutableParagraphStyle alloc] init] autorelease];
282   [style setLineBreakMode:NSLineBreakByTruncatingTail];
283   [style setTighteningFactorForTruncation:0.0];
284   [style setAlignment:textAlignment];
285   [attributedString addAttribute:NSParagraphStyleAttributeName
286                            value:style
287                            range:NSMakeRange(0, [attributedString length])];
289   return attributedString;
292 NSMutableAttributedString* CreateAttributedString(
293     const base::string16& text,
294     NSColor* text_color) {
295   return CreateAttributedString(text, text_color, NSNaturalTextAlignment);
298 NSAttributedString* CreateClassifiedAttributedString(
299     const base::string16& text,
300     NSColor* text_color,
301     const ACMatchClassifications& classifications) {
302   NSMutableAttributedString* attributedString =
303       CreateAttributedString(text, text_color);
304   NSUInteger match_length = [attributedString length];
306   // Mark up the runs which differ from the default.
307   for (ACMatchClassifications::const_iterator i = classifications.begin();
308        i != classifications.end(); ++i) {
309     const bool is_last = ((i + 1) == classifications.end());
310     const NSUInteger next_offset =
311         (is_last ? match_length : static_cast<NSUInteger>((i + 1)->offset));
312     const NSUInteger location = static_cast<NSUInteger>(i->offset);
313     const NSUInteger length = next_offset - static_cast<NSUInteger>(i->offset);
314     // Guard against bad, off-the-end classification ranges.
315     if (location >= match_length || length <= 0)
316       break;
317     const NSRange range =
318         NSMakeRange(location, std::min(length, match_length - location));
320     if (0 != (i->style & ACMatchClassification::MATCH)) {
321       [attributedString addAttribute:NSFontAttributeName
322                                value:BoldFieldFont()
323                                range:range];
324     }
326     if (0 != (i->style & ACMatchClassification::URL)) {
327       [attributedString addAttribute:NSForegroundColorAttributeName
328                                value:URLTextColor()
329                                range:range];
330     } else if (0 != (i->style & ACMatchClassification::DIM)) {
331       [attributedString addAttribute:NSForegroundColorAttributeName
332                                value:DimTextColor()
333                                range:range];
334     }
335   }
337   return attributedString;
340 }  // namespace
342 @interface OmniboxPopupCell ()
343 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString
344                withFrame:(NSRect)cellFrame
345                   origin:(NSPoint)origin
346             withMaxWidth:(int)maxWidth;
347 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame
348                           tableView:(OmniboxPopupMatrix*)tableView
349                withContentsMaxWidth:(int*)contentsMaxWidth;
350 - (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
351 @end
353 @implementation OmniboxPopupCellData
355 @synthesize contents = contents_;
356 @synthesize description = description_;
357 @synthesize prefix = prefix_;
358 @synthesize image = image_;
359 @synthesize answerImage = answerImage_;
360 @synthesize contentsOffset = contentsOffset_;
361 @synthesize isContentsRTL = isContentsRTL_;
362 @synthesize isAnswer = isAnswer_;
363 @synthesize matchType = matchType_;
365 - (instancetype)initWithMatch:(const AutocompleteMatch&)match
366                contentsOffset:(CGFloat)contentsOffset
367                         image:(NSImage*)image
368                   answerImage:(NSImage*)answerImage {
369   if ((self = [super init])) {
370     image_ = [image retain];
371     answerImage_ = [answerImage retain];
372     contentsOffset_ = contentsOffset;
374     isContentsRTL_ =
375         (base::i18n::RIGHT_TO_LEFT ==
376          base::i18n::GetFirstStrongCharacterDirection(match.contents));
377     matchType_ = match.type;
379     // Prefix may not have any characters with strong directionality, and may
380     // take the UI directionality. But prefix needs to appear in continuation
381     // of the contents so we force the directionality.
382     NSTextAlignment textAlignment =
383         isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment;
384     prefix_ =
385         [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo(
386                                     kACMatchPropertyContentsPrefix)),
387                                 ContentTextColor(), textAlignment) retain];
389     isAnswer_ = match.answer;
390     if (isAnswer_) {
391       contents_ = [CreateAnswerLine(match.answer->first_line()) retain];
392       description_ = [CreateAnswerLine(match.answer->second_line()) retain];
393     } else {
394       contents_ = [CreateClassifiedAttributedString(
395           match.contents, ContentTextColor(), match.contents_class) retain];
396       if (!match.description.empty()) {
397         description_ = [CreateClassifiedAttributedString(
398             match.description, DimTextColor(), match.description_class) retain];
399       }
400     }
401   }
402   return self;
405 - (instancetype)copyWithZone:(NSZone*)zone {
406   return [self retain];
409 - (CGFloat)getMatchContentsWidth {
410   return [contents_ size].width;
413 @end
415 @implementation OmniboxPopupCell
417 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
418   if ([self state] == NSOnState || [self isHighlighted]) {
419     if ([self state] == NSOnState)
420       [SelectedBackgroundColor() set];
421     else
422       [HoveredBackgroundColor() set];
423     NSBezierPath* path =
424         [NSBezierPath bezierPathWithRoundedRect:cellFrame
425                                         xRadius:kCellRoundingRadius
426                                         yRadius:kCellRoundingRadius];
427     [path fill];
428   }
430   [self drawMatchWithFrame:cellFrame inView:controlView];
433 - (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
434   OmniboxPopupCellData* cellData =
435       base::mac::ObjCCastStrict<OmniboxPopupCellData>([self objectValue]);
436   OmniboxPopupMatrix* tableView =
437       base::mac::ObjCCastStrict<OmniboxPopupMatrix>(controlView);
438   CGFloat remainingWidth = GetContentAreaWidth(cellFrame);
439   CGFloat contentsWidth = [cellData getMatchContentsWidth];
440   CGFloat separatorWidth = [[tableView separator] size].width;
441   CGFloat descriptionWidth =
442       [cellData description] ? [[cellData description] size].width : 0;
443   int contentsMaxWidth, descriptionMaxWidth;
444   OmniboxPopupModel::ComputeMatchMaxWidths(
445       ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth),
446       ceilf(remainingWidth),
447       !AutocompleteMatch::IsSearchType([cellData matchType]), &contentsMaxWidth,
448       &descriptionMaxWidth);
450   NSRect imageRect = cellFrame;
451   imageRect.size = [[cellData image] size];
452   imageRect.origin.x += kImageXOffset;
453   imageRect.origin.y += kPaddingOffset;
454   [[cellData image] drawInRect:FlipIfRTL(imageRect, cellFrame)
455                       fromRect:NSZeroRect
456                      operation:NSCompositeSourceOver
457                       fraction:1.0
458                 respectFlipped:YES
459                          hints:nil];
461   NSPoint origin = NSMakePoint(kTextStartOffset, kPaddingOffset);
462   if ([cellData matchType] == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
463     // Infinite suggestions are rendered with a prefix (usually ellipsis), which
464     // appear vertically stacked.
465     origin.x += [self drawMatchPrefixWithFrame:cellFrame
466                                      tableView:tableView
467                           withContentsMaxWidth:&contentsMaxWidth];
468   }
469   origin.x += [self drawMatchPart:[cellData contents]
470                         withFrame:cellFrame
471                            origin:origin
472                      withMaxWidth:contentsMaxWidth];
474   if (descriptionMaxWidth > 0) {
475     if ([cellData isAnswer]) {
476       origin =
477           NSMakePoint(kTextStartOffset, kContentLineHeight - kPaddingOffset);
478       CGFloat imageSize = [tableView answerLineHeight];
479       NSRect imageRect =
480           NSMakeRect(NSMinX(cellFrame) + origin.x, NSMinY(cellFrame) + origin.y,
481                      imageSize, imageSize);
482       [[cellData answerImage] drawInRect:FlipIfRTL(imageRect, cellFrame)
483                                 fromRect:NSZeroRect
484                                operation:NSCompositeSourceOver
485                                 fraction:1.0
486                           respectFlipped:YES
487                                    hints:nil];
488       if ([cellData answerImage])
489         origin.x += imageSize + kPaddingOffset;
490     } else {
491       origin.x += [self drawMatchPart:[tableView separator]
492                             withFrame:cellFrame
493                                origin:origin
494                          withMaxWidth:separatorWidth];
495     }
496     origin.x += [self drawMatchPart:[cellData description]
497                           withFrame:cellFrame
498                              origin:origin
499                        withMaxWidth:descriptionMaxWidth];
500   }
503 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame
504                           tableView:(OmniboxPopupMatrix*)tableView
505                withContentsMaxWidth:(int*)contentsMaxWidth {
506   OmniboxPopupCellData* cellData =
507       base::mac::ObjCCastStrict<OmniboxPopupCellData>([self objectValue]);
508   CGFloat offset = 0.0f;
509   CGFloat remainingWidth = GetContentAreaWidth(cellFrame);
510   CGFloat prefixWidth = [[cellData prefix] size].width;
512   CGFloat prefixOffset = 0.0f;
513   if (base::i18n::IsRTL() != [cellData isContentsRTL]) {
514     // The contents is rendered between the contents offset extending towards
515     // the start edge, while prefix is rendered in opposite direction. Ideally
516     // the prefix should be rendered at |contentsOffset_|. If that is not
517     // sufficient to render the widest suggestion, we increase it to
518     // |maxMatchContentsWidth|.  If |remainingWidth| is not sufficient to
519     // accommodate that, we reduce the offset so that the prefix gets rendered.
520     prefixOffset = std::min(
521         remainingWidth - prefixWidth,
522         std::max([cellData contentsOffset], [tableView maxMatchContentsWidth]));
523     offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth);
524   } else { // The direction of contents is same as UI direction.
525     // Ideally the offset should be |contentsOffset_|. If the max total width
526     // (|prefixWidth| + |maxMatchContentsWidth|) from offset will exceed the
527     // |remainingWidth|, then we shift the offset to the left , so that all
528     // postfix suggestions are visible.
529     // We have to render the prefix, so offset has to be at least |prefixWidth|.
530     offset =
531         std::max(prefixWidth,
532                  std::min(remainingWidth - [tableView maxMatchContentsWidth],
533                           [cellData contentsOffset]));
534     prefixOffset = offset - prefixWidth;
535   }
536   *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth),
537                                *contentsMaxWidth);
538   [self drawMatchPart:[cellData prefix]
539             withFrame:cellFrame
540                origin:NSMakePoint(prefixOffset + kTextStartOffset, 0)
541          withMaxWidth:prefixWidth];
542   return offset;
545 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString
546                withFrame:(NSRect)cellFrame
547                   origin:(NSPoint)origin
548             withMaxWidth:(int)maxWidth {
549   NSRect renderRect = NSIntersectionRect(
550       cellFrame, NSOffsetRect(cellFrame, origin.x, origin.y));
551   renderRect.size.width =
552       std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth));
553   if (!NSIsEmptyRect(renderRect))
554     [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)];
555   return NSWidth(renderRect);
558 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match {
559   const base::string16& inputText = base::UTF8ToUTF16(
560       match.GetAdditionalInfo(kACMatchPropertyInputText));
561   int contentsStartIndex = 0;
562   base::StringToInt(
563       match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex),
564       &contentsStartIndex);
565   // Ignore invalid state.
566   if (!base::StartsWith(match.fill_into_edit, inputText,
567                         base::CompareCase::SENSITIVE) ||
568       !base::EndsWith(match.fill_into_edit, match.contents,
569                       base::CompareCase::SENSITIVE) ||
570       ((size_t)contentsStartIndex >= inputText.length())) {
571     return 0;
572   }
573   bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT ==
574       base::i18n::GetFirstStrongCharacterDirection(match.contents));
576   // Color does not matter.
577   NSAttributedString* attributedString =
578       CreateAttributedString(inputText, DimTextColor());
579   base::scoped_nsobject<NSTextStorage> textStorage(
580       [[NSTextStorage alloc] initWithAttributedString:attributedString]);
581   base::scoped_nsobject<NSLayoutManager> layoutManager(
582       [[NSLayoutManager alloc] init]);
583   base::scoped_nsobject<NSTextContainer> textContainer(
584       [[NSTextContainer alloc] init]);
585   [layoutManager addTextContainer:textContainer];
586   [textStorage addLayoutManager:layoutManager];
588   NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex);
589   NSUInteger glyphIndex =
590       [layoutManager glyphIndexForCharacterAtIndex:charIndex];
592   // This offset is computed from the left edge of the glyph always from the
593   // left edge of the string, irrespective of the directionality of UI or text.
594   CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x;
596   CGFloat inputWidth = [attributedString size].width;
598   // The offset obtained above may need to be corrected because the left-most
599   // glyph may not have 0 offset. So we find the offset of left-most glyph, and
600   // subtract it from the offset of the glyph we obtained above.
601   CGFloat minOffset = glyphOffset;
603   // If content is RTL, we are interested in the right-edge of the glyph.
604   // Unfortunately the bounding rect computation methods from NSLayoutManager or
605   // NSFont don't work correctly with bidirectional text. So we compute the
606   // glyph width by finding the closest glyph offset to the right of the glyph
607   // we are looking for.
608   CGFloat glyphWidth = inputWidth;
610   for (NSUInteger i = 0; i < [attributedString length]; i++) {
611     if (i == charIndex) continue;
612     glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i];
613     CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x;
614     minOffset = std::min(minOffset, offset);
615     if (offset > glyphOffset)
616       glyphWidth = std::min(glyphWidth, offset - glyphOffset);
617   }
618   glyphOffset -= minOffset;
619   if (glyphWidth == 0)
620     glyphWidth = inputWidth - glyphOffset;
621   if (isContentsRTL)
622     glyphOffset += glyphWidth;
623   return base::i18n::IsRTL() ? (inputWidth - glyphOffset) : glyphOffset;
626 + (NSAttributedString*)createSeparatorString {
627   base::string16 raw_separator =
628       l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
629   return CreateAttributedString(raw_separator, DimTextColor());
632 @end