Home | History | Annotate | Download | only in resource
      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