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
57namespace ui {
58
59namespace {
60
61// Font sizes relative to base font.
62const int kSmallFontSizeDelta = -1;
63const int kMediumFontSizeDelta = 3;
64const int kLargeFontSizeDelta = 8;
65
66// PNG-related constants.
67const unsigned char kPngMagic[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
68const size_t kPngChunkMetadataSize = 12;  // length, type, crc32
69const unsigned char kPngScaleChunkType[4] = { 'c', 's', 'C', 'l' };
70const unsigned char kPngDataChunkType[4] = { 'I', 'D', 'A', 'T' };
71
72#if !defined(OS_MACOSX)
73const char kPakFileSuffix[] = ".pak";
74#endif
75
76ResourceBundle* g_shared_instance_ = NULL;
77
78void 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.
99ScaleFactor 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.
123class 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
166std::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
176std::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
185void 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
204void ResourceBundle::InitSharedInstanceWithPakPath(const base::FilePath& path) {
205  InitSharedInstance(NULL);
206  g_shared_instance_->LoadTestResources(path, path);
207
208  InitDefaultFontList();
209}
210
211// static
212void ResourceBundle::CleanupSharedInstance() {
213  if (g_shared_instance_) {
214    delete g_shared_instance_;
215    g_shared_instance_ = NULL;
216  }
217}
218
219// static
220bool ResourceBundle::HasSharedInstance() {
221  return g_shared_instance_ != NULL;
222}
223
224// static
225ResourceBundle& ResourceBundle::GetSharedInstance() {
226  // Must call InitSharedInstance before this function.
227  CHECK(g_shared_instance_ != NULL);
228  return *g_shared_instance_;
229}
230
231bool 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
244void ResourceBundle::AddDataPackFromPath(const base::FilePath& path,
245                                         ScaleFactor scale_factor) {
246  AddDataPackFromPathInternal(path, scale_factor, false);
247}
248
249void ResourceBundle::AddOptionalDataPackFromPath(const base::FilePath& path,
250                                         ScaleFactor scale_factor) {
251  AddDataPackFromPathInternal(path, scale_factor, true);
252}
253
254void ResourceBundle::AddDataPackFromFile(base::File file,
255                                         ScaleFactor scale_factor) {
256  AddDataPackFromFileRegion(
257      file.Pass(), base::MemoryMappedFile::Region::kWholeFile, scale_factor);
258}
259
260void 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)
275base::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
305std::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
333void 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
348void ResourceBundle::UnloadLocaleResources() {
349  locale_resources_data_.reset();
350}
351
352void ResourceBundle::OverrideLocalePakForTest(const base::FilePath& pak_path) {
353  overridden_pak_path_ = pak_path;
354}
355
356const base::FilePath& ResourceBundle::GetOverriddenPakPath() {
357  return overridden_pak_path_;
358}
359
360std::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
367gfx::ImageSkia* ResourceBundle::GetImageSkiaNamed(int resource_id) {
368  const gfx::ImageSkia* image = GetImageNamed(resource_id).ToImageSkia();
369  return const_cast<gfx::ImageSkia*>(image);
370}
371
372gfx::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
422gfx::Image& ResourceBundle::GetNativeImageNamed(int resource_id) {
423  return GetNativeImageNamed(resource_id, RTL_DISABLED);
424}
425
426base::RefCountedStaticMemory* ResourceBundle::LoadDataResourceBytes(
427    int resource_id) const {
428  return LoadDataResourceBytesForScale(resource_id, ui::SCALE_FACTOR_NONE);
429}
430
431base::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
449base::StringPiece ResourceBundle::GetRawDataResource(int resource_id) const {
450  return GetRawDataResourceForScale(resource_id, ui::SCALE_FACTOR_NONE);
451}
452
453base::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
479base::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
523const 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
548const gfx::Font& ResourceBundle::GetFont(FontStyle style) {
549  return GetFontList(style).GetPrimaryFont();
550}
551
552void ResourceBundle::ReloadFonts() {
553  base::AutoLock lock_scope(*images_and_fonts_lock_);
554  base_font_list_.reset();
555  LoadFontsIfNecessary();
556}
557
558ScaleFactor 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
566bool 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
574ResourceBundle::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
581ResourceBundle::~ResourceBundle() {
582  FreeImages();
583  UnloadLocaleResources();
584}
585
586// static
587void 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
644void ResourceBundle::FreeImages() {
645  images_.clear();
646}
647
648void 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
673void 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
681void 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
742scoped_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
751bool 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
780bool 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
800gfx::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
815bool 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
849bool 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