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