summaryrefslogtreecommitdiffstats
path: root/views/view_text_utils.cc
blob: 9b4442d170705834f9826b56ba6cbfba0d9c5820 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright (c) 2010 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.

#include "views/view_text_utils.h"

#include "app/bidi_line_iterator.h"
#include "base/i18n/word_iterator.h"
#include "base/logging.h"
#include "base/utf_string_conversions.h"
#include "gfx/canvas_skia.h"
#include "gfx/color_utils.h"
#include "gfx/size.h"
#include "views/controls/label.h"
#include "views/controls/link.h"

namespace view_text_utils {

void DrawTextAndPositionUrl(gfx::Canvas* canvas,
                            views::Label* label,
                            const std::wstring& text,
                            views::Link* link,
                            gfx::Rect* rect,
                            gfx::Size* position,
                            bool text_direction_is_rtl,
                            const gfx::Rect& bounds,
                            const gfx::Font& font) {
  DCHECK(canvas && position);

  // The |text| parameter is potentially a mix of LTR and RTL "runs," where
  // a run is a sequence of words that share the same directionality. We
  // initialize a bidirectional ICU line iterator and split the text into
  // runs that are either strictly LTR or strictly RTL, with no mix.
  BiDiLineIterator bidi_line;
  if (!bidi_line.Open(text.c_str(), true, false))
    return;

  // Iterate over each run and draw it.
  int run_start = 0;
  int run_end = 0;
  const int runs = bidi_line.CountRuns();
  for (int run = 0; run < runs; ++run) {
    UBiDiLevel level = 0;
    bidi_line.GetLogicalRun(run_start, &run_end, &level);
    DCHECK(run_end > run_start);
    std::wstring fragment = text.substr(run_start, run_end - run_start);

    // A flag that tells us whether we found LTR text inside RTL text.
    bool ltr_inside_rtl_text =
        ((level & 1) == UBIDI_LTR) && text_direction_is_rtl;

    // Draw text chunk contained in |fragment|. |position| is relative to the
    // top left corner of the label we draw inside, even when drawing RTL.
    DrawTextStartingFrom(canvas, label, fragment, position, bounds, font,
        text_direction_is_rtl, ltr_inside_rtl_text);

    run_start = run_end;  // Advance over what we just drew.
  }

  // If the caller is interested in placing a link after this text blurb, we
  // figure out here where to place it.
  if (link && rect) {
    gfx::Size sz = link->GetPreferredSize();
    gfx::Insets insets = link->GetInsets();
    WrapIfWordDoesntFit(sz.width(), font.GetHeight(), position, bounds);
    int x = position->width();
    int y = position->height();

    // Links have a border to allow them to be focused.
    y -= insets.top();

    *rect = gfx::Rect(x, y, sz.width(), sz.height());

    // Go from relative pixel coordinates (within the label we are drawing
    // on) to absolute pixel coordinates (relative to the top left corner of
    // the dialog content).
    rect->Offset(bounds.x(), bounds.y());
    // And leave some space to draw the link in.
    position->Enlarge(sz.width(), 0);
  }
}

void DrawTextStartingFrom(gfx::Canvas* canvas,
                          views::Label* label,
                          const std::wstring& text,
                          gfx::Size* position,
                          const gfx::Rect& bounds,
                          const gfx::Font& font,
                          bool text_direction_is_rtl,
                          bool ltr_within_rtl) {
#if defined(OS_WIN)
  const SkColor text_color = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
#else
  // TODO(beng): source from theme provider.
  const SkColor text_color = SK_ColorBLACK;
#endif

  // Iterate through line breaking opportunities (which in English would be
  // spaces and such). This tells us where to wrap.
  string16 text16(WideToUTF16(text));
  WordIterator iter(&text16, WordIterator::BREAK_LINE);
  if (!iter.Init())
    return;

  int flags = (text_direction_is_rtl ? gfx::Canvas::TEXT_ALIGN_RIGHT :
                                       gfx::Canvas::TEXT_ALIGN_LEFT);
  flags |= gfx::Canvas::MULTI_LINE | gfx::Canvas::HIDE_PREFIX;

  // Iterate over each word in the text, or put in a more locale-neutral way:
  // iterate to the next line breaking opportunity.
  while (iter.Advance()) {
    // Get the word and figure out the dimensions.
    std::wstring word;
    if (!ltr_within_rtl)
      word = UTF16ToWide(iter.GetWord());  // Get the next word.
    else
      word = text;  // Draw the whole text at once.

    int w = font.GetStringWidth(word), h = font.GetHeight();
    gfx::CanvasSkia::SizeStringInt(word, font, &w, &h, flags);

    // If we exceed the boundaries, we need to wrap.
    WrapIfWordDoesntFit(w, font.GetHeight(), position, bounds);

    int x = label->MirroredXCoordinateInsideView(position->width()) +
                                                 bounds.x();
    if (text_direction_is_rtl) {
      x -= w;
      // When drawing LTR strings inside RTL text we need to make sure we
      // draw the trailing space (if one exists after the LTR text) to the
      // left of the LTR string.
      if (ltr_within_rtl && word[word.size() - 1] == L' ') {
        int space_w = font.GetStringWidth(L" "), space_h = font.GetHeight();
        gfx::CanvasSkia::SizeStringInt(L" ", font, &space_w, &space_h, flags);
        x += space_w;
      }
    }
    int y = position->height() + bounds.y();

    // Draw the text on the screen (mirrored, if RTL run).
    canvas->DrawStringInt(word, font, text_color, x, y, w, font.GetHeight(),
                          flags);

    if (word.size() > 0 && word[word.size() - 1] == L'\x0a') {
      // When we come across '\n', we move to the beginning of the next line.
      position->set_width(0);
      position->Enlarge(0, font.GetHeight());
    } else {
      // Otherwise, we advance position to the next word.
      position->Enlarge(w, 0);
    }

    if (ltr_within_rtl)
      break;  // LTR within RTL is drawn as one unit, so we are done.
  }
}

void WrapIfWordDoesntFit(int word_width,
                         int font_height,
                         gfx::Size* position,
                         const gfx::Rect& bounds) {
  if (position->width() + word_width > bounds.right()) {
    position->set_width(0);
    position->Enlarge(0, font_height);
  }
}

}  // namespace view_text_utils