blob: 7a531f6bd64efca0663ae3ae3594e973e91379a3 [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/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h"
#include "base/metrics/histogram_functions.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/bindings/modules/v8/offscreen_rendering_context.h"
#include "third_party/blink/renderer/core/css/offscreen_font_selector.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/resolver/font_style_resolver.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/canvas/text_metrics.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/core/workers/worker_settings.h"
#include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/text/bidi_text_run.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/linked_hash_set.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace {
const size_t kHardMaxCachedFonts = 250;
const size_t kMaxCachedFonts = 25;
const float kUMASampleProbability = 0.01;
class OffscreenFontCache {
public:
void PruneLocalFontCache(size_t target_size) {
while (font_lru_list_.size() > target_size) {
fonts_resolved_.erase(font_lru_list_.back());
font_lru_list_.pop_back();
}
}
void AddFont(String name, blink::Font font) {
fonts_resolved_.insert(name, font);
auto add_result = font_lru_list_.PrependOrMoveToFirst(name);
DCHECK(add_result.is_new_entry);
PruneLocalFontCache(kHardMaxCachedFonts);
}
blink::Font* GetFont(String name) {
HashMap<String, blink::Font>::iterator i = fonts_resolved_.find(name);
if (i != fonts_resolved_.end()) {
auto add_result = font_lru_list_.PrependOrMoveToFirst(name);
DCHECK(!add_result.is_new_entry);
return &(i->value);
}
return nullptr;
}
private:
HashMap<String, blink::Font> fonts_resolved_;
LinkedHashSet<String> font_lru_list_;
};
OffscreenFontCache& GetOffscreenFontCache() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<OffscreenFontCache>,
thread_specific_pool, ());
return *thread_specific_pool;
}
} // namespace
namespace blink {
OffscreenCanvasRenderingContext2D::~OffscreenCanvasRenderingContext2D() =
default;
OffscreenCanvasRenderingContext2D::OffscreenCanvasRenderingContext2D(
OffscreenCanvas* canvas,
const CanvasContextCreationAttributesCore& attrs)
: CanvasRenderingContext(canvas, attrs),
random_generator_((uint32_t)base::RandUint64()),
bernoulli_distribution_(kUMASampleProbability) {
is_valid_size_ = IsValidImageSize(Host()->Size());
// Clear the background transparent or opaque.
if (IsCanvas2DBufferValid()) {
GetOrCreateCanvasResourceProvider()->Clear();
DidDraw();
}
ExecutionContext* execution_context = canvas->GetTopExecutionContext();
if (auto* document = Document::DynamicFrom(execution_context)) {
Settings* settings = document->GetSettings();
if (settings && settings->GetDisableReadingFromCanvas())
canvas->SetDisableReadingFromCanvasTrue();
return;
}
dirty_rect_for_commit_.setEmpty();
WorkerSettings* worker_settings =
To<WorkerGlobalScope>(execution_context)->GetWorkerSettings();
if (worker_settings && worker_settings->DisableReadingFromCanvas())
canvas->SetDisableReadingFromCanvasTrue();
}
void OffscreenCanvasRenderingContext2D::Trace(Visitor* visitor) {
CanvasRenderingContext::Trace(visitor);
BaseRenderingContext2D::Trace(visitor);
}
void OffscreenCanvasRenderingContext2D::commit() {
// TODO(fserb): consolidate this with PushFrame
SkIRect damage_rect(dirty_rect_for_commit_);
dirty_rect_for_commit_.setEmpty();
FinalizeFrame();
Host()->Commit(ProduceCanvasResource(), damage_rect);
GetOffscreenFontCache().PruneLocalFontCache(kMaxCachedFonts);
}
void OffscreenCanvasRenderingContext2D::FlushRecording() {
if (!have_recorded_draw_commands_)
return;
GetCanvasResourceProvider()->FlushCanvas();
GetCanvasResourceProvider()->ReleaseLockedImages();
have_recorded_draw_commands_ = false;
}
void OffscreenCanvasRenderingContext2D::FinalizeFrame() {
TRACE_EVENT0("blink", "OffscreenCanvasRenderingContext2D::FinalizeFrame");
// Make sure surface is ready for painting: fix the rendering mode now
// because it will be too late during the paint invalidation phase.
if (!GetOrCreateCanvasResourceProvider())
return;
FlushRecording();
}
// BaseRenderingContext2D implementation
bool OffscreenCanvasRenderingContext2D::OriginClean() const {
return Host()->OriginClean();
}
void OffscreenCanvasRenderingContext2D::SetOriginTainted() {
Host()->SetOriginTainted();
}
bool OffscreenCanvasRenderingContext2D::WouldTaintOrigin(
CanvasImageSource* source) {
return CanvasRenderingContext::WouldTaintOrigin(source);
}
int OffscreenCanvasRenderingContext2D::Width() const {
return Host()->Size().Width();
}
int OffscreenCanvasRenderingContext2D::Height() const {
return Host()->Size().Height();
}
bool OffscreenCanvasRenderingContext2D::CanCreateCanvas2dResourceProvider()
const {
if (!Host() || Host()->Size().IsEmpty())
return false;
return !!GetOrCreateCanvasResourceProvider();
}
CanvasResourceProvider*
OffscreenCanvasRenderingContext2D::GetOrCreateCanvasResourceProvider() const {
// TODO(aaronhk) use Host() instead of offscreenCanvasForBinding() here
return offscreenCanvasForBinding()->GetOrCreateResourceProvider();
}
CanvasResourceProvider*
OffscreenCanvasRenderingContext2D::GetCanvasResourceProvider() const {
return Host()->ResourceProvider();
}
void OffscreenCanvasRenderingContext2D::Reset() {
Host()->DiscardResourceProvider();
BaseRenderingContext2D::Reset();
// Because the host may have changed to a zero size
is_valid_size_ = IsValidImageSize(Host()->Size());
}
scoped_refptr<CanvasResource>
OffscreenCanvasRenderingContext2D::ProduceCanvasResource() {
if (!GetOrCreateCanvasResourceProvider())
return nullptr;
scoped_refptr<CanvasResource> frame =
GetCanvasResourceProvider()->ProduceCanvasResource();
if (!frame)
return nullptr;
frame->SetOriginClean(this->OriginClean());
return frame;
}
bool OffscreenCanvasRenderingContext2D::PushFrame() {
if (dirty_rect_for_commit_.isEmpty())
return false;
SkIRect damage_rect(dirty_rect_for_commit_);
FinalizeFrame();
bool ret = Host()->PushFrame(ProduceCanvasResource(), damage_rect);
dirty_rect_for_commit_.setEmpty();
GetOffscreenFontCache().PruneLocalFontCache(kMaxCachedFonts);
return ret;
}
ImageBitmap* OffscreenCanvasRenderingContext2D::TransferToImageBitmap(
ScriptState* script_state) {
WebFeature feature = WebFeature::kOffscreenCanvasTransferToImageBitmap2D;
UseCounter::Count(ExecutionContext::From(script_state), feature);
if (!GetOrCreateCanvasResourceProvider())
return nullptr;
scoped_refptr<StaticBitmapImage> image = GetImage(kPreferAcceleration);
if (!image)
return nullptr;
image->SetOriginClean(this->OriginClean());
if (image->IsTextureBacked()) {
// Before discarding the image resource, we need to flush pending render ops
// to fully resolve the snapshot.
// We can only do this if the skImage is not null
if (auto skImage = image->PaintImageForCurrentFrame().GetSkImage()) {
skImage->getBackendTexture(true); // Flush pending ops.
} else {
// If the SkImage was null, we better return a null ImageBitmap
return nullptr;
}
}
Host()->DiscardResourceProvider();
return MakeGarbageCollected<ImageBitmap>(std::move(image));
}
scoped_refptr<StaticBitmapImage> OffscreenCanvasRenderingContext2D::GetImage(
AccelerationHint hint) {
FinalizeFrame();
if (!IsPaintable())
return nullptr;
scoped_refptr<StaticBitmapImage> image =
GetCanvasResourceProvider()->Snapshot();
return image;
}
void OffscreenCanvasRenderingContext2D::SetOffscreenCanvasGetContextResult(
OffscreenRenderingContext& result) {
result.SetOffscreenCanvasRenderingContext2D(this);
}
bool OffscreenCanvasRenderingContext2D::ParseColorOrCurrentColor(
Color& color,
const String& color_string) const {
return ::blink::ParseColorOrCurrentColor(color, color_string, nullptr);
}
cc::PaintCanvas* OffscreenCanvasRenderingContext2D::GetOrCreatePaintCanvas() {
if (!is_valid_size_ || !GetOrCreateCanvasResourceProvider())
return nullptr;
return GetPaintCanvas();
}
cc::PaintCanvas* OffscreenCanvasRenderingContext2D::GetPaintCanvas() const {
if (!is_valid_size_ || !GetCanvasResourceProvider())
return nullptr;
return GetCanvasResourceProvider()->Canvas();
}
void OffscreenCanvasRenderingContext2D::DidDraw() {
have_recorded_draw_commands_ = true;
dirty_rect_for_commit_.setWH(Width(), Height());
Host()->DidDraw();
if (GetCanvasResourceProvider() && GetCanvasResourceProvider()->needs_flush())
FinalizeFrame();
}
void OffscreenCanvasRenderingContext2D::DidDraw(const SkIRect& dirty_rect) {
have_recorded_draw_commands_ = true;
dirty_rect_for_commit_.join(dirty_rect);
Host()->DidDraw(SkRect::Make(dirty_rect_for_commit_));
if (GetCanvasResourceProvider() && GetCanvasResourceProvider()->needs_flush())
FinalizeFrame();
}
bool OffscreenCanvasRenderingContext2D::StateHasFilter() {
return GetState().HasFilterForOffscreenCanvas(Host()->Size(), this);
}
sk_sp<PaintFilter> OffscreenCanvasRenderingContext2D::StateGetFilter() {
return GetState().GetFilterForOffscreenCanvas(Host()->Size(), this);
}
void OffscreenCanvasRenderingContext2D::SnapshotStateForFilter() {
ModifiableState().SetFontForFilter(AccessFont());
}
void OffscreenCanvasRenderingContext2D::ValidateStateStackWithCanvas(
const cc::PaintCanvas* canvas) const {
#if DCHECK_IS_ON()
if (canvas) {
DCHECK_EQ(static_cast<size_t>(canvas->getSaveCount()),
state_stack_.size() + 1);
}
#endif
}
bool OffscreenCanvasRenderingContext2D::isContextLost() const {
return false;
}
bool OffscreenCanvasRenderingContext2D::IsPaintable() const {
return Host()->ResourceProvider();
}
String OffscreenCanvasRenderingContext2D::ColorSpaceAsString() const {
return CanvasRenderingContext::ColorSpaceAsString();
}
CanvasPixelFormat OffscreenCanvasRenderingContext2D::PixelFormat() const {
return ColorParams().PixelFormat();
}
CanvasColorParams OffscreenCanvasRenderingContext2D::ColorParams() const {
return CanvasRenderingContext::ColorParams();
}
bool OffscreenCanvasRenderingContext2D::WritePixels(
const SkImageInfo& orig_info,
const void* pixels,
size_t row_bytes,
int x,
int y) {
if (!GetOrCreateCanvasResourceProvider())
return false;
DCHECK(IsPaintable());
FinalizeFrame();
have_recorded_draw_commands_ = false;
return offscreenCanvasForBinding()->ResourceProvider()->WritePixels(
orig_info, pixels, row_bytes, x, y);
}
bool OffscreenCanvasRenderingContext2D::IsAccelerated() const {
return IsPaintable() && GetCanvasResourceProvider()->IsAccelerated();
}
String OffscreenCanvasRenderingContext2D::font() const {
if (!GetState().HasRealizedFont())
return kDefaultFont;
StringBuilder serialized_font;
const FontDescription& font_description = GetState().GetFontDescription();
if (font_description.Style() == ItalicSlopeValue())
serialized_font.Append("italic ");
if (font_description.Weight() == BoldWeightValue())
serialized_font.Append("bold ");
if (font_description.VariantCaps() == FontDescription::kSmallCaps)
serialized_font.Append("small-caps ");
serialized_font.AppendNumber(font_description.ComputedPixelSize());
serialized_font.Append("px");
const FontFamily& first_font_family = font_description.Family();
for (const FontFamily* font_family = &first_font_family; font_family;
font_family = font_family->Next()) {
if (font_family != &first_font_family)
serialized_font.Append(',');
// FIXME: We should append family directly to serializedFont rather than
// building a temporary string.
String family = font_family->Family();
if (family.StartsWith("-webkit-"))
family = family.Substring(8);
if (family.Contains(' '))
family = "\"" + family + "\"";
serialized_font.Append(' ');
serialized_font.Append(family);
}
return serialized_font.ToString();
}
void OffscreenCanvasRenderingContext2D::setFont(const String& new_font) {
if (GetState().HasRealizedFont() && new_font == GetState().UnparsedFont())
return;
base::TimeTicks start_time = base::TimeTicks::Now();
OffscreenFontCache& font_cache = GetOffscreenFontCache();
Font* cached_font = font_cache.GetFont(new_font);
if (cached_font) {
ModifiableState().SetFont(*cached_font, Host()->GetFontSelector());
} else {
auto* style =
MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLStandardMode);
if (!style)
return;
CSSParser::ParseValue(
style, CSSPropertyID::kFont, new_font, true,
Host()->GetTopExecutionContext()->GetSecureContextMode());
// According to
// http://lists.w3.org/Archives/Public/public-html/2009Jul/0947.html,
// the "inherit", "initial" and "unset" values must be ignored.
const CSSValue* font_value =
style->GetPropertyCSSValue(CSSPropertyID::kFontSize);
if (!font_value || font_value->IsCSSWideKeyword())
return;
FontDescription desc =
FontStyleResolver::ComputeFont(*style, Host()->GetFontSelector());
Font font = Font(desc);
font_cache.AddFont(new_font, font);
ModifiableState().SetFont(font, Host()->GetFontSelector());
}
ModifiableState().SetUnparsedFont(new_font);
if (bernoulli_distribution_(random_generator_)) {
base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
base::UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
"OffscreenCanvas.TextMetrics.SetFont", elapsed);
}
}
static inline TextDirection ToTextDirection(
CanvasRenderingContext2DState::Direction direction) {
switch (direction) {
case CanvasRenderingContext2DState::kDirectionInherit:
return TextDirection::kLtr;
case CanvasRenderingContext2DState::kDirectionRTL:
return TextDirection::kRtl;
case CanvasRenderingContext2DState::kDirectionLTR:
return TextDirection::kLtr;
}
NOTREACHED();
return TextDirection::kLtr;
}
String OffscreenCanvasRenderingContext2D::direction() const {
return ToTextDirection(GetState().GetDirection()) == TextDirection::kRtl
? kRtlDirectionString
: kLtrDirectionString;
}
void OffscreenCanvasRenderingContext2D::setDirection(
const String& direction_string) {
CanvasRenderingContext2DState::Direction direction;
if (direction_string == kInheritDirectionString)
direction = CanvasRenderingContext2DState::kDirectionInherit;
else if (direction_string == kRtlDirectionString)
direction = CanvasRenderingContext2DState::kDirectionRTL;
else if (direction_string == kLtrDirectionString)
direction = CanvasRenderingContext2DState::kDirectionLTR;
else
return;
if (GetState().GetDirection() != direction)
ModifiableState().SetDirection(direction);
}
void OffscreenCanvasRenderingContext2D::fillText(const String& text,
double x,
double y) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kFillPaintType);
}
void OffscreenCanvasRenderingContext2D::fillText(const String& text,
double x,
double y,
double max_width) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kFillPaintType,
&max_width);
}
void OffscreenCanvasRenderingContext2D::strokeText(const String& text,
double x,
double y) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kStrokePaintType);
}
void OffscreenCanvasRenderingContext2D::strokeText(const String& text,
double x,
double y,
double max_width) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kStrokePaintType,
&max_width);
}
void OffscreenCanvasRenderingContext2D::DrawTextInternal(
const String& text,
double x,
double y,
CanvasRenderingContext2DState::PaintType paint_type,
double* max_width) {
cc::PaintCanvas* paint_canvas = GetOrCreatePaintCanvas();
if (!paint_canvas)
return;
if (!std::isfinite(x) || !std::isfinite(y))
return;
if (max_width && (!std::isfinite(*max_width) || *max_width <= 0))
return;
const Font& font = AccessFont();
font.GetFontDescription().SetSubpixelAscentDescent(true);
const SimpleFontData* font_data = font.PrimaryFont();
DCHECK(font_data);
if (!font_data)
return;
const FontMetrics& font_metrics = font_data->GetFontMetrics();
// FIXME: Need to turn off font smoothing.
TextDirection direction = ToTextDirection(GetState().GetDirection());
bool is_rtl = direction == TextDirection::kRtl;
TextRun text_run(text, 0, 0, TextRun::kAllowTrailingExpansion, direction,
false);
text_run.SetNormalizeSpace(true);
// Draw the item text at the correct point.
FloatPoint location(x, y + GetFontBaseline(*font_data));
double font_width = font.Width(text_run);
bool use_max_width = (max_width && *max_width < font_width);
double width = use_max_width ? *max_width : font_width;
TextAlign align = GetState().GetTextAlign();
if (align == kStartTextAlign)
align = is_rtl ? kRightTextAlign : kLeftTextAlign;
else if (align == kEndTextAlign)
align = is_rtl ? kLeftTextAlign : kRightTextAlign;
switch (align) {
case kCenterTextAlign:
location.SetX(location.X() - width / 2);
break;
case kRightTextAlign:
location.SetX(location.X() - width);
break;
default:
break;
}
TextRunPaintInfo text_run_paint_info(text_run);
FloatRect bounds(
location.X() - font_metrics.Height() / 2,
location.Y() - font_metrics.Ascent() - font_metrics.LineGap(),
width + font_metrics.Height(), font_metrics.LineSpacing());
if (paint_type == CanvasRenderingContext2DState::kStrokePaintType)
InflateStrokeRect(bounds);
int save_count = paint_canvas->getSaveCount();
if (use_max_width) {
paint_canvas->save();
paint_canvas->translate(location.X(), location.Y());
// We draw when fontWidth is 0 so compositing operations (eg, a "copy" op)
// still work.
paint_canvas->scale((font_width > 0 ? (width / font_width) : 0), 1);
location = FloatPoint();
}
Draw(
[&font, &text_run_paint_info, &location](
cc::PaintCanvas* paint_canvas,
const PaintFlags* flags) /* draw lambda */ {
font.DrawBidiText(paint_canvas, text_run_paint_info, location,
Font::kUseFallbackIfFontNotReady, kCDeviceScaleFactor,
*flags);
},
[](const SkIRect& rect) // overdraw test lambda
{ return false; },
bounds, paint_type);
paint_canvas->restoreToCount(save_count);
ValidateStateStack();
}
TextMetrics* OffscreenCanvasRenderingContext2D::measureText(
const String& text) {
const Font& font = AccessFont();
TextDirection direction;
if (GetState().GetDirection() ==
CanvasRenderingContext2DState::kDirectionInherit)
direction = DetermineDirectionality(text);
else
direction = ToTextDirection(GetState().GetDirection());
return MakeGarbageCollected<TextMetrics>(font, direction,
GetState().GetTextBaseline(),
GetState().GetTextAlign(), text);
}
const Font& OffscreenCanvasRenderingContext2D::AccessFont() {
if (!GetState().HasRealizedFont())
setFont(GetState().UnparsedFont());
return ModifiableState().GetFont();
}
bool OffscreenCanvasRenderingContext2D::IsCanvas2DBufferValid() const {
if (IsPaintable())
return GetCanvasResourceProvider()->IsValid();
return false;
}
} // namespace blink