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

Side by Side Diff: chrome/browser/ui/views/omnibox/omnibox_result_view.cc

Issue 119813002: Implement eliding/truncating at end in RenderText (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed merge issue Created 6 years, 11 months 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 | Annotate | Revision Log
« no previous file with comments | « chrome/browser/ui/views/omnibox/omnibox_result_view.h ('k') | ui/gfx/render_text.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // For WinDDK ATL compatibility, these ATL headers must come first. 5 // For WinDDK ATL compatibility, these ATL headers must come first.
6 #include "build/build_config.h" 6 #include "build/build_config.h"
7 #if defined(OS_WIN) 7 #if defined(OS_WIN)
8 #include <atlbase.h> // NOLINT 8 #include <atlbase.h> // NOLINT
9 #include <atlwin.h> // NOLINT 9 #include <atlwin.h> // NOLINT
10 #endif 10 #endif
(...skipping 341 matching lines...) Expand 10 before | Expand all | Expand 10 after
352 // regardless of locale. 352 // regardless of locale.
353 bool is_url = true; 353 bool is_url = true;
354 for (ACMatchClassifications::const_iterator i(classifications.begin()); 354 for (ACMatchClassifications::const_iterator i(classifications.begin());
355 i != classifications.end(); ++i) { 355 i != classifications.end(); ++i) {
356 if (!(i->style & ACMatchClassification::URL)) { 356 if (!(i->style & ACMatchClassification::URL)) {
357 is_url = false; 357 is_url = false;
358 break; 358 break;
359 } 359 }
360 } 360 }
361 361
362 // Split the text into visual runs. We do this first so that we don't need to 362 scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
363 // worry about whether our eliding might change the visual display in 363 const size_t text_length = text.length();
364 // unintended ways, e.g. by removing directional markings or by adding an 364 render_text->SetText(text);
365 // ellipsis that's not enclosed in appropriate markings. 365 render_text->SetFontList(font_list_);
366 base::i18n::BiDiLineIterator bidi_line; 366 render_text->SetMultiline(false);
367 if (!bidi_line.Open(text, base::i18n::IsRTL(), is_url)) 367 render_text->SetCursorEnabled(false);
368 return x; 368 if (is_url)
369 const int num_runs = bidi_line.CountRuns(); 369 render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR);
370 ScopedVector<gfx::RenderText> render_texts;
371 Runs runs;
372 for (int run = 0; run < num_runs; ++run) {
373 int run_start_int = 0, run_length_int = 0;
374 // The index we pass to GetVisualRun corresponds to the position of the run
375 // in the displayed text. For example, the string "Google in HEBREW" (where
376 // HEBREW is text in the Hebrew language) has two runs: "Google in " which
377 // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the
378 // run "Google in " has the index 0 (since it is the leftmost run
379 // displayed). In an RTL context, the same run has the index 1 because it
380 // is the rightmost run. This is why the order in which we traverse the
381 // runs is different depending on the locale direction.
382 const UBiDiDirection run_direction = bidi_line.GetVisualRun(
383 (base::i18n::IsRTL() && !is_url) ? (num_runs - run - 1) : run,
384 &run_start_int, &run_length_int);
385 DCHECK_GT(run_length_int, 0);
386 runs.push_back(RunData());
387 RunData* current_run = &runs.back();
388 current_run->run_start = run_start_int;
389 const size_t run_end = current_run->run_start + run_length_int;
390 current_run->visual_order = run;
391 current_run->is_rtl = !is_url && (run_direction == UBIDI_RTL);
392 370
393 // Compute classifications for this run. 371 // Apply classifications.
394 for (size_t i = 0; i < classifications.size(); ++i) { 372 for (size_t i = 0; i < classifications.size(); ++i) {
395 const size_t text_start = 373 const size_t text_start = classifications[i].offset;
396 std::max(classifications[i].offset, current_run->run_start); 374 if (text_start >= text_length)
397 if (text_start >= run_end) 375 break;
398 break; // We're past the last classification in the run.
399 376
400 const size_t text_end = (i < (classifications.size() - 1)) ? 377 const size_t text_end = (i < (classifications.size() - 1)) ?
401 std::min(classifications[i + 1].offset, run_end) : run_end; 378 std::min(classifications[i + 1].offset, text_length) : text_length;
402 if (text_end <= current_run->run_start) 379 const gfx::Range current_range(text_start, text_end);
403 continue; // We haven't reached the first classification in the run.
404 380
405 render_texts.push_back(gfx::RenderText::CreateInstance()); 381 // Calculate style-related data.
406 gfx::RenderText* render_text = render_texts.back(); 382 if (classifications[i].style & ACMatchClassification::MATCH)
407 current_run->classifications.push_back(render_text); 383 render_text->ApplyStyle(gfx::BOLD, true, current_range);
408 render_text->SetText(text.substr(text_start, text_end - text_start));
409 render_text->SetFontList(font_list_);
410 384
411 // Calculate style-related data. 385 ColorKind color_kind = TEXT;
412 if (classifications[i].style & ACMatchClassification::MATCH) 386 if (classifications[i].style & ACMatchClassification::URL) {
413 render_text->SetStyle(gfx::BOLD, true); 387 color_kind = URL;
414 const ResultViewState state = GetState(); 388 } else if (force_dim ||
415 if (classifications[i].style & ACMatchClassification::URL) 389 (classifications[i].style & ACMatchClassification::DIM)) {
416 render_text->SetColor(GetColor(state, URL)); 390 color_kind = DIMMED_TEXT;
417 else if (classifications[i].style & ACMatchClassification::DIM)
418 render_text->SetColor(GetColor(state, DIMMED_TEXT));
419 else
420 render_text->SetColor(GetColor(state, force_dim ? DIMMED_TEXT : TEXT));
421
422 current_run->pixel_width += render_text->GetStringSize().width();
423 } 391 }
424 DCHECK(!current_run->classifications.empty()); 392 render_text->ApplyColor(GetColor(GetState(), color_kind), current_range);
425 }
426 DCHECK(!runs.empty());
427
428 // Sort into logical order so we can elide logically.
429 std::sort(runs.begin(), runs.end(), &SortRunsLogically);
430
431 // Now determine what to elide, if anything. Several subtle points:
432 // * Because we have the run data, we can get edge cases correct, like
433 // whether to place an ellipsis before or after the end of a run when the
434 // text needs to be elided at the run boundary.
435 // * The "or one before it" comments below refer to cases where an earlier
436 // classification fits completely, but leaves too little space for an
437 // ellipsis that turns out to be needed later. These cases are commented
438 // more completely in Elide().
439 int remaining_width = mirroring_context_->remaining_width(x);
440 for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
441 if (i->pixel_width > remaining_width) {
442 // This run or one before it needs to be elided.
443 for (Classifications::iterator j(i->classifications.begin());
444 j != i->classifications.end(); ++j) {
445 const int width = (*j)->GetStringSize().width();
446 if (width > remaining_width) {
447 // This classification or one before it needs to be elided. Erase all
448 // further classifications and runs so Elide() can simply reverse-
449 // iterate over everything to find the specific classification to
450 // elide.
451 i->classifications.erase(++j, i->classifications.end());
452 runs.erase(++i, runs.end());
453 Elide(&runs, remaining_width);
454 break;
455 }
456 remaining_width -= width;
457 }
458 break;
459 }
460 remaining_width -= i->pixel_width;
461 } 393 }
462 394
463 // Sort back into visual order so we can display the runs correctly. 395 int remaining_width = mirroring_context_->remaining_width(x);
464 std::sort(runs.begin(), runs.end(), &SortRunsVisually);
465 396
466 // Draw the runs. 397 // No need to try anything if we can't even show a solitary character.
467 for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) { 398 if ((text_length == 1) &&
468 const bool reverse_visible_order = (i->is_rtl != base::i18n::IsRTL()); 399 (remaining_width < render_text->GetContentWidth())) {
469 if (reverse_visible_order) 400 return x;
470 std::reverse(i->classifications.begin(), i->classifications.end());
471 for (Classifications::const_iterator j(i->classifications.begin());
472 j != i->classifications.end(); ++j) {
473 const gfx::Size size = (*j)->GetStringSize();
474 // Align the text runs to a common baseline.
475 const gfx::Rect rect(
476 mirroring_context_->mirrored_left_coord(x, x + size.width()), y,
477 size.width(), height());
478 (*j)->SetDisplayRect(rect);
479 (*j)->Draw(canvas);
480 x += size.width();
481 }
482 } 401 }
483 402
484 return x; 403 if (render_text->GetContentWidth() > remaining_width)
485 } 404 render_text->SetElideBehavior(gfx::ELIDE_AT_END);
486 405
487 void OmniboxResultView::Elide(Runs* runs, int remaining_width) const { 406 // Set the display rect to trigger eliding.
488 // The complexity of this function is due to edge cases like the following: 407 render_text->SetDisplayRect(gfx::Rect(
489 // We have 100 px of available space, an initial classification that takes 86 408 mirroring_context_->mirrored_left_coord(x, x + remaining_width), y,
490 // px, and a font that has a 15 px wide ellipsis character. Now if the first 409 remaining_width, height()));
491 // classification is followed by several very narrow classifications (e.g. 3 410 render_text->set_clip_to_display_rect(true);
492 // px wide each), we don't know whether we need to elide or not at the time we 411 render_text->Draw(canvas);
493 // see the first classification -- it depends on how many subsequent
494 // classifications follow, and some of those may be in the next run (or
495 // several runs!). This is why instead we let our caller move forward until
496 // we know we definitely need to elide, and then in this function we move
497 // backward again until we find a string that we can successfully do the
498 // eliding on.
499 bool on_trailing_classification = true;
500 for (Runs::reverse_iterator i(runs->rbegin()); i != runs->rend(); ++i) {
501 for (Classifications::reverse_iterator j(i->classifications.rbegin());
502 j != i->classifications.rend(); ++j) {
503 if (!on_trailing_classification) {
504 // We also add this classification's width (sans ellipsis) back to the
505 // available width since we want to consider the available space we'll
506 // have when we draw this classification.
507 remaining_width += (*j)->GetStringSize().width();
508 412
509 // If we reached here, we couldn't fit an ellipsis in the space taken by 413 // Need to call GetContentWidth again as the SetDisplayRect may modify it.
510 // the previous classifications we looped over (see comments at bottom 414 return x + render_text->GetContentWidth();
511 // of loop). Append one here to represent those elided portions.
512 (*j)->SetText((*j)->text() + kEllipsis);
513 }
514 on_trailing_classification = false;
515
516 // Can we fit at least an ellipsis?
517 const gfx::FontList& font_list =
518 (*j)->GetStyle(gfx::BOLD) ?
519 (*j)->font_list().DeriveFontListWithSizeDeltaAndStyle(
520 0, gfx::Font::BOLD) :
521 (*j)->font_list();
522 base::string16 elided_text(
523 gfx::ElideText((*j)->text(), font_list, remaining_width,
524 gfx::ELIDE_AT_END));
525 Classifications::reverse_iterator prior(j + 1);
526 const bool on_leading_classification =
527 (prior == i->classifications.rend());
528 if (elided_text.empty() && (remaining_width >= ellipsis_width_) &&
529 on_leading_classification) {
530 // Edge case: This classification is bold, we can't fit a bold ellipsis
531 // but we can fit a normal one, and this is the first classification in
532 // the run. We should display a lone normal ellipsis, because appending
533 // one to the end of the previous run might put it in the wrong visual
534 // location (if the previous run is reversed from the normal visual
535 // order).
536 // NOTE: If this isn't the first classification in the run, we don't
537 // need to bother with this; see note below.
538 elided_text = kEllipsis;
539 }
540 if (!elided_text.empty()) {
541 // Success. Elide this classification and stop.
542 (*j)->SetText(elided_text);
543
544 // If we could only fit an ellipsis, then only make it bold if there was
545 // an immediate prior classification in this run that was also bold, or
546 // it will look orphaned.
547 if ((*j)->GetStyle(gfx::BOLD) && (elided_text.length() == 1) &&
548 (on_leading_classification || !(*prior)->GetStyle(gfx::BOLD)))
549 (*j)->SetStyle(gfx::BOLD, false);
550
551 // Erase any other classifications that come after the elided one.
552 i->classifications.erase(j.base(), i->classifications.end());
553 runs->erase(i.base(), runs->end());
554 return;
555 }
556
557 // We couldn't fit an ellipsis. Move back one classification,
558 // append an ellipsis, and try again.
559 // NOTE: In the edge case that a bold ellipsis doesn't fit but a
560 // normal one would, and we reach here, then there is a previous
561 // classification in this run, and so either:
562 // * It's normal, and will be able to draw successfully with the
563 // ellipsis we'll append to it, or
564 // * It is also bold, in which case we don't want to fall back
565 // to a normal ellipsis anyway (see comment above).
566 }
567 }
568
569 // We couldn't draw anything.
570 runs->clear();
571 } 415 }
572 416
573 void OmniboxResultView::Layout() { 417 void OmniboxResultView::Layout() {
574 const gfx::ImageSkia icon = GetIcon(); 418 const gfx::ImageSkia icon = GetIcon();
575 419
576 icon_bounds_.SetRect(edge_item_padding_ + 420 icon_bounds_.SetRect(edge_item_padding_ +
577 ((icon.width() == default_icon_size_) ? 421 ((icon.width() == default_icon_size_) ?
578 0 : LocationBarView::kIconInternalPadding), 422 0 : LocationBarView::kIconInternalPadding),
579 (height() - icon.height()) / 2, icon.width(), icon.height()); 423 (height() - icon.height()) / 2, icon.width(), icon.height());
580 424
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
626 int x = GetMirroredXForRect(keyword_text_bounds_); 470 int x = GetMirroredXForRect(keyword_text_bounds_);
627 mirroring_context_->Initialize(x, keyword_text_bounds_.width()); 471 mirroring_context_->Initialize(x, keyword_text_bounds_.width());
628 PaintMatch(canvas, *match_.associated_keyword.get(), x); 472 PaintMatch(canvas, *match_.associated_keyword.get(), x);
629 } 473 }
630 } 474 }
631 475
632 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) { 476 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
633 Layout(); 477 Layout();
634 SchedulePaint(); 478 SchedulePaint();
635 } 479 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/views/omnibox/omnibox_result_view.h ('k') | ui/gfx/render_text.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698