15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/canvas.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/i18n/rtl.h" 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h" 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/scoped_ptr.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/font_list.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/insets.h" 1258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "ui/gfx/range/range.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/rect.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/render_text.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/shadow_value.h" 1658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "ui/gfx/text_elider.h" 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/text_utils.h" 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace gfx { 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 23f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#if defined(OS_WIN) 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// If necessary, wraps |text| with RTL/LTR directionality characters based on 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// |flags| and |text| content. 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Returns true if the text will be rendered right-to-left. 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// TODO(msw): Nix this, now that RenderTextWin supports directionality directly. 28868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)bool AdjustStringDirection(int flags, base::string16* text) { 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(msw): FORCE_LTR_DIRECTIONALITY does not work for RTL text now. 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If the string is empty or LTR was forced, simply return false since the 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // default RenderText directionality is already LTR. 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (text->empty() || (flags & Canvas::FORCE_LTR_DIRECTIONALITY)) 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If RTL is forced, apply it to the string. 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (flags & Canvas::FORCE_RTL_DIRECTIONALITY) { 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::i18n::WrapStringWithRTLFormatting(text); 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If a direction wasn't forced but the UI language is RTL and there were 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // strong RTL characters, ensure RTL is applied. 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (base::i18n::IsRTL() && base::i18n::StringContainsStrongRTLChars(*text)) { 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::i18n::WrapStringWithRTLFormatting(text); 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // In the default case, the string should be rendered as LTR. RenderText's 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // default directionality is LTR, so the text doesn't need to be wrapped. 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Note that individual runs within the string may still be rendered RTL 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // (which will be the case for RTL text under non-RTL locales, since under RTL 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // locales it will be handled by the if statement above). 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#endif // defined(OS_WIN) 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Checks each pixel immediately adjacent to the given pixel in the bitmap. If 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// any of them are not the halo color, returns true. This defines the halo of 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// pixels that will appear around the text. Note that we have to check each 613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// pixel against both the halo color and transparent since 623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// |DrawStringRectWithHalo| will modify the bitmap as it goes, and cleared 633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// pixels shouldn't count as changed. 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool PixelShouldGetHalo(const SkBitmap& bitmap, 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int x, int y, 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SkColor halo_color) { 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (x > 0 && 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x - 1, y) != halo_color && 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x - 1, y) != 0) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; // Touched pixel to the left. 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (x < bitmap.width() - 1 && 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x + 1, y) != halo_color && 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x + 1, y) != 0) 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; // Touched pixel to the right. 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (y > 0 && 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x, y - 1) != halo_color && 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x, y - 1) != 0) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; // Touched pixel above. 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (y < bitmap.height() - 1 && 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x, y + 1) != halo_color && 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *bitmap.getAddr32(x, y + 1) != 0) 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; // Touched pixel below. 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Strips accelerator character prefixes in |text| if needed, based on |flags|. 8746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Returns a range in |text| to underline or Range::InvalidRange() if 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// underlining is not needed. 89d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)Range StripAcceleratorChars(int flags, base::string16* text) { 902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (flags & (Canvas::SHOW_PREFIX | Canvas::HIDE_PREFIX)) { 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int char_pos = -1; 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int char_span = 0; 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *text = RemoveAcceleratorChar(*text, '&', &char_pos, &char_span); 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if ((flags & Canvas::SHOW_PREFIX) && char_pos != -1) 95d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return Range(char_pos, char_pos + char_span); 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 97d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return Range::InvalidRange(); 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Elides |text| and adjusts |range| appropriately. If eliding causes |range| 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// to no longer point to the same character in |text|, |range| is made invalid. 1023551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void ElideTextAndAdjustRange(const FontList& font_list, 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int width, 104868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::string16* text, 105d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) Range* range) { 106868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) const base::char16 start_char = 107868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) (range->IsValid() ? text->at(range->start()) : 0); 10846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) *text = ElideText(*text, font_list, width, ELIDE_TAIL); 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!range->IsValid()) 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (range->start() >= text->length() || 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) text->at(range->start()) != start_char) { 113d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) *range = Range::InvalidRange(); 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Updates |render_text| from the specified parameters. 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void UpdateRenderText(const Rect& rect, 119868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) const base::string16& text, 1203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const FontList& font_list, 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int flags, 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SkColor color, 1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) RenderText* render_text) { 1243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text->SetFontList(font_list); 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->SetText(text); 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->SetCursorEnabled(false); 1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci render_text->SetDisplayRect(rect); 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Set the text alignment explicitly based on the directionality of the UI, 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // if not specified. 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (!(flags & (Canvas::TEXT_ALIGN_CENTER | 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Canvas::TEXT_ALIGN_RIGHT | 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Canvas::TEXT_ALIGN_LEFT))) { 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) flags |= Canvas::DefaultCanvasTextAlignment(); 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (flags & Canvas::TEXT_ALIGN_RIGHT) 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) render_text->SetHorizontalAlignment(ALIGN_RIGHT); 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else if (flags & Canvas::TEXT_ALIGN_CENTER) 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) render_text->SetHorizontalAlignment(ALIGN_CENTER); 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) render_text->SetHorizontalAlignment(ALIGN_LEFT); 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (flags & Canvas::NO_SUBPIXEL_RENDERING) 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->set_background_is_transparent(true); 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) render_text->SetColor(color); 1483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const int font_style = font_list.GetFontStyle(); 1493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text->SetStyle(BOLD, (font_style & Font::BOLD) != 0); 1503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text->SetStyle(ITALIC, (font_style & Font::ITALIC) != 0); 1513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text->SetStyle(UNDERLINE, (font_style & Font::UNDERLINE) != 0); 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static 1574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void Canvas::SizeStringFloat(const base::string16& text, 1584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) const FontList& font_list, 1594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) float* width, float* height, 1604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) int line_height, 1614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) int flags) { 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK_GE(*width, 0); 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK_GE(*height, 0); 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::string16 adjusted_text = text; 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_WIN) 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AdjustStringDirection(flags, &adjusted_text); 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ((flags & MULTI_LINE) && *width != 0) { 17146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) WordWrapBehavior wrap_behavior = TRUNCATE_LONG_WORDS; 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (flags & CHARACTER_BREAK) 17346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) wrap_behavior = WRAP_LONG_WORDS; 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else if (!(flags & NO_ELLIPSIS)) 17546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) wrap_behavior = ELIDE_LONG_WORDS; 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Rect rect(*width, INT_MAX); 178868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) std::vector<base::string16> strings; 17946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) ElideRectangleText(adjusted_text, font_list, rect.width(), rect.height(), 18046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) wrap_behavior, &strings); 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); 1823551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) UpdateRenderText(rect, base::string16(), font_list, flags, 0, 1833551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text.get()); 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) float h = 0; 1864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) float w = 0; 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (size_t i = 0; i < strings.size(); ++i) { 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StripAcceleratorChars(flags, &strings[i]); 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->SetText(strings[i]); 1904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) const SizeF& string_size = render_text->GetStringSizeF(); 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) w = std::max(w, string_size.width()); 192c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) h += (i > 0 && line_height > 0) ? line_height : string_size.height(); 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *width = w; 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *height = h; 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If the string is too long, the call by |RenderTextWin| to |ScriptShape()| 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // will inexplicably fail with result E_INVALIDARG. Guard against this. 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const size_t kMaxRenderTextLength = 5000; 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (adjusted_text.length() >= kMaxRenderTextLength) { 2013551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *width = font_list.GetExpectedTextWidth(adjusted_text.length()); 2023551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *height = font_list.GetHeight(); 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); 2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Rect rect(*width, *height); 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StripAcceleratorChars(flags, &adjusted_text); 2073551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) UpdateRenderText(rect, adjusted_text, font_list, flags, 0, 2083551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text.get()); 2094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) const SizeF& string_size = render_text->GetStringSizeF(); 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *width = string_size.width(); 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *height = string_size.height(); 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void Canvas::DrawStringRectWithShadows(const base::string16& text, 2173551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const FontList& font_list, 2183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) SkColor color, 2193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const Rect& text_bounds, 2203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) int line_height, 2213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) int flags, 2223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const ShadowValues& shadows) { 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!IntersectsClipRect(text_bounds)) 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Rect clip_rect(text_bounds); 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) clip_rect.Inset(ShadowValue::GetMargin(shadows)); 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 229effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch canvas_->save(); 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ClipRect(clip_rect); 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Rect rect(text_bounds); 233868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::string16 adjusted_text = text; 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_WIN) 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AdjustStringDirection(flags, &adjusted_text); 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); 240f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) render_text->set_shadows(shadows); 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (flags & MULTI_LINE) { 24346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) WordWrapBehavior wrap_behavior = IGNORE_LONG_WORDS; 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (flags & CHARACTER_BREAK) 24546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) wrap_behavior = WRAP_LONG_WORDS; 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else if (!(flags & NO_ELLIPSIS)) 24746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) wrap_behavior = ELIDE_LONG_WORDS; 2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 249868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) std::vector<base::string16> strings; 25046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) ElideRectangleText(adjusted_text, font_list, text_bounds.width(), 25146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) text_bounds.height(), wrap_behavior, &strings); 2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (size_t i = 0; i < strings.size(); i++) { 254d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) Range range = StripAcceleratorChars(flags, &strings[i]); 2553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) UpdateRenderText(rect, strings[i], font_list, flags, color, 2563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) render_text.get()); 257c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) int line_padding = 0; 258c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (line_height > 0) 259c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) line_padding = line_height - render_text->GetStringSize().height(); 260c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else 261c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) line_height = render_text->GetStringSize().height(); 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(msw|asvitkine): Center Windows multi-line text: crbug.com/107357 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if !defined(OS_WIN) 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (i == 0) { 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(msw|asvitkine): Support multi-line text with varied heights. 267c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const int text_height = strings.size() * line_height - line_padding; 268c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) rect += Vector2d(0, (text_bounds.height() - text_height) / 2); 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 272c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) rect.set_height(line_height - line_padding); 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (range.IsValid()) 2752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) render_text->ApplyStyle(UNDERLINE, true, range); 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->SetDisplayRect(rect); 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->Draw(this); 2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) rect += Vector2d(0, line_height); 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 281d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) Range range = StripAcceleratorChars(flags, &adjusted_text); 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool elide_text = ((flags & NO_ELLIPSIS) == 0); 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_LINUX) 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // On Linux, eliding really means fading the end of the string. But only 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // for LTR text. RTL text is still elided (on the left) with "...". 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (elide_text) { 2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->SetText(adjusted_text); 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (render_text->GetTextDirection() == base::i18n::LEFT_TO_RIGHT) { 29046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) render_text->SetElideBehavior(FADE_TAIL); 2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) elide_text = false; 2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (elide_text) { 29746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) ElideTextAndAdjustRange(font_list, text_bounds.width(), &adjusted_text, 2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) &range); 2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3013551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) UpdateRenderText(rect, adjusted_text, font_list, flags, color, 3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text.get()); 3032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (range.IsValid()) 3042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) render_text->ApplyStyle(UNDERLINE, true, range); 3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->Draw(this); 3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) canvas_->restore(); 3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3113551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void Canvas::DrawStringRectWithHalo(const base::string16& text, 3123551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const FontList& font_list, 3133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) SkColor text_color, 3143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) SkColor halo_color_in, 3153551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) const Rect& display_rect, 3163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) int flags) { 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Some callers will have semitransparent halo colors, which we don't handle 3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // (since the resulting image can have 1-bit transparency only). 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SkColor halo_color = SkColorSetA(halo_color_in, 0xFF); 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Create a temporary buffer filled with the halo color. It must leave room 3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // for the 1-pixel border around the text. 3233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) Size size(display_rect.width() + 2, display_rect.height() + 2); 3241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) Canvas text_canvas(size, image_scale(), false); 3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SkPaint bkgnd_paint; 3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bkgnd_paint.setColor(halo_color); 3272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) text_canvas.DrawRect(Rect(size), bkgnd_paint); 3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw the text into the temporary buffer. This will have correct 3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // ClearType since the background color is the same as the halo color. 3313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) text_canvas.DrawStringRectWithFlags( 3323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) text, font_list, text_color, 3333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) Rect(1, 1, display_rect.width(), display_rect.height()), flags); 3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) uint32_t halo_premul = SkPreMultiplyColor(halo_color); 3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SkBitmap& text_bitmap = const_cast<SkBitmap&>( 3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) skia::GetTopDevice(*text_canvas.sk_canvas())->accessBitmap(true)); 3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (int cur_y = 0; cur_y < text_bitmap.height(); cur_y++) { 3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) uint32_t* text_row = text_bitmap.getAddr32(0, cur_y); 3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (int cur_x = 0; cur_x < text_bitmap.width(); cur_x++) { 3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (text_row[cur_x] == halo_premul) { 3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // This pixel was not touched by the text routines. See if it borders 3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // a touched pixel in any of the 4 directions (not diagonally). 3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!PixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul)) 3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) text_row[cur_x] = 0; // Make transparent. 3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) text_row[cur_x] |= 0xff << SK_A32_SHIFT; // Make opaque. 3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw the halo bitmap with blur. 3542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ImageSkia text_image = ImageSkia(ImageSkiaRep(text_bitmap, 35568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles) text_canvas.image_scale())); 3563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) DrawImageInt(text_image, display_rect.x() - 1, display_rect.y() - 1); 3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 35946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void Canvas::DrawFadedString(const base::string16& text, 36046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) const FontList& font_list, 36146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) SkColor color, 36246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) const Rect& display_rect, 36346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) int flags) { 3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If the whole string fits in the destination then just draw it directly. 3653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) if (GetStringWidth(text, font_list) <= display_rect.width()) { 3663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) DrawStringRectWithFlags(text, font_list, color, display_rect, flags); 3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 370116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch // Align with forced content directionality, overriding alignment flags. 371116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if (flags & FORCE_RTL_DIRECTIONALITY) { 372116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch flags &= ~(TEXT_ALIGN_CENTER | TEXT_ALIGN_LEFT); 373116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch flags |= TEXT_ALIGN_RIGHT; 374116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch } else if (flags & FORCE_LTR_DIRECTIONALITY) { 375116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch flags &= ~(TEXT_ALIGN_CENTER | TEXT_ALIGN_RIGHT); 376116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch flags |= TEXT_ALIGN_LEFT; 377116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch } else if (!(flags & TEXT_ALIGN_LEFT) && !(flags & TEXT_ALIGN_RIGHT)) { 378116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch // Also align with content directionality instead of fading both ends. 37946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) flags &= ~TEXT_ALIGN_CENTER; 38046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) const bool is_rtl = base::i18n::GetFirstStrongCharacterDirection(text) == 38146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) base::i18n::RIGHT_TO_LEFT; 38246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) flags |= is_rtl ? TEXT_ALIGN_RIGHT : TEXT_ALIGN_LEFT; 3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 38446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) flags |= NO_ELLIPSIS; 3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 38646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); 3872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Rect rect = display_rect; 3881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) UpdateRenderText(rect, text, font_list, flags, color, render_text.get()); 38946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) render_text->SetElideBehavior(FADE_TAIL); 3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 391effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch canvas_->save(); 3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ClipRect(display_rect); 3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) render_text->Draw(this); 3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) canvas_->restore(); 3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace gfx 398