Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 #include "ui/gfx/render_text_linux.h" | 5 #include "ui/gfx/render_text_linux.h" |
| 6 | 6 |
| 7 #include <pango/pangocairo.h> | |
| 8 #include <algorithm> | |
| 9 | |
| 10 #include "base/logging.h" | |
| 11 #include "ui/gfx/canvas_skia.h" | |
| 12 #include "ui/gfx/gtk_util.h" | |
| 13 #include "unicode/uchar.h" | |
| 14 | |
| 15 #define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) | |
| 16 #define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) | |
|
oshima
2011/08/19 18:44:07
space around operators
xji
2011/08/20 00:53:05
these are actually not needed. defined in include
| |
| 17 // TODO(xji): massage ARGB to RGB. | |
| 18 #define COLOR_16_TO_32_BIT(c) ((c)/0xff * 0xffff) | |
|
oshima
2011/08/19 18:44:07
If this is to convert ARGB to RGB, please name acc
xji
2011/08/20 00:53:05
It is just convert R/G/B from 16 bit to 32 bit.
bu
| |
| 19 | |
| 20 // TODO(xji): index saved in upper layer are utf16 index. Pango uses utf8 index. | |
| 21 // Since caret_pos is used internally, we could save utf8 index for caret_pos | |
| 22 // to avoid conversion. | |
| 23 | |
| 7 namespace gfx { | 24 namespace gfx { |
| 8 | 25 |
| 9 RenderTextLinux::RenderTextLinux() | 26 RenderTextLinux::RenderTextLinux() |
| 10 : RenderText() { | 27 : layout_(NULL), |
| 28 layout_line_(NULL) { | |
| 11 } | 29 } |
| 12 | 30 |
| 13 RenderTextLinux::~RenderTextLinux() { | 31 RenderTextLinux::~RenderTextLinux() { |
| 32 ResetLayout(); | |
| 14 } | 33 } |
| 15 | 34 |
| 16 RenderText* RenderText::CreateRenderText() { | 35 RenderText* RenderText::CreateRenderText() { |
| 17 return new RenderTextLinux; | 36 return new RenderTextLinux; |
| 18 } | 37 } |
| 19 | 38 |
| 39 int RenderTextLinux::GetStringWidth() { | |
| 40 PangoLayout* layout = EnsureLayout(); | |
| 41 int width, height; | |
| 42 pango_layout_get_pixel_size(layout, &width, &height); | |
| 43 return width; | |
| 44 } | |
| 45 | |
| 46 void RenderTextLinux::Draw(Canvas* canvas) { | |
| 47 PangoLayout* layout = EnsureLayout(); | |
| 48 Rect bounds(display_rect()); | |
| 49 | |
| 50 // Clip the canvas to the text display area. | |
| 51 CanvasSkia* canvas_skia = static_cast<CanvasSkia*>(canvas); | |
|
oshima
2011/08/19 18:44:07
canvas->AsCanvasSkia()
xji
2011/08/20 00:53:05
Done.
| |
| 52 canvas_skia->ClipRectInt( | |
| 53 bounds.x(), bounds.y(), bounds.width(), bounds.height()); | |
| 54 | |
| 55 // TODO(xji): is this Begin/EndPlatformPaint correct? How about performance? | |
|
oshima
2011/08/19 18:44:07
This seems to be fine (assuming this class replace
xji
2011/08/20 00:53:05
comments removed.
| |
| 56 cairo_t* cr = skia::BeginPlatformPaint(canvas_skia); | |
| 57 cairo_save(cr); | |
| 58 cairo_rectangle(cr, bounds.x(), bounds.y(), bounds.width(), bounds.height()); | |
| 59 cairo_clip(cr); | |
| 60 | |
| 61 int text_width, text_height; | |
| 62 pango_layout_get_pixel_size(layout, &text_width, &text_height); | |
| 63 // Vertically centered. | |
| 64 int text_y = bounds.y() + ((bounds.height() - text_height) / 2); | |
| 65 int text_x = bounds.x() + GetUpdatedDisplayOffset().x(); | |
| 66 cairo_move_to(cr, text_x, text_y); | |
| 67 pango_cairo_show_layout(cr, layout); | |
| 68 | |
| 69 // Destructor. | |
| 70 cairo_restore(cr); | |
| 71 skia::EndPlatformPaint(canvas_skia); | |
| 72 | |
| 73 // Paint cursor. | |
| 74 bounds = GetUpdatedCursorBounds(); | |
| 75 if (cursor_visible() && focused() && !bounds.IsEmpty()) { | |
| 76 if (!bounds.IsEmpty()) | |
| 77 canvas->DrawRectInt(kCursorColor, | |
| 78 bounds.x(), | |
| 79 bounds.y(), | |
| 80 bounds.width(), | |
| 81 bounds.height()); | |
| 82 } | |
| 83 } | |
| 84 | |
| 85 SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) { | |
| 86 // TODO(xji): when points outside of text, return HOME/END position. | |
| 87 PangoLayout* layout = EnsureLayout(); | |
| 88 | |
| 89 if (text().length() == 0) | |
| 90 return SelectionModel(0, 0, SelectionModel::LEADING); | |
| 91 | |
| 92 int adjusted_x = | |
| 93 point.x() - display_rect().x() - GetUpdatedDisplayOffset().x(); | |
| 94 int caret_pos, trailing; | |
| 95 pango_layout_xy_to_index(layout, | |
| 96 adjusted_x * PANGO_SCALE, | |
| 97 point.y() * PANGO_SCALE, | |
| 98 &caret_pos, | |
| 99 &trailing); | |
| 100 | |
| 101 size_t selection_end = caret_pos; | |
| 102 if (trailing > 0) { | |
| 103 const char* layout_text = pango_layout_get_text(layout); | |
| 104 selection_end = g_utf8_offset_to_pointer(layout_text + caret_pos, trailing) | |
| 105 - layout_text; | |
| 106 } | |
| 107 | |
| 108 return SelectionModel( | |
| 109 Utf8IndexToUtf16Index(text(), selection_end), | |
| 110 Utf8IndexToUtf16Index(text(), caret_pos), | |
| 111 trailing > 0 ? SelectionModel::TRAILING : SelectionModel::LEADING); | |
| 112 } | |
| 113 | |
| 114 Rect RenderTextLinux::GetCursorBounds(const SelectionModel& selection, | |
| 115 bool insert_mode) { | |
| 116 PangoLayout* layout = EnsureLayout(); | |
| 117 | |
| 118 PangoRectangle pos; | |
| 119 pango_layout_index_to_pos( | |
| 120 layout, Utf16IndexToUtf8Index(text(), selection.caret_pos()), &pos); | |
| 121 | |
| 122 int x; | |
| 123 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
| 124 if (caret_placement == SelectionModel::TRAILING) | |
| 125 x = (pos.x + pos.width) / PANGO_SCALE; | |
| 126 else | |
| 127 x = pos.x / PANGO_SCALE; | |
| 128 x += display_rect().x() + GetUpdatedDisplayOffset().x(); | |
|
oshima
2011/08/19 18:44:07
these can be
int x = (caret_placement == Selectio
xji
2011/08/20 00:53:05
I'd prefer the old way.
moderate use of tertiary i
| |
| 129 | |
| 130 const Font& font = default_style().font; | |
| 131 int h = std::min(display_rect().height(), font.GetHeight()); | |
| 132 Rect bounds(x, (display_rect().height() - h) / 2, 1, h); | |
| 133 | |
| 134 size_t position = selection.selection_end(); | |
| 135 if (!insert_mode && text().length() != position) | |
| 136 AdjustBoundsForNonInsertMode(selection, pos, &bounds); | |
| 137 | |
| 138 return bounds; | |
| 139 } | |
| 140 | |
| 141 SelectionModel RenderTextLinux::GetLeftSelectionModel( | |
| 142 const SelectionModel& current, BreakType break_type) { | |
| 143 EnsureLayout(); | |
| 144 | |
| 145 if (break_type == LINE_BREAK) | |
| 146 return GetSelectionModelForVisualLeftmost(); | |
|
oshima
2011/08/19 18:44:07
indent
xji
2011/08/20 00:53:05
Done.
| |
| 147 if (break_type == CHARACTER_BREAK) | |
| 148 return GetLeftSelectionModelByGrapheme(current); | |
| 149 // TODO(xji): implementation. | |
| 150 return RenderText::GetLeftSelectionModel(current, break_type); | |
| 151 } | |
| 152 | |
| 153 SelectionModel RenderTextLinux::GetRightSelectionModel( | |
| 154 const SelectionModel& current, BreakType break_type) { | |
| 155 EnsureLayout(); | |
| 156 | |
| 157 if (break_type == LINE_BREAK) { | |
| 158 const char* layout_text = pango_layout_get_text(layout_); | |
| 159 PangoDirection base_dir = | |
| 160 pango_find_base_dir(layout_text, strlen(layout_text)); | |
| 161 if (base_dir == PANGO_DIRECTION_RTL || | |
| 162 base_dir == PANGO_DIRECTION_WEAK_RTL) | |
| 163 return SelectionModel(0, 0, SelectionModel::LEADING); | |
| 164 return SelectionModel( | |
| 165 text().length(), text().length(), SelectionModel::LEADING); | |
| 166 } | |
| 167 if (break_type == CHARACTER_BREAK) | |
| 168 return GetRightSelectionModelByGrapheme(current); | |
| 169 // TODO(xji): implementation. | |
|
oshima
2011/08/19 18:44:07
can you update the comment to be more descriptive?
xji
2011/08/20 00:53:05
changed to "not implemented yet".
| |
| 170 return RenderText::GetRightSelectionModel(current, break_type); | |
| 171 } | |
| 172 | |
| 173 size_t RenderTextLinux::GetIndexOfPreviousGrapheme(size_t position) { | |
| 174 EnsureLayout(); | |
| 175 return Utf16IndexOfAdjacentGrapheme(position, PREVIOUS); | |
| 176 } | |
| 177 | |
| 178 SelectionModel RenderTextLinux::GetSelectionModelForVisualLeftmost() const { | |
| 179 const char* layout_text = pango_layout_get_text(layout_); | |
| 180 PangoDirection base_dir = | |
| 181 pango_find_base_dir(layout_text, strlen(layout_text)); | |
| 182 | |
| 183 if (base_dir == PANGO_DIRECTION_RTL || base_dir == PANGO_DIRECTION_WEAK_RTL) { | |
| 184 GSList* first_visual_run = layout_line_->runs; | |
| 185 if (first_visual_run) { | |
| 186 PangoItem* item = | |
| 187 reinterpret_cast<PangoLayoutRun*>(first_visual_run->data)->item; | |
| 188 if (item->analysis.level % 2 == 0) { // LTR. | |
| 189 size_t utf16_index = Utf8IndexToUtf16Index(text(), item->offset); | |
| 190 return SelectionModel( | |
| 191 text().length(), utf16_index, SelectionModel::LEADING); | |
| 192 } else { // RTL. | |
| 193 size_t utf16_index = Utf8IndexToUtf16Index( | |
| 194 text(), item->offset + item->length); | |
| 195 utf16_index = Utf16IndexOfAdjacentGrapheme(utf16_index, PREVIOUS); | |
| 196 return SelectionModel( | |
| 197 text().length(), utf16_index, SelectionModel::TRAILING); | |
| 198 } | |
| 199 } | |
| 200 } | |
| 201 return SelectionModel(0, 0, SelectionModel::LEADING); | |
| 202 } | |
| 203 | |
| 204 GSList* RenderTextLinux::GetRunContainsCaretPos(const SelectionModel& current, | |
| 205 CursorMovementDirection dir, | |
| 206 bool* at_boundary) const { | |
| 207 GSList* run_contains_caret_pos = NULL; | |
| 208 *at_boundary = false; | |
| 209 | |
| 210 GSList* run = layout_line_->runs; | |
| 211 while (run) { | |
| 212 PangoItem* pango_item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
| 213 size_t caret_pos = current.caret_pos(); | |
| 214 size_t run_end_utf16_index = | |
| 215 Utf8IndexToUtf16Index(text(), pango_item->offset + pango_item->length); | |
| 216 size_t run_start_utf16_index = | |
| 217 Utf8IndexToUtf16Index(text(), pango_item->offset); | |
| 218 | |
| 219 if (caret_pos >= run_start_utf16_index && caret_pos < run_end_utf16_index) { | |
| 220 run_contains_caret_pos = run; | |
| 221 | |
| 222 size_t selection_end = current.selection_end(); | |
| 223 SelectionModel::CaretPlacement caret_placement = | |
| 224 current.caret_placement(); | |
| 225 | |
| 226 if (caret_placement == SelectionModel::LEADING && | |
| 227 caret_pos == run_start_utf16_index) { | |
| 228 *at_boundary = (dir == RIGHT && pango_item->analysis.level % 2) || | |
| 229 (dir == LEFT && pango_item->analysis.level % 2 == 0); | |
| 230 } else if (caret_placement == SelectionModel::TRAILING && | |
| 231 selection_end == run_end_utf16_index) { | |
| 232 *at_boundary = (dir == RIGHT && pango_item->analysis.level % 2 == 0) || | |
| 233 (dir == LEFT && pango_item->analysis.level % 2); | |
| 234 } | |
| 235 break; | |
| 236 } | |
| 237 run = run->next; | |
| 238 } | |
| 239 return run_contains_caret_pos; | |
| 240 } | |
| 241 | |
| 242 size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme( | |
| 243 size_t utf16_index_of_current_grapheme, RelativeLogicalPosition pos) const { | |
| 244 PangoLogAttr* log_attrs; | |
| 245 gint n_attrs; | |
| 246 pango_layout_get_log_attrs(layout_, &log_attrs, &n_attrs); | |
| 247 | |
| 248 const char* layout_text = pango_layout_get_text(layout_); | |
| 249 const char* ch = layout_text + | |
| 250 Utf16IndexToUtf8Index(text(), utf16_index_of_current_grapheme); | |
| 251 int char_offset = static_cast<int>(g_utf8_pointer_to_offset(layout_text, ch)); | |
| 252 | |
| 253 if (pos == PREVIOUS) { | |
| 254 if (ch > layout_text) { | |
| 255 do { | |
| 256 ch = g_utf8_find_prev_char(layout_text, ch); | |
| 257 --char_offset; | |
| 258 } while (ch && *ch && !log_attrs[char_offset].is_cursor_position); | |
| 259 if (!ch) | |
| 260 ch = layout_text; | |
| 261 } | |
| 262 } else { | |
| 263 if (ch < layout_text + strlen(layout_text)) { | |
| 264 do { | |
| 265 ch = g_utf8_find_next_char(ch, NULL); | |
| 266 ++char_offset; | |
| 267 } while (ch && *ch && !log_attrs[char_offset].is_cursor_position); | |
| 268 if (ch >= layout_text + strlen(layout_text)) | |
| 269 ch = layout_text + strlen(layout_text); | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 size_t utf8_index = static_cast<size_t>(ch - layout_text); | |
| 274 // TODO(xji): how costly those g_free is? | |
|
oshima
2011/08/19 18:44:07
negligible compared to other stuff we're doing.
xji
2011/08/20 00:53:05
comment removed.
| |
| 275 g_free(log_attrs); | |
| 276 return utf8_index; | |
| 277 } | |
| 278 | |
| 279 size_t RenderTextLinux::Utf16IndexOfAdjacentGrapheme( | |
| 280 size_t utf16_index_of_current_grapheme, RelativeLogicalPosition pos) const { | |
| 281 size_t utf8_index = Utf8IndexOfAdjacentGrapheme( | |
| 282 utf16_index_of_current_grapheme, pos); | |
| 283 return Utf8IndexToUtf16Index(text(), utf8_index); | |
| 284 } | |
| 285 | |
| 286 SelectionModel RenderTextLinux::FirstSelectionModelInsideRun( | |
| 287 const PangoItem* run) const { | |
| 288 size_t utf16_index = Utf8IndexToUtf16Index(text(), run->offset); | |
|
oshima
2011/08/19 18:44:07
indnet
xji
2011/08/20 00:53:05
Done.
| |
| 289 return SelectionModel(Utf16IndexOfAdjacentGrapheme(utf16_index, NEXT), | |
| 290 utf16_index, | |
| 291 SelectionModel::TRAILING); | |
| 292 } | |
| 293 | |
| 294 SelectionModel RenderTextLinux::LastSelectionModelInsideRun( | |
| 295 const PangoItem* run) const { | |
| 296 size_t utf16_index = Utf8IndexToUtf16Index( | |
| 297 text(), run->offset + run->length); | |
|
oshima
2011/08/19 18:44:07
ditto
xji
2011/08/20 00:53:05
Done.
| |
| 298 utf16_index = Utf16IndexOfAdjacentGrapheme(utf16_index, PREVIOUS); | |
| 299 return SelectionModel(utf16_index, utf16_index, SelectionModel::LEADING); | |
| 300 } | |
| 301 | |
| 302 SelectionModel RenderTextLinux::LeftmostSelectionModelInsideRun( | |
| 303 const PangoItem* run) const { | |
| 304 if (run->analysis.level % 2 == 0) | |
| 305 return FirstSelectionModelInsideRun(run); | |
| 306 return LastSelectionModelInsideRun(run); | |
|
oshima
2011/08/19 18:44:07
can these be
return run->analysis.level % 2 == 0 ?
xji
2011/08/20 00:53:05
Done.
| |
| 307 } | |
| 308 | |
| 309 SelectionModel RenderTextLinux::RightmostSelectionModelInsideRun( | |
| 310 const PangoItem* run) const { | |
| 311 if (run->analysis.level % 2) | |
| 312 return FirstSelectionModelInsideRun(run); | |
| 313 return LastSelectionModelInsideRun(run); | |
| 314 } | |
| 315 | |
| 316 bool RenderTextLinux::GetVisuallyAdjacentCursorPositionForEnd( | |
| 317 const SelectionModel& current, | |
| 318 CursorMovementDirection dir, | |
| 319 SelectionModel* adjacent) const { | |
| 320 size_t text_length = text().length(); | |
| 321 if (current.selection_end() == text_length && | |
| 322 current.caret_pos() == text_length && | |
| 323 current.caret_placement() == SelectionModel::LEADING) { | |
| 324 // Visually rightmost END position. | |
| 325 if (dir == LEFT) { | |
| 326 GSList* last_run = GetLastRun(); | |
| 327 if (last_run) { | |
| 328 *adjacent = RightmostSelectionModelInsideRun( | |
| 329 reinterpret_cast<PangoLayoutRun*>(last_run->data)->item); | |
| 330 } | |
| 331 } | |
| 332 adjacent->set_selection_start(adjacent->selection_end()); | |
| 333 return true; | |
| 334 } | |
| 335 return false; | |
| 336 } | |
| 337 | |
| 338 // Assume caret_pos in |current| is n, 'l' represents leading in | |
| 339 // caret_placement and 't' represents trailing in caret_placement. Following | |
| 340 // is the calculation from (caret_pos, caret_placement) in |current| to | |
| 341 // (caret_pos, caret_placement, selection_end) when moving cursor left by | |
| 342 // one grapheme (for simplilcity, assume each grapheme is one character). | |
| 343 // If n is in LTR run, | |
| 344 // (n, t) ---> (n, l, n) | |
| 345 // (n, l) ---> (n-1, l, n-1) if n is inside run (not at boundary). | |
| 346 // (n, l) ---> goto across run case if n is at run boundary; | |
| 347 // If n is in RTL run, | |
| 348 // (n, l) --> (n, t, n+1); | |
| 349 // (n, t) --> (n+1, t n+2) if n is inside run; | |
| 350 // (n, t) --> goto across run case if n is at run boundar; | |
| 351 // If n is at run boudary, get its visually left run, | |
| 352 // if left run is LTR run, | |
| 353 // (n, t) --> (left run's end, l, left run's end); | |
| 354 // If rght run is RTL run, | |
| 355 // (n, t) --> (left run's begin, t, left run's begin + 1); | |
| 356 // | |
| 357 // TODO(xji): duplication with GetRightSelectionModelByGrapheme. | |
|
oshima
2011/08/19 18:44:07
do you mean you will consolidate two?
xji
2011/08/20 00:53:05
I am not sure whether to consolidate them. Althoug
| |
| 358 SelectionModel RenderTextLinux::GetLeftSelectionModelByGrapheme( | |
| 359 const SelectionModel& current) const { | |
| 360 SelectionModel left(current); | |
| 361 if (GetVisuallyAdjacentCursorPositionForEnd(current, LEFT, &left)) | |
| 362 return left; | |
| 363 | |
| 364 bool at_run_boundary = false; | |
| 365 GSList* run = GetRunContainsCaretPos(current, LEFT, &at_run_boundary); | |
| 366 if (run == NULL) { | |
| 367 left.set_selection_start(left.selection_end()); | |
| 368 return left; | |
| 369 } | |
| 370 | |
| 371 PangoItem* box = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
| 372 if (box->analysis.level % 2 == 0) { // LTR box. | |
| 373 if (current.caret_placement() == SelectionModel::TRAILING) { | |
| 374 left.set_caret_placement(SelectionModel::LEADING); | |
| 375 left.set_selection_end(left.caret_pos()); | |
| 376 } else { // caret_placement == TRAILING. | |
| 377 if (at_run_boundary) { | |
| 378 GSList* prev_run = GetPreviousRun(run); | |
| 379 if (prev_run) { | |
| 380 return RightmostSelectionModelInsideRun( | |
| 381 reinterpret_cast<PangoLayoutRun*>(prev_run->data)->item); | |
| 382 } | |
|
oshima
2011/08/19 18:44:07
nuke {}
xji
2011/08/20 00:53:05
Done.
| |
| 383 } else { // !at_run_boundary. | |
| 384 size_t prev = Utf16IndexOfAdjacentGrapheme(left.caret_pos(), PREVIOUS); | |
| 385 left.set_caret_pos(prev); | |
| 386 left.set_selection_end(prev); | |
| 387 } | |
| 388 } | |
| 389 } else { // RTL box. | |
| 390 if (current.caret_placement() == SelectionModel::LEADING) { | |
| 391 left.set_caret_placement(SelectionModel::TRAILING); | |
| 392 left.set_selection_end( | |
| 393 Utf16IndexOfAdjacentGrapheme(current.caret_pos(), NEXT)); | |
| 394 } else { // caret_placement == LEADING | |
| 395 if (at_run_boundary) { | |
| 396 GSList* prev_run = GetPreviousRun(run); | |
| 397 if (prev_run) { | |
| 398 return RightmostSelectionModelInsideRun( | |
| 399 reinterpret_cast<PangoLayoutRun*>(prev_run->data)->item); | |
| 400 } | |
|
oshima
2011/08/19 18:44:07
ditto
xji
2011/08/20 00:53:05
Done.
| |
| 401 } else { // !at_run_boundary | |
| 402 // current.seletion_end - current.caret_pos | |
| 403 // == NumOfCharsInGrapheme(caret_pos); | |
|
oshima
2011/08/19 18:44:07
is this comment or dead code?
xji
2011/08/20 00:53:05
it is comment, changed "==" to "equals to"
| |
| 404 left.set_caret_pos(current.selection_end()); | |
| 405 left.set_selection_end( | |
| 406 Utf16IndexOfAdjacentGrapheme(left.caret_pos(), NEXT)); | |
| 407 } | |
| 408 } | |
| 409 } | |
| 410 | |
| 411 left.set_selection_start(left.selection_end()); | |
| 412 return left; | |
| 413 } | |
| 414 | |
| 415 // Assume caret_pos in |current| is n, 'l' represents leading in | |
| 416 // caret_placement and 't' represents trailing in caret_placement. Following | |
| 417 // is the calculation from (caret_pos, caret_placement) in |current| to | |
| 418 // (caret_pos, caret_placement, selection_end) when moving cursor right by | |
| 419 // one grapheme (for simplilcity, assume each grapheme is one character). | |
| 420 // If n is in LTR run, | |
| 421 // (n, l) ---> (n, t, n+1) | |
| 422 // (n, t) ---> (n+1, t, n+2) if n is inside run (not at boundary). | |
| 423 // (n, t) ---> goto across run case if n is at run boundary; | |
| 424 // If n is in RTL run, | |
| 425 // (n, t) --> (n, l, n); | |
| 426 // (n, l) --> (n-1, l, n-1) if n is inside run; | |
| 427 // (n, l) --> goto across run case if n is at run boundar; | |
| 428 // If n is at run boudary, get its visually right run, | |
| 429 // if right run is LTR run, | |
| 430 // (n, t) --> (right run's begin, t, right run's begin + 1); | |
| 431 // If rght run is RTL run, | |
| 432 // (n, t) --> (right run's end, l, right run's end); | |
| 433 SelectionModel RenderTextLinux::GetRightSelectionModelByGrapheme( | |
| 434 const SelectionModel& current) const { | |
| 435 SelectionModel right(current); | |
| 436 if (GetVisuallyAdjacentCursorPositionForEnd(current, RIGHT, &right)) | |
| 437 return right; | |
| 438 | |
| 439 bool at_run_boundary = false; | |
| 440 GSList* run = GetRunContainsCaretPos(current, RIGHT, &at_run_boundary); | |
| 441 if (run == NULL) { | |
| 442 right.set_selection_start(right.selection_end()); | |
| 443 return right; | |
| 444 } | |
| 445 | |
| 446 PangoItem* box = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
| 447 if (box->analysis.level % 2 == 0) { // LTR box. | |
| 448 if (current.caret_placement() == SelectionModel::LEADING) { | |
| 449 right.set_caret_placement(SelectionModel::TRAILING); | |
| 450 right.set_selection_end( | |
| 451 Utf16IndexOfAdjacentGrapheme(current.caret_pos(), NEXT)); | |
| 452 } else { // caret_placement == TRAILING. | |
| 453 if (at_run_boundary) { | |
| 454 if (run->next) { | |
| 455 return LeftmostSelectionModelInsideRun( | |
| 456 reinterpret_cast<PangoLayoutRun*>(run->next->data)->item); | |
| 457 } | |
| 458 } else { // !at_run_boundary. | |
| 459 // current.seletion_end - current.caret_pos | |
| 460 // == NumOfCharsInGrapheme(caret_pos); | |
| 461 right.set_caret_pos(current.selection_end()); | |
| 462 right.set_selection_end( | |
| 463 Utf16IndexOfAdjacentGrapheme(right.caret_pos(), NEXT)); | |
| 464 } | |
| 465 } | |
| 466 } else { // RTL box. | |
| 467 if (current.caret_placement() == SelectionModel::TRAILING) { | |
| 468 right.set_caret_placement(SelectionModel::LEADING); | |
| 469 right.set_selection_end(right.caret_pos()); | |
| 470 } else { // caret_placement == LEADING | |
| 471 if (at_run_boundary) { | |
| 472 if (run->next) { | |
| 473 return LeftmostSelectionModelInsideRun( | |
| 474 reinterpret_cast<PangoLayoutRun*>(run->next->data)->item); | |
| 475 } | |
| 476 } else { // !at_run_boundary | |
| 477 // current.seletion_end - current.caret_pos | |
| 478 // == NumOfCharsInGrapheme(caret_pos); | |
| 479 size_t prev = Utf16IndexOfAdjacentGrapheme(right.caret_pos(), PREVIOUS); | |
| 480 right.set_caret_pos(prev); | |
| 481 right.set_selection_end(prev); | |
| 482 } | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 right.set_selection_start(right.selection_end()); | |
| 487 return right; | |
| 488 } | |
| 489 | |
| 490 PangoLayout* RenderTextLinux::EnsureLayout() { | |
| 491 if (layout_ == NULL) { | |
| 492 CanvasSkia canvas(display_rect().width(), display_rect().height(), false); | |
| 493 // TODO(xji): how costly is the Begin/EndPlatformPaint? Can I get cairo | |
| 494 // context in another way? | |
| 495 cairo_t* cr = skia::BeginPlatformPaint(&canvas); | |
| 496 | |
| 497 layout_ = pango_cairo_create_layout(cr); | |
| 498 SetupPangoLayout(layout_, text(), default_style().font, | |
| 499 display_rect().width(), GetTextDirection(), | |
| 500 CanvasSkia::DefaultCanvasTextAlignment()); | |
| 501 pango_layout_set_height(layout_, display_rect().height() * PANGO_SCALE); | |
| 502 SetupPangoAttributes(layout_); | |
| 503 | |
| 504 skia::EndPlatformPaint(&canvas); | |
| 505 | |
| 506 layout_line_ = pango_layout_get_line_readonly(layout_, 0); | |
| 507 } | |
| 508 return layout_; | |
| 509 } | |
| 510 | |
| 511 void RenderTextLinux::ResetLayout() { | |
| 512 // set_cached_bounds_and_offset_valid(false) is done in RenderText for every | |
| 513 // operation that trigger ResetLayout(). | |
| 514 if (layout_) { | |
| 515 g_object_unref(layout_); | |
| 516 layout_ = NULL; | |
| 517 } | |
| 518 if (layout_line_) { | |
| 519 // TODO(xji): do I need ref/unref? | |
| 520 // pango_layout_line_unref(layout_line_); | |
|
oshima
2011/08/19 18:44:07
you may want to run test with valgrind bots. (linu
xji
2011/08/20 00:53:05
submitted.
| |
| 521 layout_line_ = NULL; | |
| 522 } | |
| 523 } | |
| 524 | |
| 525 void RenderTextLinux::SetupPangoAttributes(PangoLayout* layout) { | |
| 526 PangoAttrList* attrs = pango_attr_list_new(); | |
| 527 // Set selection background color. | |
| 528 SkColor selection_color = | |
| 529 focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; | |
| 530 PangoAttribute* pango_attr = pango_attr_background_new( | |
| 531 COLOR_16_TO_32_BIT(SkColorGetR(selection_color)), | |
| 532 COLOR_16_TO_32_BIT(SkColorGetG(selection_color)), | |
| 533 COLOR_16_TO_32_BIT(SkColorGetB(selection_color))); | |
| 534 AppendPangoAttribute(MinOfSelection(), MaxOfSelection(), | |
| 535 pango_attr, attrs); | |
| 536 | |
| 537 StyleRanges ranges_of_style(style_ranges()); | |
| 538 ApplyCompositionAndSelectionStyles(&ranges_of_style); | |
| 539 | |
| 540 for (StyleRanges::const_iterator i = ranges_of_style.begin(); | |
| 541 i < ranges_of_style.end(); ++i) { | |
| 542 const Font& font = !i->underline ? i->font : | |
| 543 i->font.DeriveFont(0, i->font.GetStyle() | Font::UNDERLINED); | |
| 544 pango_attr = pango_attr_font_desc_new( | |
| 545 reinterpret_cast<PangoFontDescription*>(font.GetNativeFont())); | |
| 546 AppendPangoAttribute(i->range.start(), i->range.end(), pango_attr, attrs); | |
| 547 | |
| 548 SkColor foreground = i->foreground; | |
| 549 pango_attr = pango_attr_foreground_new( | |
| 550 COLOR_16_TO_32_BIT(SkColorGetR(foreground)), | |
| 551 COLOR_16_TO_32_BIT(SkColorGetG(foreground)), | |
| 552 COLOR_16_TO_32_BIT(SkColorGetB(foreground))); | |
| 553 AppendPangoAttribute(i->range.start(), i->range.end(), pango_attr, attrs); | |
| 554 | |
| 555 if (i->strike) { | |
| 556 pango_attr = pango_attr_strikethrough_new(true); | |
| 557 AppendPangoAttribute(i->range.start(), i->range.end(), pango_attr, attrs); | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 pango_layout_set_attributes(layout, attrs); | |
| 562 pango_attr_list_unref(attrs); | |
| 563 } | |
| 564 | |
| 565 void RenderTextLinux::AppendPangoAttribute(size_t start, | |
| 566 size_t end, | |
| 567 PangoAttribute* pango_attr, | |
| 568 PangoAttrList* attrs) { | |
| 569 pango_attr->start_index = Utf16IndexToUtf8Index(text(), start); | |
| 570 pango_attr->end_index = Utf16IndexToUtf8Index(text(), end); | |
| 571 pango_attr_list_insert(attrs, pango_attr); | |
| 572 } | |
| 573 | |
| 574 GSList* RenderTextLinux::GetPreviousRun(GSList* run) const { | |
| 575 GSList* current = layout_line_->runs; | |
| 576 GSList* prev = NULL; | |
| 577 while (current) { | |
| 578 if (current == run) | |
| 579 return prev; | |
| 580 prev = current; | |
| 581 current = current->next; | |
| 582 } | |
| 583 return NULL; | |
| 584 } | |
| 585 | |
| 586 GSList* RenderTextLinux::GetLastRun() const { | |
| 587 GSList* current = layout_line_->runs; | |
| 588 while (current && current->next) { | |
| 589 current = current->next; | |
| 590 } | |
| 591 return current; | |
| 592 } | |
| 593 | |
| 594 size_t RenderTextLinux::Utf16IndexToUtf8Index(const string16& text, | |
| 595 size_t index) const { | |
|
oshima
2011/08/19 18:44:07
so there is no library which does this?
xji
2011/08/20 00:53:05
there is no ICU library does that directly without
| |
| 596 int utf8_index = 0; | |
| 597 for (size_t i = 0; i < index; ++i) { | |
| 598 if (text[i] < 0x80) { | |
| 599 ++utf8_index; | |
| 600 } else if (text[i] < 0x800) { | |
| 601 utf8_index += 2; | |
| 602 } else { | |
| 603 if ((U16_IS_LEAD(text[i]))) { | |
| 604 if (i + 1 < index) { | |
| 605 if (U16_IS_TRAIL(text[i + 1])) { | |
| 606 // A surrogate pair. | |
| 607 utf8_index += 4; | |
| 608 ++i; | |
| 609 continue; | |
| 610 } | |
| 611 } else if (i + 1 < text.length() && U16_IS_TRAIL(text[i + 1])) { | |
| 612 // A split surrogate pair, stop at the leading. | |
| 613 break; | |
| 614 } | |
| 615 } | |
| 616 utf8_index += 3; | |
| 617 } | |
| 618 } | |
| 619 return utf8_index; | |
| 620 } | |
| 621 | |
| 622 | |
| 623 size_t RenderTextLinux::Utf8IndexToUtf16Index(const string16& text, | |
| 624 size_t index) const { | |
| 625 // TODO(xji): DUP with utf16IndexToUtf8Index. | |
| 626 size_t utf8_index = 0; | |
| 627 size_t utf16_index = 0; | |
| 628 for (; utf16_index < text.length(); ++utf16_index) { | |
| 629 if (text[utf16_index] < 0x80) { | |
| 630 ++utf8_index; | |
| 631 } else if (text[utf16_index] < 0x800) { | |
| 632 utf8_index += 2; | |
| 633 } else { | |
| 634 if ((U16_IS_LEAD(text[utf16_index]))) { | |
| 635 if (utf16_index + 1 < text.length()) { | |
| 636 if (U16_IS_TRAIL(text[utf16_index + 1])) { | |
| 637 // A surrogate pair. | |
| 638 utf8_index += 4; | |
| 639 if (utf8_index >= index) | |
| 640 break; | |
| 641 ++utf16_index; | |
| 642 continue; | |
| 643 } | |
| 644 } | |
| 645 } | |
| 646 utf8_index += 3; | |
| 647 } | |
| 648 if (utf8_index > index) | |
| 649 break; | |
| 650 } | |
| 651 return utf16_index; | |
| 652 } | |
| 653 | |
| 654 void RenderTextLinux::AdjustBoundsForNonInsertMode( | |
| 655 const SelectionModel& selection, | |
| 656 const PangoRectangle& pos, | |
| 657 Rect* bounds) const { | |
| 658 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
| 659 if (caret_placement == SelectionModel::LEADING) { | |
| 660 if (pos.width > 0) { | |
| 661 bounds->set_width(pos.width); | |
| 662 } else { | |
| 663 bounds->set_x(pos.x + pos.width); | |
| 664 bounds->set_width(-pos.width); | |
| 665 } | |
| 666 } else { | |
| 667 // TODO(xji): what is the expected behavior? For example, "abcFED", | |
| 668 // when |selection| == SelectionModel(3, 2, TRAILING), in insert-mode, | |
| 669 // cursor is at right of 'c'. In non-insert-mode, is the bounds around 'D'? | |
| 670 } | |
| 671 } | |
| 672 | |
| 20 } // namespace gfx | 673 } // namespace gfx |
| OLD | NEW |