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 "ui/base/resource/resource_bundle.h" 6 7 #include <limits> 8 #include <vector> 9 10 #include "base/big_endian.h" 11 #include "base/command_line.h" 12 #include "base/files/file.h" 13 #include "base/files/file_util.h" 14 #include "base/logging.h" 15 #include "base/memory/ref_counted_memory.h" 16 #include "base/metrics/histogram.h" 17 #include "base/path_service.h" 18 #include "base/stl_util.h" 19 #include "base/strings/string_piece.h" 20 #include "base/strings/utf_string_conversions.h" 21 #include "base/synchronization/lock.h" 22 #include "build/build_config.h" 23 #include "skia/ext/image_operations.h" 24 #include "third_party/skia/include/core/SkBitmap.h" 25 #include "ui/base/l10n/l10n_util.h" 26 #include "ui/base/layout.h" 27 #include "ui/base/resource/data_pack.h" 28 #include "ui/base/ui_base_paths.h" 29 #include "ui/base/ui_base_switches.h" 30 #include "ui/gfx/codec/jpeg_codec.h" 31 #include "ui/gfx/codec/png_codec.h" 32 #include "ui/gfx/image/image_skia.h" 33 #include "ui/gfx/image/image_skia_source.h" 34 #include "ui/gfx/safe_integer_conversions.h" 35 #include "ui/gfx/screen.h" 36 #include "ui/gfx/size_conversions.h" 37 #include "ui/strings/grit/app_locale_settings.h" 38 39 #if defined(OS_ANDROID) 40 #include "ui/base/resource/resource_bundle_android.h" 41 #endif 42 43 #if defined(OS_CHROMEOS) 44 #include "ui/base/l10n/l10n_util.h" 45 #include "ui/gfx/platform_font_pango.h" 46 #endif 47 48 #if defined(OS_WIN) 49 #include "ui/base/win/dpi_setup.h" 50 #include "ui/gfx/win/dpi.h" 51 #endif 52 53 #if defined(OS_MACOSX) && !defined(OS_IOS) 54 #include "base/mac/mac_util.h" 55 #endif 56 57 namespace ui { 58 59 namespace { 60 61 // Font sizes relative to base font. 62 const int kSmallFontSizeDelta = -1; 63 const int kMediumFontSizeDelta = 3; 64 const int kLargeFontSizeDelta = 8; 65 66 // PNG-related constants. 67 const unsigned char kPngMagic[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 }; 68 const size_t kPngChunkMetadataSize = 12; // length, type, crc32 69 const unsigned char kPngScaleChunkType[4] = { 'c', 's', 'C', 'l' }; 70 const unsigned char kPngDataChunkType[4] = { 'I', 'D', 'A', 'T' }; 71 72 #if !defined(OS_MACOSX) 73 const char kPakFileSuffix[] = ".pak"; 74 #endif 75 76 ResourceBundle* g_shared_instance_ = NULL; 77 78 void InitDefaultFontList() { 79 #if defined(OS_CHROMEOS) && defined(USE_PANGO) 80 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 81 std::string font_family = base::UTF16ToUTF8( 82 rb.GetLocalizedString(IDS_UI_FONT_FAMILY_CROS)); 83 gfx::FontList::SetDefaultFontDescription(font_family); 84 85 // TODO(yukishiino): Remove SetDefaultFontDescription() once the migration to 86 // the font list is done. We will no longer need SetDefaultFontDescription() 87 // after every client gets started using a FontList instead of a Font. 88 gfx::PlatformFontPango::SetDefaultFontDescription(font_family); 89 #else 90 // Use a single default font as the default font list. 91 gfx::FontList::SetDefaultFontDescription(std::string()); 92 #endif 93 } 94 95 #if defined(OS_ANDROID) 96 // Returns the scale factor closest to |scale| from the full list of factors. 97 // Note that it does NOT rely on the list of supported scale factors. 98 // Finding the closest match is inefficient and shouldn't be done frequently. 99 ScaleFactor FindClosestScaleFactorUnsafe(float scale) { 100 float smallest_diff = std::numeric_limits<float>::max(); 101 ScaleFactor closest_match = SCALE_FACTOR_100P; 102 for (int i = SCALE_FACTOR_100P; i < NUM_SCALE_FACTORS; ++i) { 103 const ScaleFactor scale_factor = static_cast<ScaleFactor>(i); 104 float diff = std::abs(GetScaleForScaleFactor(scale_factor) - scale); 105 if (diff < smallest_diff) { 106 closest_match = scale_factor; 107 smallest_diff = diff; 108 } 109 } 110 return closest_match; 111 } 112 #endif // OS_ANDROID 113 114 } // namespace 115 116 // An ImageSkiaSource that loads bitmaps for the requested scale factor from 117 // ResourceBundle on demand for a given |resource_id|. If the bitmap for the 118 // requested scale factor does not exist, it will return the 1x bitmap scaled 119 // by the scale factor. This may lead to broken UI if the correct size of the 120 // scaled image is not exactly |scale_factor| * the size of the 1x resource. 121 // When --highlight-missing-scaled-resources flag is specified, scaled 1x images 122 // are higlighted by blending them with red. 123 class ResourceBundle::ResourceBundleImageSource : public gfx::ImageSkiaSource { 124 public: 125 ResourceBundleImageSource(ResourceBundle* rb, int resource_id) 126 : rb_(rb), resource_id_(resource_id) {} 127 virtual ~ResourceBundleImageSource() {} 128 129 // gfx::ImageSkiaSource overrides: 130 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE { 131 SkBitmap image; 132 bool fell_back_to_1x = false; 133 ScaleFactor scale_factor = GetSupportedScaleFactor(scale); 134 bool found = rb_->LoadBitmap(resource_id_, &scale_factor, 135 &image, &fell_back_to_1x); 136 if (!found) 137 return gfx::ImageSkiaRep(); 138 139 // If the resource is in the package with SCALE_FACTOR_NONE, it 140 // can be used in any scale factor. The image is maked as "unscaled" 141 // so that the ImageSkia do not automatically scale. 142 if (scale_factor == ui::SCALE_FACTOR_NONE) 143 return gfx::ImageSkiaRep(image, 0.0f); 144 145 if (fell_back_to_1x) { 146 // GRIT fell back to the 100% image, so rescale it to the correct size. 147 image = skia::ImageOperations::Resize( 148 image, 149 skia::ImageOperations::RESIZE_LANCZOS3, 150 gfx::ToCeiledInt(image.width() * scale), 151 gfx::ToCeiledInt(image.height() * scale)); 152 } else { 153 scale = GetScaleForScaleFactor(scale_factor); 154 } 155 return gfx::ImageSkiaRep(image, scale); 156 } 157 158 private: 159 ResourceBundle* rb_; 160 const int resource_id_; 161 162 DISALLOW_COPY_AND_ASSIGN(ResourceBundleImageSource); 163 }; 164 165 // static 166 std::string ResourceBundle::InitSharedInstanceWithLocale( 167 const std::string& pref_locale, 168 Delegate* delegate, 169 LoadResources load_resources) { 170 InitSharedInstance(delegate); 171 if (load_resources == LOAD_COMMON_RESOURCES) 172 g_shared_instance_->LoadCommonResources(); 173 std::string result = g_shared_instance_->LoadLocaleResources(pref_locale); 174 InitDefaultFontList(); 175 return result; 176 } 177 178 // static 179 void ResourceBundle::InitSharedInstanceWithPakFileRegion( 180 base::File pak_file, 181 const base::MemoryMappedFile::Region& region) { 182 InitSharedInstance(NULL); 183 scoped_ptr<DataPack> data_pack(new DataPack(SCALE_FACTOR_100P)); 184 if (!data_pack->LoadFromFileRegion(pak_file.Pass(), region)) { 185 NOTREACHED() << "failed to load pak file"; 186 return; 187 } 188 g_shared_instance_->locale_resources_data_.reset(data_pack.release()); 189 InitDefaultFontList(); 190 } 191 192 // static 193 void ResourceBundle::InitSharedInstanceWithPakPath(const base::FilePath& path) { 194 InitSharedInstance(NULL); 195 g_shared_instance_->LoadTestResources(path, path); 196 197 InitDefaultFontList(); 198 } 199 200 // static 201 void ResourceBundle::CleanupSharedInstance() { 202 if (g_shared_instance_) { 203 delete g_shared_instance_; 204 g_shared_instance_ = NULL; 205 } 206 } 207 208 // static 209 bool ResourceBundle::HasSharedInstance() { 210 return g_shared_instance_ != NULL; 211 } 212 213 // static 214 ResourceBundle& ResourceBundle::GetSharedInstance() { 215 // Must call InitSharedInstance before this function. 216 CHECK(g_shared_instance_ != NULL); 217 return *g_shared_instance_; 218 } 219 220 bool ResourceBundle::LocaleDataPakExists(const std::string& locale) { 221 bool locale_file_path_exists = !GetLocaleFilePath(locale, true).empty(); 222 #if defined(OS_ANDROID) 223 // TODO(mkosiba,primiano): Chrome should mmap the .pak files too, in which 224 // case we'd not need to check if locale_file_path_exists here. 225 // http://crbug.com/394502. 226 return locale_file_path_exists || 227 AssetContainedInApk(locale + kPakFileSuffix); 228 #else 229 return locale_file_path_exists; 230 #endif 231 } 232 233 void ResourceBundle::AddDataPackFromPath(const base::FilePath& path, 234 ScaleFactor scale_factor) { 235 AddDataPackFromPathInternal(path, scale_factor, false); 236 } 237 238 void ResourceBundle::AddOptionalDataPackFromPath(const base::FilePath& path, 239 ScaleFactor scale_factor) { 240 AddDataPackFromPathInternal(path, scale_factor, true); 241 } 242 243 void ResourceBundle::AddDataPackFromFile(base::File file, 244 ScaleFactor scale_factor) { 245 AddDataPackFromFileRegion( 246 file.Pass(), base::MemoryMappedFile::Region::kWholeFile, scale_factor); 247 } 248 249 void ResourceBundle::AddDataPackFromFileRegion( 250 base::File file, 251 const base::MemoryMappedFile::Region& region, 252 ScaleFactor scale_factor) { 253 scoped_ptr<DataPack> data_pack( 254 new DataPack(scale_factor)); 255 if (data_pack->LoadFromFileRegion(file.Pass(), region)) { 256 AddDataPack(data_pack.release()); 257 } else { 258 LOG(ERROR) << "Failed to load data pack from file." 259 << "\nSome features may not be available."; 260 } 261 } 262 263 #if !defined(OS_MACOSX) 264 base::FilePath ResourceBundle::GetLocaleFilePath(const std::string& app_locale, 265 bool test_file_exists) { 266 if (app_locale.empty()) 267 return base::FilePath(); 268 269 base::FilePath locale_file_path; 270 271 PathService::Get(ui::DIR_LOCALES, &locale_file_path); 272 273 if (!locale_file_path.empty()) { 274 locale_file_path = 275 locale_file_path.AppendASCII(app_locale + kPakFileSuffix); 276 } 277 278 if (delegate_) { 279 locale_file_path = 280 delegate_->GetPathForLocalePack(locale_file_path, app_locale); 281 } 282 283 // Don't try to load empty values or values that are not absolute paths. 284 if (locale_file_path.empty() || !locale_file_path.IsAbsolute()) 285 return base::FilePath(); 286 287 if (test_file_exists && !base::PathExists(locale_file_path)) 288 return base::FilePath(); 289 290 return locale_file_path; 291 } 292 #endif 293 294 std::string ResourceBundle::LoadLocaleResources( 295 const std::string& pref_locale) { 296 DCHECK(!locale_resources_data_.get()) << "locale.pak already loaded"; 297 std::string app_locale = l10n_util::GetApplicationLocale(pref_locale); 298 base::FilePath locale_file_path = GetOverriddenPakPath(); 299 if (locale_file_path.empty()) 300 locale_file_path = GetLocaleFilePath(app_locale, true); 301 302 if (locale_file_path.empty()) { 303 // It's possible that there is no locale.pak. 304 LOG(WARNING) << "locale_file_path.empty()"; 305 return std::string(); 306 } 307 308 scoped_ptr<DataPack> data_pack( 309 new DataPack(SCALE_FACTOR_100P)); 310 if (!data_pack->LoadFromPath(locale_file_path)) { 311 UMA_HISTOGRAM_ENUMERATION("ResourceBundle.LoadLocaleResourcesError", 312 logging::GetLastSystemErrorCode(), 16000); 313 LOG(ERROR) << "failed to load locale.pak"; 314 NOTREACHED(); 315 return std::string(); 316 } 317 318 locale_resources_data_.reset(data_pack.release()); 319 return app_locale; 320 } 321 322 void ResourceBundle::LoadTestResources(const base::FilePath& path, 323 const base::FilePath& locale_path) { 324 DCHECK(!ui::GetSupportedScaleFactors().empty()); 325 const ScaleFactor scale_factor(ui::GetSupportedScaleFactors()[0]); 326 // Use the given resource pak for both common and localized resources. 327 scoped_ptr<DataPack> data_pack(new DataPack(scale_factor)); 328 if (!path.empty() && data_pack->LoadFromPath(path)) 329 AddDataPack(data_pack.release()); 330 331 data_pack.reset(new DataPack(ui::SCALE_FACTOR_NONE)); 332 if (!locale_path.empty() && data_pack->LoadFromPath(locale_path)) { 333 locale_resources_data_.reset(data_pack.release()); 334 } else { 335 locale_resources_data_.reset(new DataPack(ui::SCALE_FACTOR_NONE)); 336 } 337 } 338 339 void ResourceBundle::UnloadLocaleResources() { 340 locale_resources_data_.reset(); 341 } 342 343 void ResourceBundle::OverrideLocalePakForTest(const base::FilePath& pak_path) { 344 overridden_pak_path_ = pak_path; 345 } 346 347 void ResourceBundle::OverrideLocaleStringResource( 348 int message_id, 349 const base::string16& string) { 350 overridden_locale_strings_[message_id] = string; 351 } 352 353 const base::FilePath& ResourceBundle::GetOverriddenPakPath() { 354 return overridden_pak_path_; 355 } 356 357 std::string ResourceBundle::ReloadLocaleResources( 358 const std::string& pref_locale) { 359 base::AutoLock lock_scope(*locale_resources_data_lock_); 360 361 // Remove all overriden strings, as they will not be valid for the new locale. 362 overridden_locale_strings_.clear(); 363 364 UnloadLocaleResources(); 365 return LoadLocaleResources(pref_locale); 366 } 367 368 gfx::ImageSkia* ResourceBundle::GetImageSkiaNamed(int resource_id) { 369 const gfx::ImageSkia* image = GetImageNamed(resource_id).ToImageSkia(); 370 return const_cast<gfx::ImageSkia*>(image); 371 } 372 373 gfx::Image& ResourceBundle::GetImageNamed(int resource_id) { 374 // Check to see if the image is already in the cache. 375 { 376 base::AutoLock lock_scope(*images_and_fonts_lock_); 377 if (images_.count(resource_id)) 378 return images_[resource_id]; 379 } 380 381 gfx::Image image; 382 if (delegate_) 383 image = delegate_->GetImageNamed(resource_id); 384 385 if (image.IsEmpty()) { 386 DCHECK(!data_packs_.empty()) << 387 "Missing call to SetResourcesDataDLL?"; 388 389 #if defined(OS_CHROMEOS) || defined(OS_WIN) 390 ui::ScaleFactor scale_factor_to_load = GetMaxScaleFactor(); 391 #else 392 ui::ScaleFactor scale_factor_to_load = ui::SCALE_FACTOR_100P; 393 #endif 394 395 // TODO(oshima): Consider reading the image size from png IHDR chunk and 396 // skip decoding here and remove #ifdef below. 397 // ResourceBundle::GetSharedInstance() is destroyed after the 398 // BrowserMainLoop has finished running. |image_skia| is guaranteed to be 399 // destroyed before the resource bundle is destroyed. 400 gfx::ImageSkia image_skia(new ResourceBundleImageSource(this, resource_id), 401 GetScaleForScaleFactor(scale_factor_to_load)); 402 if (image_skia.isNull()) { 403 LOG(WARNING) << "Unable to load image with id " << resource_id; 404 NOTREACHED(); // Want to assert in debug mode. 405 // The load failed to retrieve the image; show a debugging red square. 406 return GetEmptyImage(); 407 } 408 image_skia.SetReadOnly(); 409 image = gfx::Image(image_skia); 410 } 411 412 // The load was successful, so cache the image. 413 base::AutoLock lock_scope(*images_and_fonts_lock_); 414 415 // Another thread raced the load and has already cached the image. 416 if (images_.count(resource_id)) 417 return images_[resource_id]; 418 419 images_[resource_id] = image; 420 return images_[resource_id]; 421 } 422 423 gfx::Image& ResourceBundle::GetNativeImageNamed(int resource_id) { 424 return GetNativeImageNamed(resource_id, RTL_DISABLED); 425 } 426 427 base::RefCountedStaticMemory* ResourceBundle::LoadDataResourceBytes( 428 int resource_id) const { 429 return LoadDataResourceBytesForScale(resource_id, ui::SCALE_FACTOR_NONE); 430 } 431 432 base::RefCountedStaticMemory* ResourceBundle::LoadDataResourceBytesForScale( 433 int resource_id, 434 ScaleFactor scale_factor) const { 435 base::RefCountedStaticMemory* bytes = NULL; 436 if (delegate_) 437 bytes = delegate_->LoadDataResourceBytes(resource_id, scale_factor); 438 439 if (!bytes) { 440 base::StringPiece data = 441 GetRawDataResourceForScale(resource_id, scale_factor); 442 if (!data.empty()) { 443 bytes = new base::RefCountedStaticMemory(data.data(), data.length()); 444 } 445 } 446 447 return bytes; 448 } 449 450 base::StringPiece ResourceBundle::GetRawDataResource(int resource_id) const { 451 return GetRawDataResourceForScale(resource_id, ui::SCALE_FACTOR_NONE); 452 } 453 454 base::StringPiece ResourceBundle::GetRawDataResourceForScale( 455 int resource_id, 456 ScaleFactor scale_factor) const { 457 base::StringPiece data; 458 if (delegate_ && 459 delegate_->GetRawDataResource(resource_id, scale_factor, &data)) 460 return data; 461 462 if (scale_factor != ui::SCALE_FACTOR_100P) { 463 for (size_t i = 0; i < data_packs_.size(); i++) { 464 if (data_packs_[i]->GetScaleFactor() == scale_factor && 465 data_packs_[i]->GetStringPiece(resource_id, &data)) 466 return data; 467 } 468 } 469 for (size_t i = 0; i < data_packs_.size(); i++) { 470 if ((data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_100P || 471 data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_200P || 472 data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_NONE) && 473 data_packs_[i]->GetStringPiece(resource_id, &data)) 474 return data; 475 } 476 477 return base::StringPiece(); 478 } 479 480 base::string16 ResourceBundle::GetLocalizedString(int message_id) { 481 base::string16 string; 482 if (delegate_ && delegate_->GetLocalizedString(message_id, &string)) 483 return string; 484 485 // Ensure that ReloadLocaleResources() doesn't drop the resources while 486 // we're using them. 487 base::AutoLock lock_scope(*locale_resources_data_lock_); 488 489 IdToStringMap::const_iterator it = 490 overridden_locale_strings_.find(message_id); 491 if (it != overridden_locale_strings_.end()) 492 return it->second; 493 494 // If for some reason we were unable to load the resources , return an empty 495 // string (better than crashing). 496 if (!locale_resources_data_.get()) { 497 LOG(WARNING) << "locale resources are not loaded"; 498 return base::string16(); 499 } 500 501 base::StringPiece data; 502 if (!locale_resources_data_->GetStringPiece(message_id, &data)) { 503 // Fall back on the main data pack (shouldn't be any strings here except in 504 // unittests). 505 data = GetRawDataResource(message_id); 506 if (data.empty()) { 507 NOTREACHED() << "unable to find resource: " << message_id; 508 return base::string16(); 509 } 510 } 511 512 // Strings should not be loaded from a data pack that contains binary data. 513 ResourceHandle::TextEncodingType encoding = 514 locale_resources_data_->GetTextEncodingType(); 515 DCHECK(encoding == ResourceHandle::UTF16 || encoding == ResourceHandle::UTF8) 516 << "requested localized string from binary pack file"; 517 518 // Data pack encodes strings as either UTF8 or UTF16. 519 base::string16 msg; 520 if (encoding == ResourceHandle::UTF16) { 521 msg = base::string16(reinterpret_cast<const base::char16*>(data.data()), 522 data.length() / 2); 523 } else if (encoding == ResourceHandle::UTF8) { 524 msg = base::UTF8ToUTF16(data); 525 } 526 return msg; 527 } 528 529 const gfx::FontList& ResourceBundle::GetFontList(FontStyle style) { 530 { 531 base::AutoLock lock_scope(*images_and_fonts_lock_); 532 LoadFontsIfNecessary(); 533 } 534 switch (style) { 535 case BoldFont: 536 return *bold_font_list_; 537 case SmallFont: 538 return *small_font_list_; 539 case MediumFont: 540 return *medium_font_list_; 541 case SmallBoldFont: 542 return *small_bold_font_list_; 543 case MediumBoldFont: 544 return *medium_bold_font_list_; 545 case LargeFont: 546 return *large_font_list_; 547 case LargeBoldFont: 548 return *large_bold_font_list_; 549 default: 550 return *base_font_list_; 551 } 552 } 553 554 const gfx::Font& ResourceBundle::GetFont(FontStyle style) { 555 return GetFontList(style).GetPrimaryFont(); 556 } 557 558 void ResourceBundle::ReloadFonts() { 559 base::AutoLock lock_scope(*images_and_fonts_lock_); 560 base_font_list_.reset(); 561 LoadFontsIfNecessary(); 562 } 563 564 ScaleFactor ResourceBundle::GetMaxScaleFactor() const { 565 #if defined(OS_CHROMEOS) || defined(OS_WIN) 566 return max_scale_factor_; 567 #else 568 return GetSupportedScaleFactors().back(); 569 #endif 570 } 571 572 bool ResourceBundle::IsScaleFactorSupported(ScaleFactor scale_factor) { 573 const std::vector<ScaleFactor>& supported_scale_factors = 574 ui::GetSupportedScaleFactors(); 575 return std::find(supported_scale_factors.begin(), 576 supported_scale_factors.end(), 577 scale_factor) != supported_scale_factors.end(); 578 } 579 580 ResourceBundle::ResourceBundle(Delegate* delegate) 581 : delegate_(delegate), 582 images_and_fonts_lock_(new base::Lock), 583 locale_resources_data_lock_(new base::Lock), 584 max_scale_factor_(SCALE_FACTOR_100P) { 585 } 586 587 ResourceBundle::~ResourceBundle() { 588 FreeImages(); 589 UnloadLocaleResources(); 590 } 591 592 // static 593 void ResourceBundle::InitSharedInstance(Delegate* delegate) { 594 DCHECK(g_shared_instance_ == NULL) << "ResourceBundle initialized twice"; 595 g_shared_instance_ = new ResourceBundle(delegate); 596 static std::vector<ScaleFactor> supported_scale_factors; 597 #if !defined(OS_IOS) && !defined(OS_WIN) 598 // On platforms other than iOS, 100P is always a supported scale factor. 599 // For Windows we have a separate case in this function. 600 supported_scale_factors.push_back(SCALE_FACTOR_100P); 601 #endif 602 #if defined(OS_ANDROID) 603 const gfx::Display display = 604 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); 605 const float display_density = display.device_scale_factor(); 606 const ScaleFactor closest = FindClosestScaleFactorUnsafe(display_density); 607 if (closest != SCALE_FACTOR_100P) 608 supported_scale_factors.push_back(closest); 609 #elif defined(OS_IOS) 610 gfx::Display display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); 611 if (display.device_scale_factor() > 2.0) { 612 DCHECK_EQ(3.0, display.device_scale_factor()); 613 supported_scale_factors.push_back(SCALE_FACTOR_300P); 614 } else if (display.device_scale_factor() > 1.0) { 615 DCHECK_EQ(2.0, display.device_scale_factor()); 616 supported_scale_factors.push_back(SCALE_FACTOR_200P); 617 } else { 618 supported_scale_factors.push_back(SCALE_FACTOR_100P); 619 } 620 #elif defined(OS_MACOSX) 621 if (base::mac::IsOSLionOrLater()) 622 supported_scale_factors.push_back(SCALE_FACTOR_200P); 623 #elif defined(OS_CHROMEOS) 624 // TODO(oshima): Include 200P only if the device support 200P 625 supported_scale_factors.push_back(SCALE_FACTOR_200P); 626 #elif defined(OS_LINUX) && defined(ENABLE_HIDPI) 627 supported_scale_factors.push_back(SCALE_FACTOR_200P); 628 #elif defined(OS_WIN) 629 bool default_to_100P = true; 630 if (gfx::IsHighDPIEnabled()) { 631 // On Windows if the dpi scale is greater than 1.25 on high dpi machines 632 // downscaling from 200 percent looks better than scaling up from 100 633 // percent. 634 if (gfx::GetDPIScale() > 1.25) { 635 supported_scale_factors.push_back(SCALE_FACTOR_200P); 636 default_to_100P = false; 637 } 638 } 639 if (default_to_100P) 640 supported_scale_factors.push_back(SCALE_FACTOR_100P); 641 #endif 642 ui::SetSupportedScaleFactors(supported_scale_factors); 643 #if defined(OS_WIN) 644 // Must be called _after_ supported scale factors are set since it 645 // uses them. 646 // Don't initialize the device scale factor if it has already been 647 // initialized. 648 if (!gfx::win::IsDeviceScaleFactorSet()) 649 ui::win::InitDeviceScaleFactor(); 650 #endif 651 } 652 653 void ResourceBundle::FreeImages() { 654 images_.clear(); 655 } 656 657 void ResourceBundle::AddDataPackFromPathInternal(const base::FilePath& path, 658 ScaleFactor scale_factor, 659 bool optional) { 660 // Do not pass an empty |path| value to this method. If the absolute path is 661 // unknown pass just the pack file name. 662 DCHECK(!path.empty()); 663 664 base::FilePath pack_path = path; 665 if (delegate_) 666 pack_path = delegate_->GetPathForResourcePack(pack_path, scale_factor); 667 668 // Don't try to load empty values or values that are not absolute paths. 669 if (pack_path.empty() || !pack_path.IsAbsolute()) 670 return; 671 672 scoped_ptr<DataPack> data_pack( 673 new DataPack(scale_factor)); 674 if (data_pack->LoadFromPath(pack_path)) { 675 AddDataPack(data_pack.release()); 676 } else if (!optional) { 677 LOG(ERROR) << "Failed to load " << pack_path.value() 678 << "\nSome features may not be available."; 679 } 680 } 681 682 void ResourceBundle::AddDataPack(DataPack* data_pack) { 683 data_packs_.push_back(data_pack); 684 685 if (GetScaleForScaleFactor(data_pack->GetScaleFactor()) > 686 GetScaleForScaleFactor(max_scale_factor_)) 687 max_scale_factor_ = data_pack->GetScaleFactor(); 688 } 689 690 void ResourceBundle::LoadFontsIfNecessary() { 691 images_and_fonts_lock_->AssertAcquired(); 692 if (!base_font_list_.get()) { 693 if (delegate_) { 694 base_font_list_ = GetFontListFromDelegate(BaseFont); 695 bold_font_list_ = GetFontListFromDelegate(BoldFont); 696 small_font_list_ = GetFontListFromDelegate(SmallFont); 697 small_bold_font_list_ = GetFontListFromDelegate(SmallBoldFont); 698 medium_font_list_ = GetFontListFromDelegate(MediumFont); 699 medium_bold_font_list_ = GetFontListFromDelegate(MediumBoldFont); 700 large_font_list_ = GetFontListFromDelegate(LargeFont); 701 large_bold_font_list_ = GetFontListFromDelegate(LargeBoldFont); 702 } 703 704 if (!base_font_list_.get()) 705 base_font_list_.reset(new gfx::FontList()); 706 707 if (!bold_font_list_.get()) { 708 bold_font_list_.reset(new gfx::FontList()); 709 *bold_font_list_ = base_font_list_->DeriveWithStyle( 710 base_font_list_->GetFontStyle() | gfx::Font::BOLD); 711 } 712 713 if (!small_font_list_.get()) { 714 small_font_list_.reset(new gfx::FontList()); 715 *small_font_list_ = 716 base_font_list_->DeriveWithSizeDelta(kSmallFontSizeDelta); 717 } 718 719 if (!small_bold_font_list_.get()) { 720 small_bold_font_list_.reset(new gfx::FontList()); 721 *small_bold_font_list_ = small_font_list_->DeriveWithStyle( 722 small_font_list_->GetFontStyle() | gfx::Font::BOLD); 723 } 724 725 if (!medium_font_list_.get()) { 726 medium_font_list_.reset(new gfx::FontList()); 727 *medium_font_list_ = 728 base_font_list_->DeriveWithSizeDelta(kMediumFontSizeDelta); 729 } 730 731 if (!medium_bold_font_list_.get()) { 732 medium_bold_font_list_.reset(new gfx::FontList()); 733 *medium_bold_font_list_ = medium_font_list_->DeriveWithStyle( 734 medium_font_list_->GetFontStyle() | gfx::Font::BOLD); 735 } 736 737 if (!large_font_list_.get()) { 738 large_font_list_.reset(new gfx::FontList()); 739 *large_font_list_ = 740 base_font_list_->DeriveWithSizeDelta(kLargeFontSizeDelta); 741 } 742 743 if (!large_bold_font_list_.get()) { 744 large_bold_font_list_.reset(new gfx::FontList()); 745 *large_bold_font_list_ = large_font_list_->DeriveWithStyle( 746 large_font_list_->GetFontStyle() | gfx::Font::BOLD); 747 } 748 } 749 } 750 751 scoped_ptr<gfx::FontList> ResourceBundle::GetFontListFromDelegate( 752 FontStyle style) { 753 DCHECK(delegate_); 754 scoped_ptr<gfx::Font> font = delegate_->GetFont(style); 755 if (font.get()) 756 return scoped_ptr<gfx::FontList>(new gfx::FontList(*font)); 757 return scoped_ptr<gfx::FontList>(); 758 } 759 760 bool ResourceBundle::LoadBitmap(const ResourceHandle& data_handle, 761 int resource_id, 762 SkBitmap* bitmap, 763 bool* fell_back_to_1x) const { 764 DCHECK(fell_back_to_1x); 765 scoped_refptr<base::RefCountedMemory> memory( 766 data_handle.GetStaticMemory(resource_id)); 767 if (!memory.get()) 768 return false; 769 770 if (DecodePNG(memory->front(), memory->size(), bitmap, fell_back_to_1x)) 771 return true; 772 773 #if !defined(OS_IOS) 774 // iOS does not compile or use the JPEG codec. On other platforms, 775 // 99% of our assets are PNGs, however fallback to JPEG. 776 scoped_ptr<SkBitmap> jpeg_bitmap( 777 gfx::JPEGCodec::Decode(memory->front(), memory->size())); 778 if (jpeg_bitmap.get()) { 779 bitmap->swap(*jpeg_bitmap.get()); 780 *fell_back_to_1x = false; 781 return true; 782 } 783 #endif 784 785 NOTREACHED() << "Unable to decode theme image resource " << resource_id; 786 return false; 787 } 788 789 bool ResourceBundle::LoadBitmap(int resource_id, 790 ScaleFactor* scale_factor, 791 SkBitmap* bitmap, 792 bool* fell_back_to_1x) const { 793 DCHECK(fell_back_to_1x); 794 for (size_t i = 0; i < data_packs_.size(); ++i) { 795 if (data_packs_[i]->GetScaleFactor() == ui::SCALE_FACTOR_NONE && 796 LoadBitmap(*data_packs_[i], resource_id, bitmap, fell_back_to_1x)) { 797 DCHECK(!*fell_back_to_1x); 798 *scale_factor = ui::SCALE_FACTOR_NONE; 799 return true; 800 } 801 if (data_packs_[i]->GetScaleFactor() == *scale_factor && 802 LoadBitmap(*data_packs_[i], resource_id, bitmap, fell_back_to_1x)) { 803 return true; 804 } 805 } 806 return false; 807 } 808 809 gfx::Image& ResourceBundle::GetEmptyImage() { 810 base::AutoLock lock(*images_and_fonts_lock_); 811 812 if (empty_image_.IsEmpty()) { 813 // The placeholder bitmap is bright red so people notice the problem. 814 SkBitmap bitmap; 815 bitmap.allocN32Pixels(32, 32); 816 bitmap.eraseARGB(255, 255, 0, 0); 817 empty_image_ = gfx::Image::CreateFrom1xBitmap(bitmap); 818 } 819 return empty_image_; 820 } 821 822 // static 823 bool ResourceBundle::PNGContainsFallbackMarker(const unsigned char* buf, 824 size_t size) { 825 if (size < arraysize(kPngMagic) || 826 memcmp(buf, kPngMagic, arraysize(kPngMagic)) != 0) { 827 // Data invalid or a JPEG. 828 return false; 829 } 830 size_t pos = arraysize(kPngMagic); 831 832 // Scan for custom chunks until we find one, find the IDAT chunk, or run out 833 // of chunks. 834 for (;;) { 835 if (size - pos < kPngChunkMetadataSize) 836 break; 837 uint32 length = 0; 838 base::ReadBigEndian(reinterpret_cast<const char*>(buf + pos), &length); 839 if (size - pos - kPngChunkMetadataSize < length) 840 break; 841 if (length == 0 && memcmp(buf + pos + sizeof(uint32), kPngScaleChunkType, 842 arraysize(kPngScaleChunkType)) == 0) { 843 return true; 844 } 845 if (memcmp(buf + pos + sizeof(uint32), kPngDataChunkType, 846 arraysize(kPngDataChunkType)) == 0) { 847 // Stop looking for custom chunks, any custom chunks should be before an 848 // IDAT chunk. 849 break; 850 } 851 pos += length + kPngChunkMetadataSize; 852 } 853 return false; 854 } 855 856 // static 857 bool ResourceBundle::DecodePNG(const unsigned char* buf, 858 size_t size, 859 SkBitmap* bitmap, 860 bool* fell_back_to_1x) { 861 *fell_back_to_1x = PNGContainsFallbackMarker(buf, size); 862 return gfx::PNGCodec::Decode(buf, size, bitmap); 863 } 864 865 } // namespace ui 866