• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
InitDefaultFontList()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.
FindClosestScaleFactorUnsafe(float scale)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:
ResourceBundleImageSource(ResourceBundle * rb,int resource_id)125   ResourceBundleImageSource(ResourceBundle* rb, int resource_id)
126       : rb_(rb), resource_id_(resource_id) {}
~ResourceBundleImageSource()127   virtual ~ResourceBundleImageSource() {}
128 
129   // gfx::ImageSkiaSource overrides:
GetImageForScale(float scale)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
InitSharedInstanceWithLocale(const std::string & pref_locale,Delegate * delegate)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
InitSharedInstanceLocaleOnly(const std::string & pref_locale,Delegate * delegate)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
InitSharedInstanceWithPakFileRegion(base::File pak_file,const base::MemoryMappedFile::Region & region,bool should_load_common_resources)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
InitSharedInstanceWithPakPath(const base::FilePath & path)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
CleanupSharedInstance()212 void ResourceBundle::CleanupSharedInstance() {
213   if (g_shared_instance_) {
214     delete g_shared_instance_;
215     g_shared_instance_ = NULL;
216   }
217 }
218 
219 // static
HasSharedInstance()220 bool ResourceBundle::HasSharedInstance() {
221   return g_shared_instance_ != NULL;
222 }
223 
224 // static
GetSharedInstance()225 ResourceBundle& ResourceBundle::GetSharedInstance() {
226   // Must call InitSharedInstance before this function.
227   CHECK(g_shared_instance_ != NULL);
228   return *g_shared_instance_;
229 }
230 
LocaleDataPakExists(const std::string & locale)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 
AddDataPackFromPath(const base::FilePath & path,ScaleFactor scale_factor)244 void ResourceBundle::AddDataPackFromPath(const base::FilePath& path,
245                                          ScaleFactor scale_factor) {
246   AddDataPackFromPathInternal(path, scale_factor, false);
247 }
248 
AddOptionalDataPackFromPath(const base::FilePath & path,ScaleFactor scale_factor)249 void ResourceBundle::AddOptionalDataPackFromPath(const base::FilePath& path,
250                                          ScaleFactor scale_factor) {
251   AddDataPackFromPathInternal(path, scale_factor, true);
252 }
253 
AddDataPackFromFile(base::File file,ScaleFactor scale_factor)254 void ResourceBundle::AddDataPackFromFile(base::File file,
255                                          ScaleFactor scale_factor) {
256   AddDataPackFromFileRegion(
257       file.Pass(), base::MemoryMappedFile::Region::kWholeFile, scale_factor);
258 }
259 
AddDataPackFromFileRegion(base::File file,const base::MemoryMappedFile::Region & region,ScaleFactor scale_factor)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)
GetLocaleFilePath(const std::string & app_locale,bool test_file_exists)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 
LoadLocaleResources(const std::string & pref_locale)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 
LoadTestResources(const base::FilePath & path,const base::FilePath & locale_path)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 
UnloadLocaleResources()348 void ResourceBundle::UnloadLocaleResources() {
349   locale_resources_data_.reset();
350 }
351 
OverrideLocalePakForTest(const base::FilePath & pak_path)352 void ResourceBundle::OverrideLocalePakForTest(const base::FilePath& pak_path) {
353   overridden_pak_path_ = pak_path;
354 }
355 
GetOverriddenPakPath()356 const base::FilePath& ResourceBundle::GetOverriddenPakPath() {
357   return overridden_pak_path_;
358 }
359 
ReloadLocaleResources(const std::string & pref_locale)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 
GetImageSkiaNamed(int resource_id)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 
GetImageNamed(int resource_id)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 
GetNativeImageNamed(int resource_id)422 gfx::Image& ResourceBundle::GetNativeImageNamed(int resource_id) {
423   return GetNativeImageNamed(resource_id, RTL_DISABLED);
424 }
425 
LoadDataResourceBytes(int resource_id) const426 base::RefCountedStaticMemory* ResourceBundle::LoadDataResourceBytes(
427     int resource_id) const {
428   return LoadDataResourceBytesForScale(resource_id, ui::SCALE_FACTOR_NONE);
429 }
430 
LoadDataResourceBytesForScale(int resource_id,ScaleFactor scale_factor) const431 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 
GetRawDataResource(int resource_id) const449 base::StringPiece ResourceBundle::GetRawDataResource(int resource_id) const {
450   return GetRawDataResourceForScale(resource_id, ui::SCALE_FACTOR_NONE);
451 }
452 
GetRawDataResourceForScale(int resource_id,ScaleFactor scale_factor) const453 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 
GetLocalizedString(int message_id)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 
GetFontList(FontStyle style)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 
GetFont(FontStyle style)548 const gfx::Font& ResourceBundle::GetFont(FontStyle style) {
549   return GetFontList(style).GetPrimaryFont();
550 }
551 
ReloadFonts()552 void ResourceBundle::ReloadFonts() {
553   base::AutoLock lock_scope(*images_and_fonts_lock_);
554   base_font_list_.reset();
555   LoadFontsIfNecessary();
556 }
557 
GetMaxScaleFactor() const558 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 
IsScaleFactorSupported(ScaleFactor scale_factor)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 
ResourceBundle(Delegate * delegate)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 
~ResourceBundle()581 ResourceBundle::~ResourceBundle() {
582   FreeImages();
583   UnloadLocaleResources();
584 }
585 
586 // static
InitSharedInstance(Delegate * delegate)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 
FreeImages()644 void ResourceBundle::FreeImages() {
645   images_.clear();
646 }
647 
AddDataPackFromPathInternal(const base::FilePath & path,ScaleFactor scale_factor,bool optional)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 
AddDataPack(DataPack * data_pack)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 
LoadFontsIfNecessary()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 
GetFontListFromDelegate(FontStyle style)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 
LoadBitmap(const ResourceHandle & data_handle,int resource_id,SkBitmap * bitmap,bool * fell_back_to_1x) const751 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 
LoadBitmap(int resource_id,ScaleFactor * scale_factor,SkBitmap * bitmap,bool * fell_back_to_1x) const780 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 
GetEmptyImage()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
PNGContainsFallbackMarker(const unsigned char * buf,size_t size)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
DecodePNG(const unsigned char * buf,size_t size,SkBitmap * bitmap,bool * fell_back_to_1x)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