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

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

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

Powered by Google App Engine
This is Rietveld 408576698