1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/common/badge_util.h" 6 7 #include "base/logging.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "grit/ui_resources.h" 10 #include "third_party/skia/include/core/SkPaint.h" 11 #include "third_party/skia/include/core/SkTypeface.h" 12 #include "ui/base/resource/resource_bundle.h" 13 #include "ui/gfx/canvas.h" 14 #include "ui/gfx/font.h" 15 #include "ui/gfx/rect.h" 16 #include "ui/gfx/size.h" 17 18 namespace { 19 20 // Different platforms need slightly different constants to look good. 21 #if defined(OS_LINUX) && !defined(TOOLKIT_VIEWS) 22 const float kTextSize = 9.0; 23 const int kBottomMarginBrowserAction = 0; 24 const int kBottomMarginPageAction = 2; 25 const int kPadding = 2; 26 const int kTopTextPadding = 0; 27 #elif defined(OS_LINUX) && defined(TOOLKIT_VIEWS) 28 const float kTextSize = 8.0; 29 const int kBottomMarginBrowserAction = 5; 30 const int kBottomMarginPageAction = 2; 31 const int kPadding = 2; 32 const int kTopTextPadding = 1; 33 #elif defined(OS_MACOSX) 34 const float kTextSize = 9.0; 35 const int kBottomMarginBrowserAction = 5; 36 const int kBottomMarginPageAction = 2; 37 const int kPadding = 2; 38 const int kTopTextPadding = 0; 39 #else 40 const float kTextSize = 10; 41 const int kBottomMarginBrowserAction = 5; 42 const int kBottomMarginPageAction = 2; 43 const int kPadding = 2; 44 // The padding between the top of the badge and the top of the text. 45 const int kTopTextPadding = -1; 46 #endif 47 48 const int kBadgeHeight = 11; 49 const int kMaxTextWidth = 23; 50 51 // The minimum width for center-aligning the badge. 52 const int kCenterAlignThreshold = 20; 53 54 } // namespace 55 56 namespace badge_util { 57 58 SkPaint* GetBadgeTextPaintSingleton() { 59 #if defined(OS_MACOSX) 60 const char kPreferredTypeface[] = "Helvetica Bold"; 61 #else 62 const char kPreferredTypeface[] = "Arial"; 63 #endif 64 65 static SkPaint* text_paint = NULL; 66 if (!text_paint) { 67 text_paint = new SkPaint; 68 text_paint->setAntiAlias(true); 69 text_paint->setTextAlign(SkPaint::kLeft_Align); 70 71 skia::RefPtr<SkTypeface> typeface = skia::AdoptRef( 72 SkTypeface::CreateFromName(kPreferredTypeface, SkTypeface::kBold)); 73 // Skia doesn't do any font fallback---if the user is missing the font then 74 // typeface will be NULL. If we don't do manual fallback then we'll crash. 75 if (typeface) { 76 text_paint->setFakeBoldText(true); 77 } else { 78 // Fall back to the system font. We don't bold it because we aren't sure 79 // how it will look. 80 // For the most part this code path will only be hit on Linux systems 81 // that don't have Arial. 82 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 83 const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont); 84 typeface = skia::AdoptRef(SkTypeface::CreateFromName( 85 base_font.GetFontName().c_str(), SkTypeface::kNormal)); 86 DCHECK(typeface); 87 } 88 89 text_paint->setTypeface(typeface.get()); 90 // |text_paint| adds its own ref. Release the ref from CreateFontName. 91 } 92 return text_paint; 93 } 94 95 SkBitmap DrawBadgeIconOverlay(const SkBitmap& icon, 96 float font_size, 97 const base::string16& text, 98 const base::string16& fallback) { 99 const int kMinPadding = 1; 100 101 // Calculate the proper style/text overlay to render on the badge. 102 SkPaint* paint = badge_util::GetBadgeTextPaintSingleton(); 103 paint->setTextSize(SkFloatToScalar(font_size)); 104 paint->setColor(SK_ColorWHITE); 105 106 std::string badge_text = UTF16ToUTF8(text); 107 108 // See if the text will fit - otherwise use a default. 109 SkScalar text_width = paint->measureText(badge_text.c_str(), 110 badge_text.size()); 111 112 if (SkScalarRound(text_width) > (icon.width() - kMinPadding * 2)) { 113 // String is too large - use the alternate text. 114 badge_text = UTF16ToUTF8(fallback); 115 text_width = paint->measureText(badge_text.c_str(), badge_text.size()); 116 } 117 118 // When centering the text, we need to make sure there are an equal number 119 // of pixels on each side as otherwise the text looks off-center. So if the 120 // padding would be uneven, clip one pixel off the right side. 121 int badge_width = icon.width(); 122 if ((SkScalarRound(text_width) % 1) != (badge_width % 1)) 123 badge_width--; 124 125 // Render the badge bitmap and overlay into a canvas. 126 scoped_ptr<gfx::Canvas> canvas(new gfx::Canvas( 127 gfx::Size(badge_width, icon.height()), 1.0f, false)); 128 canvas->DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(icon), 0, 0); 129 130 // Draw the text overlay centered horizontally and vertically. Skia expects 131 // us to specify the lower left coordinate of the text box, which is why we 132 // add 'font_size - 1' to the height. 133 SkScalar x = (badge_width - text_width)/2; 134 SkScalar y = (icon.height() - font_size)/2 + font_size - 1; 135 canvas->sk_canvas()->drawText( 136 badge_text.c_str(), badge_text.size(), x, y, *paint); 137 138 // Return the generated image. 139 return canvas->ExtractImageRep().sk_bitmap(); 140 } 141 142 void PaintBadge(gfx::Canvas* canvas, 143 const gfx::Rect& bounds, 144 const std::string& text, 145 const SkColor& text_color_in, 146 const SkColor& background_color_in, 147 int icon_width, 148 extensions::ActionInfo::Type action_type) { 149 if (text.empty()) 150 return; 151 152 SkColor text_color = text_color_in; 153 if (SkColorGetA(text_color_in) == 0x00) 154 text_color = SK_ColorWHITE; 155 156 SkColor background_color = background_color_in; 157 if (SkColorGetA(background_color_in) == 0x00) 158 background_color = SkColorSetARGB(255, 218, 0, 24); 159 160 canvas->Save(); 161 162 SkPaint* text_paint = badge_util::GetBadgeTextPaintSingleton(); 163 text_paint->setTextSize(SkFloatToScalar(kTextSize)); 164 text_paint->setColor(text_color); 165 166 // Calculate text width. We clamp it to a max size. 167 SkScalar sk_text_width = text_paint->measureText(text.c_str(), text.size()); 168 int text_width = std::min(kMaxTextWidth, SkScalarFloor(sk_text_width)); 169 170 // Calculate badge size. It is clamped to a min width just because it looks 171 // silly if it is too skinny. 172 int badge_width = text_width + kPadding * 2; 173 // Force the pixel width of badge to be either odd (if the icon width is odd) 174 // or even otherwise. If there is a mismatch you get http://crbug.com/26400. 175 if (icon_width != 0 && (badge_width % 2 != icon_width % 2)) 176 badge_width += 1; 177 badge_width = std::max(kBadgeHeight, badge_width); 178 179 // Paint the badge background color in the right location. It is usually 180 // right-aligned, but it can also be center-aligned if it is large. 181 int rect_height = kBadgeHeight; 182 int bottom_margin = 183 action_type == extensions::ActionInfo::TYPE_BROWSER ? 184 kBottomMarginBrowserAction : kBottomMarginPageAction; 185 int rect_y = bounds.bottom() - bottom_margin - kBadgeHeight; 186 int rect_width = badge_width; 187 int rect_x = (badge_width >= kCenterAlignThreshold) ? 188 bounds.x() + (bounds.width() - badge_width) / 2 : 189 bounds.right() - badge_width; 190 gfx::Rect rect(rect_x, rect_y, rect_width, rect_height); 191 192 SkPaint rect_paint; 193 rect_paint.setStyle(SkPaint::kFill_Style); 194 rect_paint.setAntiAlias(true); 195 rect_paint.setColor(background_color); 196 canvas->DrawRoundRect(rect, 2, rect_paint); 197 198 // Overlay the gradient. It is stretchy, so we do this in three parts. 199 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 200 gfx::ImageSkia* gradient_left = rb.GetImageSkiaNamed( 201 IDR_BROWSER_ACTION_BADGE_LEFT); 202 gfx::ImageSkia* gradient_right = rb.GetImageSkiaNamed( 203 IDR_BROWSER_ACTION_BADGE_RIGHT); 204 gfx::ImageSkia* gradient_center = rb.GetImageSkiaNamed( 205 IDR_BROWSER_ACTION_BADGE_CENTER); 206 207 canvas->DrawImageInt(*gradient_left, rect.x(), rect.y()); 208 canvas->TileImageInt(*gradient_center, 209 rect.x() + gradient_left->width(), 210 rect.y(), 211 rect.width() - gradient_left->width() - gradient_right->width(), 212 rect.height()); 213 canvas->DrawImageInt(*gradient_right, 214 rect.right() - gradient_right->width(), rect.y()); 215 216 // Finally, draw the text centered within the badge. We set a clip in case the 217 // text was too large. 218 rect.Inset(kPadding, 0); 219 canvas->ClipRect(rect); 220 canvas->sk_canvas()->drawText( 221 text.c_str(), text.size(), 222 SkFloatToScalar(rect.x() + 223 static_cast<float>(rect.width() - text_width) / 2), 224 SkFloatToScalar(rect.y() + kTextSize + kTopTextPadding), 225 *text_paint); 226 canvas->Restore(); 227 } 228 229 } // namespace badge_util 230