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.h" | 5 #include "ui/gfx/render_text.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/i18n/break_iterator.h" | 9 #include "base/i18n/break_iterator.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 123 CaretPlacement placement) { | 123 CaretPlacement placement) { |
| 124 selection_start_ = start; | 124 selection_start_ = start; |
| 125 selection_end_ = end; | 125 selection_end_ = end; |
| 126 caret_pos_ = pos; | 126 caret_pos_ = pos; |
| 127 caret_placement_ = placement; | 127 caret_placement_ = placement; |
| 128 } | 128 } |
| 129 | 129 |
| 130 RenderText::~RenderText() { | 130 RenderText::~RenderText() { |
| 131 } | 131 } |
| 132 | 132 |
| 133 void RenderText::set_display_rect(const Rect& r) { | |
| 134 display_rect_ = r; | |
| 135 } | |
| 136 | |
| 137 void RenderText::SetText(const string16& text) { | 133 void RenderText::SetText(const string16& text) { |
| 138 size_t old_text_length = text_.length(); | 134 size_t old_text_length = text_.length(); |
| 139 text_ = text; | 135 text_ = text; |
| 140 | 136 |
| 141 // Update the style ranges as needed. | 137 // Update the style ranges as needed. |
| 142 if (text_.empty()) { | 138 if (text_.empty()) { |
| 143 style_ranges_.clear(); | 139 style_ranges_.clear(); |
| 144 } else if (style_ranges_.empty()) { | 140 } else if (style_ranges_.empty()) { |
| 145 ApplyDefaultStyle(); | 141 ApplyDefaultStyle(); |
| 146 } else if (text_.length() > old_text_length) { | 142 } else if (text_.length() > old_text_length) { |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 164 } | 160 } |
| 165 | 161 |
| 166 void RenderText::SetSelectionModel(const SelectionModel& sel) { | 162 void RenderText::SetSelectionModel(const SelectionModel& sel) { |
| 167 size_t start = sel.selection_start(); | 163 size_t start = sel.selection_start(); |
| 168 size_t end = sel.selection_end(); | 164 size_t end = sel.selection_end(); |
| 169 selection_model_.set_selection_start(std::min(start, text().length())); | 165 selection_model_.set_selection_start(std::min(start, text().length())); |
| 170 selection_model_.set_selection_end(std::min(end, text().length())); | 166 selection_model_.set_selection_end(std::min(end, text().length())); |
| 171 selection_model_.set_caret_pos(std::min(sel.caret_pos(), text().length())); | 167 selection_model_.set_caret_pos(std::min(sel.caret_pos(), text().length())); |
| 172 selection_model_.set_caret_placement(sel.caret_placement()); | 168 selection_model_.set_caret_placement(sel.caret_placement()); |
| 173 | 169 |
| 174 cursor_bounds_valid_ = false; | 170 cached_bounds_and_offset_valid_ = false; |
| 171 } | |
| 172 | |
| 173 void RenderText::SetDisplayRect(const Rect& r) { | |
| 174 display_rect_ = r; | |
| 175 cached_bounds_and_offset_valid_ = false; | |
|
xji
2011/08/10 00:36:15
does it make sense to invalidate this in SetText()
msw
2011/08/10 01:27:39
Done.
| |
| 175 } | 176 } |
| 176 | 177 |
| 177 size_t RenderText::GetCursorPosition() const { | 178 size_t RenderText::GetCursorPosition() const { |
| 178 return selection_model_.selection_end(); | 179 return selection_model_.selection_end(); |
| 179 } | 180 } |
| 180 | 181 |
| 181 void RenderText::SetCursorPosition(const size_t position) { | 182 void RenderText::SetCursorPosition(const size_t position) { |
| 182 SelectionModel sel(selection_model()); | 183 SelectionModel sel(selection_model()); |
| 183 sel.set_selection_start(position); | 184 sel.set_selection_start(position); |
| 184 sel.set_selection_end(position); | 185 sel.set_selection_end(position); |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 321 void RenderText::ApplyStyleRange(StyleRange style_range) { | 322 void RenderText::ApplyStyleRange(StyleRange style_range) { |
| 322 const ui::Range& new_range = style_range.range; | 323 const ui::Range& new_range = style_range.range; |
| 323 if (!new_range.IsValid() || new_range.is_empty()) | 324 if (!new_range.IsValid() || new_range.is_empty()) |
| 324 return; | 325 return; |
| 325 CHECK(!new_range.is_reversed()); | 326 CHECK(!new_range.is_reversed()); |
| 326 CHECK(ui::Range(0, text_.length()).Contains(new_range)); | 327 CHECK(ui::Range(0, text_.length()).Contains(new_range)); |
| 327 ApplyStyleRangeImpl(&style_ranges_, style_range); | 328 ApplyStyleRangeImpl(&style_ranges_, style_range); |
| 328 #ifndef NDEBUG | 329 #ifndef NDEBUG |
| 329 CheckStyleRanges(style_ranges_, text_.length()); | 330 CheckStyleRanges(style_ranges_, text_.length()); |
| 330 #endif | 331 #endif |
| 331 // TODO(xji): only invalidate cursor_bounds if font or underline change. | 332 // TODO(xji): only invalidate if font or underline changes. |
| 332 cursor_bounds_valid_ = false; | 333 cached_bounds_and_offset_valid_ = false; |
| 333 } | 334 } |
| 334 | 335 |
| 335 void RenderText::ApplyDefaultStyle() { | 336 void RenderText::ApplyDefaultStyle() { |
| 336 style_ranges_.clear(); | 337 style_ranges_.clear(); |
| 337 StyleRange style = StyleRange(default_style_); | 338 StyleRange style = StyleRange(default_style_); |
| 338 style.range.set_end(text_.length()); | 339 style.range.set_end(text_.length()); |
| 339 style_ranges_.push_back(style); | 340 style_ranges_.push_back(style); |
| 340 cursor_bounds_valid_ = false; | 341 cached_bounds_and_offset_valid_ = false; |
| 341 } | 342 } |
| 342 | 343 |
| 343 base::i18n::TextDirection RenderText::GetTextDirection() const { | 344 base::i18n::TextDirection RenderText::GetTextDirection() const { |
| 344 // TODO(msw): Bidi implementation, intended to replace the functionality added | 345 // TODO(msw): Bidi implementation, intended to replace the functionality added |
| 345 // in crrev.com/91881 (discussed in codereview.chromium.org/7324011). | 346 // in crrev.com/91881 (discussed in codereview.chromium.org/7324011). |
| 346 return base::i18n::LEFT_TO_RIGHT; | 347 return base::i18n::LEFT_TO_RIGHT; |
| 347 } | 348 } |
| 348 | 349 |
| 349 int RenderText::GetStringWidth() { | 350 int RenderText::GetStringWidth() { |
| 350 return GetSubstringBounds(0, text_.length())[0].width(); | 351 return default_style_.font.GetStringWidth(text()); |
| 351 } | 352 } |
| 352 | 353 |
| 353 void RenderText::Draw(Canvas* canvas) { | 354 void RenderText::Draw(Canvas* canvas) { |
| 354 // Clip the canvas to the text display area. | 355 // Clip the canvas to the text display area. |
| 355 canvas->ClipRectInt(display_rect_.x(), display_rect_.y(), | 356 canvas->ClipRectInt(display_rect_.x(), display_rect_.y(), |
| 356 display_rect_.width(), display_rect_.height()); | 357 display_rect_.width(), display_rect_.height()); |
| 357 | 358 |
| 358 // Draw the selection. | 359 // Draw the selection. |
| 359 std::vector<Rect> selection(GetSubstringBounds(GetSelectionStart(), | 360 std::vector<Rect> selection(GetSubstringBounds(GetSelectionStart(), |
| 360 GetCursorPosition())); | 361 GetCursorPosition())); |
| 361 SkColor selection_color = | 362 SkColor selection_color = |
| 362 focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; | 363 focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; |
| 363 for (std::vector<Rect>::const_iterator i = selection.begin(); | 364 for (std::vector<Rect>::const_iterator i = selection.begin(); |
| 364 i < selection.end(); ++i) { | 365 i < selection.end(); ++i) { |
| 365 Rect r(*i); | 366 Rect r(*i); |
| 366 r.Offset(display_rect_.origin()); | |
| 367 r.Offset(display_offset_); | |
| 368 // Center the rect vertically in |display_rect_|. | |
| 369 r.Offset(Point(0, (display_rect_.height() - r.height()) / 2)); | |
| 370 canvas->FillRectInt(selection_color, r.x(), r.y(), r.width(), r.height()); | 367 canvas->FillRectInt(selection_color, r.x(), r.y(), r.width(), r.height()); |
| 371 } | 368 } |
| 372 | 369 |
| 373 // Create a temporary copy of the style ranges for composition and selection. | 370 // Create a temporary copy of the style ranges for composition and selection. |
| 374 StyleRanges style_ranges(style_ranges_); | 371 StyleRanges style_ranges(style_ranges_); |
| 375 ApplyCompositionAndSelectionStyles(&style_ranges); | 372 ApplyCompositionAndSelectionStyles(&style_ranges); |
| 376 | 373 |
| 377 // Draw the text. | 374 // Draw the text. |
| 378 Rect bounds(display_rect_); | 375 Rect bounds(display_rect_); |
| 379 bounds.Offset(display_offset_); | 376 bounds.Offset(GetUpdatedDisplayOffset()); |
| 380 for (StyleRanges::const_iterator i = style_ranges.begin(); | 377 for (StyleRanges::const_iterator i = style_ranges.begin(); |
| 381 i < style_ranges.end(); ++i) { | 378 i < style_ranges.end(); ++i) { |
| 382 const Font& font = !i->underline ? i->font : | 379 const Font& font = !i->underline ? i->font : |
| 383 i->font.DeriveFont(0, i->font.GetStyle() | Font::UNDERLINED); | 380 i->font.DeriveFont(0, i->font.GetStyle() | Font::UNDERLINED); |
| 384 string16 text = text_.substr(i->range.start(), i->range.length()); | 381 string16 text = text_.substr(i->range.start(), i->range.length()); |
| 385 bounds.set_width(font.GetStringWidth(text)); | 382 bounds.set_width(font.GetStringWidth(text)); |
| 386 canvas->DrawStringInt(text, font, i->foreground, bounds); | 383 canvas->DrawStringInt(text, font, i->foreground, bounds); |
| 387 | 384 |
| 388 // Draw the strikethrough. | 385 // Draw the strikethrough. |
| 389 if (i->strike) { | 386 if (i->strike) { |
| 390 SkPaint paint; | 387 SkPaint paint; |
| 391 paint.setAntiAlias(true); | 388 paint.setAntiAlias(true); |
| 392 paint.setStyle(SkPaint::kFill_Style); | 389 paint.setStyle(SkPaint::kFill_Style); |
| 393 paint.setColor(i->foreground); | 390 paint.setColor(i->foreground); |
| 394 paint.setStrokeWidth(kStrikeWidth); | 391 paint.setStrokeWidth(kStrikeWidth); |
| 395 canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()), | 392 canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()), |
| 396 SkIntToScalar(bounds.bottom()), | 393 SkIntToScalar(bounds.bottom()), |
| 397 SkIntToScalar(bounds.right()), | 394 SkIntToScalar(bounds.right()), |
| 398 SkIntToScalar(bounds.y()), | 395 SkIntToScalar(bounds.y()), |
| 399 paint); | 396 paint); |
| 400 } | 397 } |
| 401 | 398 |
| 402 bounds.set_x(bounds.x() + bounds.width()); | 399 bounds.set_x(bounds.x() + bounds.width()); |
| 403 } | 400 } |
| 404 | 401 |
| 405 // Paint cursor. Replace cursor is drawn as rectangle for now. | 402 // Paint cursor. Replace cursor is drawn as rectangle for now. |
| 406 if (cursor_visible() && focused()) { | 403 Rect cursor(GetUpdatedCursorBounds()); |
| 407 bounds = CursorBounds(); | 404 if (cursor_visible() && focused() && !cursor.IsEmpty()) |
| 408 bounds.Offset(display_offset_); | 405 canvas->DrawRectInt(kCursorColor, cursor.x(), cursor.y(), |
| 409 if (!bounds.IsEmpty()) | 406 cursor.width(), cursor.height()); |
| 410 canvas->DrawRectInt(kCursorColor, | |
| 411 bounds.x(), | |
| 412 bounds.y(), | |
| 413 bounds.width(), | |
| 414 bounds.height()); | |
| 415 } | |
| 416 } | 407 } |
| 417 | 408 |
| 418 SelectionModel RenderText::FindCursorPosition(const Point& point) { | 409 SelectionModel RenderText::FindCursorPosition(const Point& point) { |
| 419 const Font& font = default_style_.font; | 410 const Font& font = default_style_.font; |
| 420 int left = 0; | 411 int left = 0; |
| 421 int left_pos = 0; | 412 int left_pos = 0; |
| 422 int right = font.GetStringWidth(text()); | 413 int right = font.GetStringWidth(text()); |
| 423 int right_pos = text().length(); | 414 int right_pos = text().length(); |
| 424 | 415 |
| 425 int x = point.x(); | 416 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x()); |
| 426 if (x <= left) return SelectionModel(left_pos); | 417 if (x <= left) return SelectionModel(left_pos); |
| 427 if (x >= right) return SelectionModel(right_pos); | 418 if (x >= right) return SelectionModel(right_pos); |
| 428 // binary searching the cursor position. | 419 // binary searching the cursor position. |
| 429 // TODO(oshima): use the center of character instead of edge. | 420 // TODO(oshima): use the center of character instead of edge. |
| 430 // Binary search may not work for language like arabic. | 421 // Binary search may not work for language like arabic. |
| 431 while (std::abs(static_cast<long>(right_pos - left_pos)) > 1) { | 422 while (std::abs(static_cast<long>(right_pos - left_pos)) > 1) { |
| 432 int pivot_pos = left_pos + (right_pos - left_pos) / 2; | 423 int pivot_pos = left_pos + (right_pos - left_pos) / 2; |
| 433 int pivot = font.GetStringWidth(text().substr(0, pivot_pos)); | 424 int pivot = font.GetStringWidth(text().substr(0, pivot_pos)); |
| 434 if (pivot < x) { | 425 if (pivot < x) { |
| 435 left = pivot; | 426 left = pivot; |
| 436 left_pos = pivot_pos; | 427 left_pos = pivot_pos; |
| 437 } else if (pivot == x) { | 428 } else if (pivot == x) { |
| 438 return SelectionModel(pivot_pos); | 429 return SelectionModel(pivot_pos); |
| 439 } else { | 430 } else { |
| 440 right = pivot; | 431 right = pivot; |
| 441 right_pos = pivot_pos; | 432 right_pos = pivot_pos; |
| 442 } | 433 } |
| 443 } | 434 } |
| 444 return SelectionModel(left_pos); | 435 return SelectionModel(left_pos); |
| 445 } | 436 } |
| 446 | 437 |
| 447 std::vector<Rect> RenderText::GetSubstringBounds( | 438 std::vector<Rect> RenderText::GetSubstringBounds( |
| 448 size_t from, size_t to) const { | 439 size_t from, size_t to) { |
| 449 size_t start = std::min(from, to); | 440 size_t start = std::min(from, to); |
| 450 size_t end = std::max(from, to); | 441 size_t end = std::max(from, to); |
| 451 const Font& font = default_style_.font; | 442 const Font& font = default_style_.font; |
| 452 int start_x = font.GetStringWidth(text().substr(0, start)); | 443 int start_x = font.GetStringWidth(text().substr(0, start)); |
| 453 int end_x = font.GetStringWidth(text().substr(0, end)); | 444 int end_x = font.GetStringWidth(text().substr(0, end)); |
| 454 std::vector<Rect> bounds; | 445 Rect rect(start_x, 0, end_x - start_x, font.GetHeight()); |
| 455 bounds.push_back(Rect(start_x, 0, end_x - start_x, font.GetHeight())); | 446 rect.Offset(display_rect_.origin()); |
| 456 return bounds; | 447 rect.Offset(GetUpdatedDisplayOffset()); |
| 448 // Center the rect vertically in |display_rect_|. | |
| 449 rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2)); | |
| 450 return std::vector<Rect>(1, rect); | |
| 457 } | 451 } |
| 458 | 452 |
| 459 Rect RenderText::GetCursorBounds(const SelectionModel& selection, | 453 Rect RenderText::GetCursorBounds(const SelectionModel& selection, |
| 460 bool insert_mode) { | 454 bool insert_mode) { |
| 461 size_t cursor_pos = selection.selection_end(); | 455 size_t from = selection.selection_end(); |
| 462 const Font& font = default_style_.font; | 456 size_t to = insert_mode ? from : std::min(text_.length(), from + 1); |
| 463 int x = font.GetStringWidth(text_.substr(0U, cursor_pos)); | 457 return GetSubstringBounds(from, to)[0]; |
| 464 DCHECK_GE(x, 0); | |
| 465 int h = std::min(display_rect_.height(), font.GetHeight()); | |
| 466 Rect bounds(x, (display_rect_.height() - h) / 2, 1, h); | |
| 467 if (!insert_mode && text_.length() != cursor_pos) | |
| 468 bounds.set_width(font.GetStringWidth(text_.substr(0, cursor_pos + 1)) - x); | |
| 469 return bounds; | |
| 470 } | 458 } |
| 471 | 459 |
| 472 const Rect& RenderText::CursorBounds() { | 460 const Rect& RenderText::GetUpdatedCursorBounds() { |
| 473 if (cursor_bounds_valid_ == false) { | 461 UpdateCachedBoundsAndOffset(); |
| 474 UpdateCursorBoundsAndDisplayOffset(); | |
| 475 cursor_bounds_valid_ = true; | |
| 476 } | |
| 477 return cursor_bounds_; | 462 return cursor_bounds_; |
| 478 } | 463 } |
| 479 | 464 |
| 480 RenderText::RenderText() | 465 RenderText::RenderText() |
| 481 : text_(), | 466 : text_(), |
| 482 selection_model_(), | 467 selection_model_(), |
| 483 cursor_bounds_(), | 468 cursor_bounds_(), |
| 484 cursor_bounds_valid_(false), | |
| 485 cursor_visible_(false), | 469 cursor_visible_(false), |
| 486 insert_mode_(true), | 470 insert_mode_(true), |
| 487 composition_range_(), | 471 composition_range_(), |
| 488 style_ranges_(), | 472 style_ranges_(), |
| 489 default_style_(), | 473 default_style_(), |
| 490 display_rect_(), | 474 display_rect_(), |
| 491 display_offset_() { | 475 display_offset_(), |
| 476 cached_bounds_and_offset_valid_(false) { | |
| 477 } | |
| 478 | |
| 479 const Point& RenderText::GetUpdatedDisplayOffset() { | |
| 480 UpdateCachedBoundsAndOffset(); | |
| 481 return display_offset_; | |
| 492 } | 482 } |
| 493 | 483 |
| 494 SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current, | 484 SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current, |
| 495 BreakType break_type) { | 485 BreakType break_type) { |
| 496 if (break_type == LINE_BREAK) | 486 if (break_type == LINE_BREAK) |
| 497 return SelectionModel(0, 0, SelectionModel::LEADING); | 487 return SelectionModel(0, 0, SelectionModel::LEADING); |
| 498 size_t pos = std::max(static_cast<long>(current.selection_end() - 1), | 488 size_t pos = std::max(static_cast<long>(current.selection_end() - 1), |
| 499 static_cast<long>(0)); | 489 static_cast<long>(0)); |
| 500 if (break_type == CHARACTER_BREAK) | 490 if (break_type == CHARACTER_BREAK) |
| 501 return SelectionModel(pos, pos, SelectionModel::LEADING); | 491 return SelectionModel(pos, pos, SelectionModel::LEADING); |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 577 selection_style.range.set_end(MaxOfSelection()); | 567 selection_style.range.set_end(MaxOfSelection()); |
| 578 ApplyStyleRangeImpl(style_ranges, selection_style); | 568 ApplyStyleRangeImpl(style_ranges, selection_style); |
| 579 } | 569 } |
| 580 } | 570 } |
| 581 | 571 |
| 582 bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { | 572 bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { |
| 583 return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) || | 573 return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) || |
| 584 (!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos])); | 574 (!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos])); |
| 585 } | 575 } |
| 586 | 576 |
| 587 void RenderText::UpdateCursorBoundsAndDisplayOffset() { | 577 void RenderText::UpdateCachedBoundsAndOffset() { |
| 588 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode()); | 578 if (cached_bounds_and_offset_valid_) |
| 579 return; | |
| 580 cached_bounds_and_offset_valid_ = true; | |
| 581 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); | |
| 582 cursor_bounds_.set_width(std::max(cursor_bounds_.width(), 1)); | |
| 589 // Update |display_offset_| to ensure the current cursor is visible. | 583 // Update |display_offset_| to ensure the current cursor is visible. |
| 590 int display_width = display_rect_.width(); | 584 int display_width = display_rect_.width(); |
| 591 int string_width = GetStringWidth(); | 585 int string_width = GetStringWidth(); |
| 586 int delta_offset = 0; | |
| 592 if (string_width < display_width) { | 587 if (string_width < display_width) { |
| 593 // Show all text whenever the text fits to the size. | 588 // Show all text whenever the text fits to the size. |
| 594 display_offset_.set_x(0); | 589 delta_offset = -display_offset_.x(); |
| 595 } else if ((display_offset_.x() + cursor_bounds_.right()) > display_width) { | 590 } else if (cursor_bounds_.right() > display_rect_.right()) { |
| 596 // Pan to show the cursor when it overflows to the right, | 591 // Pan to show the cursor when it overflows to the right, |
| 597 display_offset_.set_x(display_width - cursor_bounds_.right()); | 592 delta_offset = display_rect_.right() - cursor_bounds_.right(); |
| 598 } else if ((display_offset_.x() + cursor_bounds_.x()) < 0) { | 593 } else if (cursor_bounds_.x() < display_rect_.x()) { |
| 599 // Pan to show the cursor when it overflows to the left. | 594 // Pan to show the cursor when it overflows to the left. |
| 600 display_offset_.set_x(-cursor_bounds_.x()); | 595 delta_offset = display_rect_.x() - cursor_bounds_.x(); |
| 601 } | 596 } |
| 597 display_offset_.Offset(delta_offset, 0); | |
| 598 cursor_bounds_.Offset(delta_offset, 0); | |
|
xji
2011/08/10 00:36:15
In the case of both cursor_bounds_ and display_off
msw
2011/08/10 01:27:39
Your first paragraph is correct, I've added a comm
xji
2011/08/10 18:15:54
I looked at GetCursorBounds() again. And I think y
| |
| 602 } | 599 } |
| 603 | 600 |
| 604 } // namespace gfx | 601 } // namespace gfx |
| OLD | NEW |