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

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

Issue 112063003: Implement eliding/truncating at end in RenderText (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: 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
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_size = text.size();
msw 2013/12/11 08:10:14 nit: I'd prefer text_length and .length(), but eit
Anuj 2013/12/12 08:08:41 Done.
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);
msw 2013/12/11 08:10:14 This shouldn't be necessary, URLs should always ha
Anuj 2013/12/11 18:41:32 Are you suggesting URLs should always have LTR dir
msw 2013/12/11 19:39:57 I guess it's fine as a precautionary measure.
Anuj 2013/12/12 08:08:41 Done.
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_size)
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_size) : text_size;
400 if (text_end <= current_run->run_start)
401 continue; // We haven't reached the first classification in the run.
402 377
msw 2013/12/11 08:10:14 optional nit: remove blank line.
Anuj 2013/12/12 08:08:41 Done.
403 render_texts.push_back(gfx::RenderText::CreateInstance()); 378 const gfx::Range current_range(text_start, text_end);
404 gfx::RenderText* render_text = render_texts.back();
405 current_run->classifications.push_back(render_text);
406 render_text->SetText(text.substr(text_start, text_end - text_start));
407 render_text->SetFontList(font_list_);
408 379
409 // Calculate style-related data. 380 // Calculate style-related data.
410 if (classifications[i].style & ACMatchClassification::MATCH) 381 if (classifications[i].style & ACMatchClassification::MATCH)
411 render_text->SetStyle(gfx::BOLD, true); 382 render_text->ApplyStyle(gfx::BOLD, true, current_range);
412 const ResultViewState state = GetState();
413 if (classifications[i].style & ACMatchClassification::URL)
414 render_text->SetColor(GetColor(state, URL));
415 else if (classifications[i].style & ACMatchClassification::DIM)
416 render_text->SetColor(GetColor(state, DIMMED_TEXT));
417 else
418 render_text->SetColor(GetColor(state, force_dim ? DIMMED_TEXT : TEXT));
419 383
msw 2013/12/11 08:10:14 optional nit: remove blank line.
Anuj 2013/12/12 08:08:41 I like it here for keeping the next if-block disti
420 current_run->pixel_width += render_text->GetStringSize().width(); 384 const ResultViewState state = GetState();
421 } 385 if (classifications[i].style & ACMatchClassification::URL)
422 DCHECK(!current_run->classifications.empty()); 386 render_text->ApplyColor(GetColor(state, URL), current_range);
msw 2013/12/11 08:10:14 nit: I suggest making this if/else translate the c
Anuj 2013/12/12 08:08:41 Done.
423 } 387 else if (classifications[i].style & ACMatchClassification::DIM)
424 DCHECK(!runs.empty()); 388 render_text->ApplyColor(GetColor(state, DIMMED_TEXT), current_range);
425 389 else
426 // Sort into logical order so we can elide logically. 390 render_text->ApplyColor(
427 std::sort(runs.begin(), runs.end(), &SortRunsLogically); 391 GetColor(state, force_dim ? DIMMED_TEXT : TEXT), current_range);
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 } 392 }
460 393
461 // Sort back into visual order so we can display the runs correctly. 394 int remaining_width = mirroring_context_->remaining_width(x);
462 std::sort(runs.begin(), runs.end(), &SortRunsVisually);
463 395
464 // Draw the runs. 396 // 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) { 397 if ((text_size == 1) &&
466 const bool reverse_visible_order = (i->is_rtl != base::i18n::IsRTL()); 398 (remaining_width < render_text->GetContentWidth())) {
467 if (reverse_visible_order) 399 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 } 400 }
481 401
402 render_text->Elide(remaining_width, gfx::ELIDE_AT_END);
403
404 const gfx::Size size = render_text->GetStringSize();
405 const int display_width = std::min(remaining_width, size.width());
msw 2013/12/11 08:10:14 Shouldn't eliding ensure that the string size is a
Anuj 2013/12/12 08:08:41 I thought as much, and the paranoid in me left it
406 // Align the text runs to a common baseline.
407 const gfx::Rect rect(
msw 2013/12/11 08:10:14 optional nit: inline this in the SetDisplayRect ca
Anuj 2013/12/12 08:08:41 Done.
408 mirroring_context_->mirrored_left_coord(x, x + display_width), y,
409 display_width, height());
410 render_text->SetDisplayRect(rect);
411 render_text->set_clip_to_display_rect(true);
412 render_text->Draw(canvas);
413 x += display_width;
msw 2013/12/11 08:10:14 nit: remove this and simply return x + display_wid
Anuj 2013/12/12 08:08:41 Done.
414
482 return x; 415 return x;
483 } 416 }
484 417
485 void OmniboxResultView::Elide(Runs* runs, int remaining_width) const {
486 // The complexity of this function is due to edge cases like the following:
487 // We have 100 px of available space, an initial classification that takes 86
488 // px, and a font that has a 15 px wide ellipsis character. Now if the first
489 // classification is followed by several very narrow classifications (e.g. 3
490 // px wide each), we don't know whether we need to elide or not at the time we
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
507 // If we reached here, we couldn't fit an ellipsis in the space taken by
508 // the previous classifications we looped over (see comments at bottom
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 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 }
568
569 void OmniboxResultView::Layout() { 418 void OmniboxResultView::Layout() {
570 const gfx::ImageSkia icon = GetIcon(); 419 const gfx::ImageSkia icon = GetIcon();
571 420
572 icon_bounds_.SetRect(edge_item_padding_ + 421 icon_bounds_.SetRect(edge_item_padding_ +
573 ((icon.width() == default_icon_size_) ? 422 ((icon.width() == default_icon_size_) ?
574 0 : LocationBarView::kIconInternalPadding), 423 0 : LocationBarView::kIconInternalPadding),
575 (height() - icon.height()) / 2, icon.width(), icon.height()); 424 (height() - icon.height()) / 2, icon.width(), icon.height());
576 425
577 int text_x = edge_item_padding_ + default_icon_size_ + item_padding_; 426 int text_x = edge_item_padding_ + default_icon_size_ + item_padding_;
578 int text_width = width() - text_x - edge_item_padding_; 427 int text_width = width() - text_x - edge_item_padding_;
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
622 int x = GetMirroredXForRect(keyword_text_bounds_); 471 int x = GetMirroredXForRect(keyword_text_bounds_);
623 mirroring_context_->Initialize(x, keyword_text_bounds_.width()); 472 mirroring_context_->Initialize(x, keyword_text_bounds_.width());
624 PaintMatch(canvas, *match_.associated_keyword.get(), x); 473 PaintMatch(canvas, *match_.associated_keyword.get(), x);
625 } 474 }
626 } 475 }
627 476
628 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) { 477 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
629 Layout(); 478 Layout();
630 SchedulePaint(); 479 SchedulePaint();
631 } 480 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698