blob: 2d9790e5c3229290987fa87154dcc8fc1358e811 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/placeholder_image.h"
#include <utility>
#include "base/stl_util.h"
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "third_party/blink/renderer/platform/fonts/font_description.h"
#include "third_party/blink/renderer/platform/fonts/font_family.h"
#include "third_party/blink/renderer/platform/fonts/font_selection_types.h"
#include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
#include "third_party/blink/renderer/platform/geometry/float_point.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/geometry/int_point.h"
#include "third_party/blink/renderer/platform/geometry/int_rect.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/image_observer.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/text/text_run.h"
#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSize.h"
namespace blink {
namespace {
// Placeholder image visual specifications:
// https://docs.google.com/document/d/1BHeA1azbgCdZgCnr16VN2g7A9MHPQ_dwKn5szh8evMQ/edit
constexpr int kIconWidth = 24;
constexpr int kIconHeight = 24;
constexpr int kFeaturePaddingX = 8;
constexpr int kIconPaddingY = 5;
constexpr int kPaddingBetweenIconAndText = 2;
constexpr int kTextPaddingY = 9;
constexpr int kFontSize = 14;
void DrawIcon(cc::PaintCanvas* canvas,
const PaintFlags& flags,
float x,
float y) {
DEFINE_STATIC_REF(Image, icon_image,
(Image::LoadPlatformResource("placeholderIcon")));
DCHECK(!icon_image->IsNull());
// Note that the |icon_image| is not scaled according to dest_rect / src_rect,
// and is always drawn at the same size. This is so that placeholder icons are
// visible (e.g. when replacing a large image that's scaled down to a small
// area) and so that all placeholder images on the same page look consistent.
canvas->drawImageRect(icon_image->PaintImageForCurrentFrame(),
IntRect(IntPoint::Zero(), icon_image->Size()),
FloatRect(x, y, kIconWidth, kIconHeight), &flags,
cc::PaintCanvas::kFast_SrcRectConstraint);
}
void DrawCenteredIcon(cc::PaintCanvas* canvas,
const PaintFlags& flags,
const FloatRect& dest_rect) {
DrawIcon(canvas, flags,
dest_rect.X() + (dest_rect.Width() - kIconWidth) / 2.0f,
dest_rect.Y() + (dest_rect.Height() - kIconHeight) / 2.0f);
}
FontDescription CreatePlaceholderFontDescription() {
FontDescription description;
description.FirstFamily().SetFamily("Roboto");
scoped_refptr<SharedFontFamily> helvetica_neue = SharedFontFamily::Create();
helvetica_neue->SetFamily("Helvetica Neue");
scoped_refptr<SharedFontFamily> helvetica = SharedFontFamily::Create();
helvetica->SetFamily("Helvetica");
scoped_refptr<SharedFontFamily> arial = SharedFontFamily::Create();
arial->SetFamily("Arial");
helvetica->AppendFamily(std::move(arial));
helvetica_neue->AppendFamily(std::move(helvetica));
description.FirstFamily().AppendFamily(std::move(helvetica_neue));
description.SetGenericFamily(FontDescription::kSansSerifFamily);
description.SetComputedSize(kFontSize);
description.SetWeight(FontSelectionValue(500));
return description;
}
// Return a byte quantity as a string in a localized human-readable format,
// suitable for being shown on a placeholder image to indicate the full original
// size of the resource.
//
// Ex: FormatOriginalResourceSizeBytes(100) => "1 KB"
// Ex: FormatOriginalResourceSizeBytes(102401) => "100 KB"
// Ex: FormatOriginalResourceSizeBytes(1740800) => "1.7 MB"
//
// See the placeholder image number format specifications for more info:
// https://docs.google.com/document/d/1BHeA1azbgCdZgCnr16VN2g7A9MHPQ_dwKn5szh8evMQ/edit#heading=h.d135l9z7tn0a
String FormatOriginalResourceSizeBytes(int64_t bytes) {
DCHECK_LT(0, bytes);
static constexpr WebLocalizedString::Name kUnitsNames[] = {
WebLocalizedString::kUnitsKibibytes, WebLocalizedString::kUnitsMebibytes,
WebLocalizedString::kUnitsGibibytes, WebLocalizedString::kUnitsTebibytes,
WebLocalizedString::kUnitsPebibytes};
// Start with KB. The formatted text will be at least "1 KB", with any smaller
// amounts being rounded up to "1 KB".
const WebLocalizedString::Name* units = kUnitsNames;
int64_t denomenator = 1024;
// Find the smallest unit that can represent |bytes| in 3 digits or less.
// Round up to the next higher unit if possible when it would take 4 digits to
// display the amount, e.g. 1000 KB will be rounded up to 1 MB.
for (; units < kUnitsNames + (base::size(kUnitsNames) - 1) &&
bytes >= denomenator * 1000;
++units, denomenator *= 1024) {
}
String numeric_string;
if (bytes < denomenator) {
// Round up to 1.
numeric_string = String::Number(1);
} else if (units != kUnitsNames && bytes < denomenator * 10) {
// For amounts between 1 and 10 units and larger than 1 MB, allow up to one
// fractional digit.
numeric_string = String::Number(
static_cast<double>(bytes) / static_cast<double>(denomenator), 2);
} else {
numeric_string = String::Number(bytes / denomenator);
}
Locale& locale = Locale::DefaultLocale();
// Locale::QueryString() will return an empty string if the embedder hasn't
// defined the string resources for the units, which will cause the
// PlaceholderImage to not show any text.
return locale.QueryString(*units,
locale.ConvertToLocalizedNumber(numeric_string));
}
} // namespace
// A simple RefCounted wrapper around a Font, so that multiple PlaceholderImages
// can share the same Font.
class PlaceholderImage::SharedFont : public RefCounted<SharedFont> {
public:
static scoped_refptr<SharedFont> GetOrCreateInstance() {
if (g_instance_)
return scoped_refptr<SharedFont>(g_instance_);
scoped_refptr<SharedFont> shared_font(base::AdoptRef(new SharedFont()));
g_instance_ = shared_font.get();
return shared_font;
}
~SharedFont() {
DCHECK_EQ(this, g_instance_);
g_instance_ = nullptr;
}
const Font& font() const { return font_; }
private:
SharedFont() : font_(CreatePlaceholderFontDescription()) {
font_.Update(nullptr);
}
static SharedFont* g_instance_;
Font font_;
};
// static
PlaceholderImage::SharedFont* PlaceholderImage::SharedFont::g_instance_ =
nullptr;
PlaceholderImage::PlaceholderImage(ImageObserver* observer,
const IntSize& size,
int64_t original_resource_size,
bool is_lazy_image)
: Image(observer),
size_(size),
text_(original_resource_size <= 0
? String()
: FormatOriginalResourceSizeBytes(original_resource_size)),
is_lazy_image_(is_lazy_image),
paint_record_content_id_(-1) {}
PlaceholderImage::~PlaceholderImage() = default;
IntSize PlaceholderImage::Size() const {
return size_;
}
bool PlaceholderImage::IsPlaceholderImage() const {
return true;
}
bool PlaceholderImage::CurrentFrameHasSingleSecurityOrigin() const {
return true;
}
bool PlaceholderImage::CurrentFrameKnownToBeOpaque() {
// Placeholder images are translucent.
return false;
}
PaintImage PlaceholderImage::PaintImageForCurrentFrame() {
auto builder = CreatePaintImageBuilder().set_completion_state(
PaintImage::CompletionState::DONE);
const IntRect dest_rect(0, 0, size_.Width(), size_.Height());
if (paint_record_for_current_frame_) {
return builder
.set_paint_record(paint_record_for_current_frame_, dest_rect,
paint_record_content_id_)
.TakePaintImage();
}
PaintRecorder paint_recorder;
Draw(paint_recorder.beginRecording(FloatRect(dest_rect)), PaintFlags(),
FloatRect(dest_rect), FloatRect(dest_rect),
kDoNotRespectImageOrientation, kClampImageToSourceRect, kSyncDecode);
paint_record_for_current_frame_ = paint_recorder.finishRecordingAsPicture();
paint_record_content_id_ = PaintImage::GetNextContentId();
return builder
.set_paint_record(paint_record_for_current_frame_, dest_rect,
paint_record_content_id_)
.TakePaintImage();
}
void PlaceholderImage::Draw(cc::PaintCanvas* canvas,
const PaintFlags& base_flags,
const FloatRect& dest_rect,
const FloatRect& src_rect,
RespectImageOrientationEnum respect_orientation,
ImageClampingMode image_clamping_mode,
ImageDecodingMode decode_mode) {
if (!src_rect.Intersects(FloatRect(0.0f, 0.0f,
static_cast<float>(size_.Width()),
static_cast<float>(size_.Height())))) {
return;
}
if (is_lazy_image_) {
// Keep the image without any color and text decorations.
return;
}
PaintFlags flags(base_flags);
flags.setStyle(PaintFlags::kFill_Style);
flags.setColor(SkColorSetARGB(0x80, 0xD9, 0xD9, 0xD9));
canvas->drawRect(dest_rect, flags);
if (dest_rect.Width() < kIconWidth + 2 * kFeaturePaddingX ||
dest_rect.Height() < kIconHeight + 2 * kIconPaddingY) {
return;
}
if (text_.IsEmpty()) {
DrawCenteredIcon(canvas, base_flags, dest_rect);
return;
}
if (!shared_font_)
shared_font_ = SharedFont::GetOrCreateInstance();
if (!cached_text_width_.has_value())
cached_text_width_ = shared_font_->font().Width(TextRun(text_));
const float icon_and_text_width =
cached_text_width_.value() +
(kIconWidth + 2 * kFeaturePaddingX + kPaddingBetweenIconAndText);
if (dest_rect.Width() < icon_and_text_width) {
DrawCenteredIcon(canvas, base_flags, dest_rect);
return;
}
const float feature_x =
dest_rect.X() + (dest_rect.Width() - icon_and_text_width) / 2.0f;
const float feature_y =
dest_rect.Y() +
(dest_rect.Height() - (kIconHeight + 2 * kIconPaddingY)) / 2.0f;
float icon_x, text_x;
if (Locale::DefaultLocale().IsRTL()) {
icon_x = feature_x + cached_text_width_.value() +
(kFeaturePaddingX + kPaddingBetweenIconAndText);
text_x = feature_x + kFeaturePaddingX;
} else {
icon_x = feature_x + kFeaturePaddingX;
text_x = feature_x +
(kFeaturePaddingX + kIconWidth + kPaddingBetweenIconAndText);
}
DrawIcon(canvas, base_flags, icon_x, feature_y + kIconPaddingY);
flags.setColor(SkColorSetARGB(0xAB, 0, 0, 0));
shared_font_->font().DrawBidiText(
canvas, TextRunPaintInfo(TextRun(text_)),
FloatPoint(text_x, feature_y + (kTextPaddingY + kFontSize)),
Font::kUseFallbackIfFontNotReady, 1.0f, flags);
}
void PlaceholderImage::DrawPattern(GraphicsContext& context,
const FloatRect& src_rect,
const FloatSize& scale,
const FloatPoint& phase,
SkBlendMode mode,
const FloatRect& dest_rect,
const FloatSize& repeat_spacing) {
DCHECK(context.Canvas());
PaintFlags flags = context.FillFlags();
flags.setBlendMode(mode);
// Ignore the pattern specifications and just draw a single placeholder image
// over the whole |dest_rect|. This is done in order to prevent repeated icons
// from cluttering tiled background images.
Draw(context.Canvas(), flags, dest_rect, src_rect,
kDoNotRespectImageOrientation, kClampImageToSourceRect,
kUnspecifiedDecode);
}
void PlaceholderImage::DestroyDecodedData() {
paint_record_for_current_frame_.reset();
shared_font_ = scoped_refptr<SharedFont>();
}
Image::SizeAvailability PlaceholderImage::SetData(scoped_refptr<SharedBuffer>,
bool) {
return Image::kSizeAvailable;
}
} // namespace blink