| /* |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. |
| * All rights reserved. |
| * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies) |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2008 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2008 Dirk Schulze <krit@webkit.org> |
| * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved. |
| * Copyright (C) 2012, 2013 Intel Corporation. All rights reserved. |
| * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "modules/canvas2d/CanvasRenderingContext2D.h" |
| |
| #include "bindings/core/v8/ExceptionMessages.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/modules/v8/RenderingContext.h" |
| #include "core/CSSPropertyNames.h" |
| #include "core/css/StylePropertySet.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/dom/AXObjectCache.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/events/Event.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/TextMetrics.h" |
| #include "core/html/canvas/CanvasFontCache.h" |
| #include "core/layout/HitTestCanvasResult.h" |
| #include "core/layout/LayoutBox.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "modules/canvas2d/CanvasStyle.h" |
| #include "modules/canvas2d/HitRegion.h" |
| #include "modules/canvas2d/Path2D.h" |
| #include "platform/fonts/FontCache.h" |
| #include "platform/graphics/CanvasHeuristicParameters.h" |
| #include "platform/graphics/DrawLooperBuilder.h" |
| #include "platform/graphics/ImageBuffer.h" |
| #include "platform/graphics/StrokeData.h" |
| #include "platform/graphics/paint/PaintCanvas.h" |
| #include "platform/graphics/paint/PaintFlags.h" |
| #include "platform/graphics/skia/SkiaUtils.h" |
| #include "platform/text/BidiTextRun.h" |
| #include "platform/wtf/MathExtras.h" |
| #include "platform/wtf/text/StringBuilder.h" |
| #include "platform/wtf/typed_arrays/ArrayBufferContents.h" |
| #include "public/platform/Platform.h" |
| #include "third_party/skia/include/core/SkImageFilter.h" |
| |
| namespace blink { |
| |
| static const char kDefaultFont[] = "10px sans-serif"; |
| static const char kInheritDirectionString[] = "inherit"; |
| static const char kRtlDirectionString[] = "rtl"; |
| static const char kLtrDirectionString[] = "ltr"; |
| static const double kTryRestoreContextInterval = 0.5; |
| static const unsigned kMaxTryRestoreContextAttempts = 4; |
| static const double kCDeviceScaleFactor = 1.0; // Canvas is device independent |
| |
| static bool ContextLostRestoredEventsEnabled() { |
| return RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); |
| } |
| |
| // Drawing methods need to use this instead of SkAutoCanvasRestore in case |
| // overdraw detection substitutes the recording canvas (to discard overdrawn |
| // draw calls). |
| class CanvasRenderingContext2DAutoRestoreSkCanvas { |
| STACK_ALLOCATED(); |
| |
| public: |
| explicit CanvasRenderingContext2DAutoRestoreSkCanvas( |
| CanvasRenderingContext2D* context) |
| : context_(context), save_count_(0) { |
| DCHECK(context_); |
| PaintCanvas* c = context_->DrawingCanvas(); |
| if (c) { |
| save_count_ = c->getSaveCount(); |
| } |
| } |
| |
| ~CanvasRenderingContext2DAutoRestoreSkCanvas() { |
| PaintCanvas* c = context_->DrawingCanvas(); |
| if (c) |
| c->restoreToCount(save_count_); |
| context_->ValidateStateStack(); |
| } |
| |
| private: |
| Member<CanvasRenderingContext2D> context_; |
| int save_count_; |
| }; |
| |
| CanvasRenderingContext2D::CanvasRenderingContext2D( |
| HTMLCanvasElement* canvas, |
| const CanvasContextCreationAttributes& attrs) |
| : CanvasRenderingContext(canvas, attrs), |
| context_lost_mode_(kNotLostContext), |
| context_restorable_(true), |
| try_restore_context_attempt_count_(0), |
| dispatch_context_lost_event_timer_( |
| TaskRunnerHelper::Get(TaskType::kMiscPlatformAPI, |
| canvas->GetDocument().GetFrame()), |
| this, |
| &CanvasRenderingContext2D::DispatchContextLostEvent), |
| dispatch_context_restored_event_timer_( |
| TaskRunnerHelper::Get(TaskType::kMiscPlatformAPI, |
| canvas->GetDocument().GetFrame()), |
| this, |
| &CanvasRenderingContext2D::DispatchContextRestoredEvent), |
| try_restore_context_event_timer_( |
| TaskRunnerHelper::Get(TaskType::kMiscPlatformAPI, |
| canvas->GetDocument().GetFrame()), |
| this, |
| &CanvasRenderingContext2D::TryRestoreContextEvent), |
| should_prune_local_font_cache_(false) { |
| if (canvas->GetDocument().GetSettings() && |
| canvas->GetDocument().GetSettings()->GetAntialiasedClips2dCanvasEnabled()) |
| clip_antialiasing_ = kAntiAliased; |
| SetShouldAntialias(true); |
| ValidateStateStack(); |
| } |
| |
| void CanvasRenderingContext2D::SetCanvasGetContextResult( |
| RenderingContext& result) { |
| result.setCanvasRenderingContext2D(this); |
| } |
| |
| CanvasRenderingContext2D::~CanvasRenderingContext2D() {} |
| |
| void CanvasRenderingContext2D::ValidateStateStack() const { |
| #if DCHECK_IS_ON() |
| if (PaintCanvas* sk_canvas = canvas()->ExistingDrawingCanvas()) { |
| // The canvas should always have an initial save frame, to support |
| // resetting the top level matrix and clip. |
| DCHECK_GT(sk_canvas->getSaveCount(), 1); |
| |
| if (context_lost_mode_ == kNotLostContext) { |
| DCHECK_EQ(static_cast<size_t>(sk_canvas->getSaveCount()), |
| state_stack_.size() + 1); |
| } |
| } |
| #endif |
| CHECK(state_stack_.front() |
| .Get()); // Temporary for investigating crbug.com/648510 |
| } |
| |
| bool CanvasRenderingContext2D::IsAccelerated() const { |
| if (!HasImageBuffer()) |
| return false; |
| return GetImageBuffer()->IsAccelerated(); |
| } |
| |
| bool CanvasRenderingContext2D::IsComposited() const { |
| return IsAccelerated(); |
| } |
| |
| void CanvasRenderingContext2D::Stop() { |
| if (!isContextLost()) { |
| // Never attempt to restore the context because the page is being torn down. |
| LoseContext(kSyntheticLostContext); |
| } |
| } |
| |
| bool CanvasRenderingContext2D::isContextLost() const { |
| return context_lost_mode_ != kNotLostContext; |
| } |
| |
| void CanvasRenderingContext2D::LoseContext(LostContextMode lost_mode) { |
| if (context_lost_mode_ != kNotLostContext) |
| return; |
| context_lost_mode_ = lost_mode; |
| if (context_lost_mode_ == kSyntheticLostContext && canvas()) { |
| host()->DiscardImageBuffer(); |
| } |
| dispatch_context_lost_event_timer_.StartOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void CanvasRenderingContext2D::DidSetSurfaceSize() { |
| if (!context_restorable_) |
| return; |
| // This code path is for restoring from an eviction |
| // Restoring from surface failure is handled internally |
| DCHECK(context_lost_mode_ != kNotLostContext && !HasImageBuffer()); |
| |
| if (GetImageBuffer()) { |
| if (ContextLostRestoredEventsEnabled()) { |
| dispatch_context_restored_event_timer_.StartOneShot(0, BLINK_FROM_HERE); |
| } else { |
| // legacy synchronous context restoration. |
| Reset(); |
| context_lost_mode_ = kNotLostContext; |
| } |
| } |
| } |
| |
| DEFINE_TRACE(CanvasRenderingContext2D) { |
| visitor->Trace(hit_region_manager_); |
| visitor->Trace(filter_operations_); |
| CanvasRenderingContext::Trace(visitor); |
| BaseRenderingContext2D::Trace(visitor); |
| SVGResourceClient::Trace(visitor); |
| } |
| |
| void CanvasRenderingContext2D::DispatchContextLostEvent(TimerBase*) { |
| if (canvas() && ContextLostRestoredEventsEnabled()) { |
| Event* event = Event::CreateCancelable(EventTypeNames::contextlost); |
| canvas()->DispatchEvent(event); |
| if (event->defaultPrevented()) { |
| context_restorable_ = false; |
| } |
| } |
| |
| // If RealLostContext, it means the context was not lost due to surface |
| // failure but rather due to a an eviction, which means image buffer exists. |
| if (context_restorable_ && context_lost_mode_ == kRealLostContext) { |
| try_restore_context_attempt_count_ = 0; |
| try_restore_context_event_timer_.StartRepeating(kTryRestoreContextInterval, |
| BLINK_FROM_HERE); |
| } |
| } |
| |
| void CanvasRenderingContext2D::TryRestoreContextEvent(TimerBase* timer) { |
| if (context_lost_mode_ == kNotLostContext) { |
| // Canvas was already restored (possibly thanks to a resize), so stop |
| // trying. |
| try_restore_context_event_timer_.Stop(); |
| return; |
| } |
| |
| DCHECK(context_lost_mode_ == kRealLostContext); |
| if (HasImageBuffer() && GetImageBuffer()->RestoreSurface()) { |
| try_restore_context_event_timer_.Stop(); |
| DispatchContextRestoredEvent(nullptr); |
| } |
| |
| if (++try_restore_context_attempt_count_ > kMaxTryRestoreContextAttempts) { |
| // final attempt: allocate a brand new image buffer instead of restoring |
| host()->DiscardImageBuffer(); |
| try_restore_context_event_timer_.Stop(); |
| if (GetImageBuffer()) |
| DispatchContextRestoredEvent(nullptr); |
| } |
| } |
| |
| void CanvasRenderingContext2D::DispatchContextRestoredEvent(TimerBase*) { |
| if (context_lost_mode_ == kNotLostContext) |
| return; |
| Reset(); |
| context_lost_mode_ = kNotLostContext; |
| if (ContextLostRestoredEventsEnabled()) { |
| Event* event(Event::Create(EventTypeNames::contextrestored)); |
| canvas()->DispatchEvent(event); |
| } |
| } |
| |
| void CanvasRenderingContext2D::WillDrawImage(CanvasImageSource* source) const { |
| canvas()->WillDrawImageTo2DContext(source); |
| } |
| |
| CanvasColorSpace CanvasRenderingContext2D::ColorSpace() const { |
| return color_params().color_space(); |
| } |
| |
| String CanvasRenderingContext2D::ColorSpaceAsString() const { |
| return CanvasRenderingContext::ColorSpaceAsString(); |
| } |
| |
| CanvasPixelFormat CanvasRenderingContext2D::PixelFormat() const { |
| return color_params().pixel_format(); |
| } |
| |
| void CanvasRenderingContext2D::Reset() { |
| // This is a multiple inherritance bootstrap |
| BaseRenderingContext2D::Reset(); |
| } |
| |
| void CanvasRenderingContext2D::RestoreCanvasMatrixClipStack( |
| PaintCanvas* c) const { |
| RestoreMatrixClipStack(c); |
| } |
| |
| bool CanvasRenderingContext2D::ShouldAntialias() const { |
| return GetState().ShouldAntialias(); |
| } |
| |
| void CanvasRenderingContext2D::SetShouldAntialias(bool do_aa) { |
| ModifiableState().SetShouldAntialias(do_aa); |
| } |
| |
| void CanvasRenderingContext2D::scrollPathIntoView() { |
| ScrollPathIntoViewInternal(path_); |
| } |
| |
| void CanvasRenderingContext2D::scrollPathIntoView(Path2D* path2d) { |
| ScrollPathIntoViewInternal(path2d->GetPath()); |
| } |
| |
| void CanvasRenderingContext2D::ScrollPathIntoViewInternal(const Path& path) { |
| if (!GetState().IsTransformInvertible() || path.IsEmpty()) |
| return; |
| |
| canvas()->GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| LayoutObject* renderer = canvas()->GetLayoutObject(); |
| LayoutBox* layout_box = canvas()->GetLayoutBox(); |
| if (!renderer || !layout_box) |
| return; |
| |
| // Apply transformation and get the bounding rect |
| Path transformed_path = path; |
| transformed_path.Transform(GetState().Transform()); |
| FloatRect bounding_rect = transformed_path.BoundingRect(); |
| |
| // Offset by the canvas rect |
| LayoutRect path_rect(bounding_rect); |
| IntRect canvas_rect = layout_box->AbsoluteContentBox(); |
| path_rect.MoveBy(canvas_rect.Location()); |
| |
| renderer->ScrollRectToVisible(path_rect, ScrollAlignment::kAlignCenterAlways, |
| ScrollAlignment::kAlignTopAlways); |
| |
| // TODO: should implement "inform the user" that the caret and/or |
| // selection the specified rectangle of the canvas. See |
| // http://crbug.com/357987 |
| } |
| |
| void CanvasRenderingContext2D::clearRect(double x, |
| double y, |
| double width, |
| double height) { |
| BaseRenderingContext2D::clearRect(x, y, width, height); |
| |
| if (hit_region_manager_) { |
| FloatRect rect(x, y, width, height); |
| hit_region_manager_->RemoveHitRegionsInRect(rect, GetState().Transform()); |
| } |
| } |
| |
| void CanvasRenderingContext2D::DidDraw(const SkIRect& dirty_rect) { |
| if (dirty_rect.isEmpty()) |
| return; |
| |
| if (CanvasHeuristicParameters::kBlurredShadowsAreExpensive && |
| GetState().ShouldDrawShadows() && GetState().ShadowBlur() > 0) { |
| ImageBuffer* buffer = GetImageBuffer(); |
| if (buffer) |
| buffer->SetHasExpensiveOp(); |
| } |
| |
| CanvasRenderingContext::DidDraw(dirty_rect); |
| } |
| |
| bool CanvasRenderingContext2D::StateHasFilter() { |
| return GetState().HasFilter(canvas(), canvas()->Size(), this); |
| } |
| |
| sk_sp<SkImageFilter> CanvasRenderingContext2D::StateGetFilter() { |
| return GetState().GetFilter(canvas(), canvas()->Size(), this); |
| } |
| |
| void CanvasRenderingContext2D::SnapshotStateForFilter() { |
| // The style resolution required for fonts is not available in frame-less |
| // documents. |
| if (!canvas()->GetDocument().GetFrame()) |
| return; |
| |
| ModifiableState().SetFontForFilter(AccessFont()); |
| } |
| |
| PaintCanvas* CanvasRenderingContext2D::DrawingCanvas() const { |
| if (isContextLost()) |
| return nullptr; |
| return canvas()->DrawingCanvas(); |
| } |
| |
| PaintCanvas* CanvasRenderingContext2D::ExistingDrawingCanvas() const { |
| return canvas()->ExistingDrawingCanvas(); |
| } |
| |
| void CanvasRenderingContext2D::DisableDeferral(DisableDeferralReason reason) { |
| canvas()->DisableDeferral(reason); |
| } |
| |
| AffineTransform CanvasRenderingContext2D::BaseTransform() const { |
| return canvas()->BaseTransform(); |
| } |
| |
| String CanvasRenderingContext2D::font() const { |
| if (!GetState().HasRealizedFont()) |
| return kDefaultFont; |
| |
| canvas()->GetDocument().GetCanvasFontCache()->WillUseCurrentFont(); |
| StringBuilder serialized_font; |
| const FontDescription& font_description = |
| GetState().GetFont().GetFontDescription(); |
| |
| if (font_description.Style() == kFontStyleItalic) |
| serialized_font.Append("italic "); |
| if (font_description.Weight() == kFontWeightBold) |
| 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 CanvasRenderingContext2D::setFont(const String& new_font) { |
| // The style resolution required for fonts is not available in frame-less |
| // documents. |
| if (!canvas()->GetDocument().GetFrame()) |
| return; |
| |
| canvas()->GetDocument().UpdateStyleAndLayoutTreeForNode(canvas()); |
| |
| // The following early exit is dependent on the cache not being empty |
| // because an empty cache may indicate that a style change has occured |
| // which would require that the font be re-resolved. This check has to |
| // come after the layout tree update to flush pending style changes. |
| if (new_font == GetState().UnparsedFont() && GetState().HasRealizedFont() && |
| fonts_resolved_using_current_style_.size() > 0) |
| return; |
| |
| CanvasFontCache* canvas_font_cache = |
| canvas()->GetDocument().GetCanvasFontCache(); |
| |
| // Map the <canvas> font into the text style. If the font uses keywords like |
| // larger/smaller, these will work relative to the canvas. |
| RefPtr<ComputedStyle> font_style; |
| const ComputedStyle* computed_style = canvas()->EnsureComputedStyle(); |
| if (computed_style) { |
| HashMap<String, Font>::iterator i = |
| fonts_resolved_using_current_style_.find(new_font); |
| if (i != fonts_resolved_using_current_style_.end()) { |
| DCHECK(font_lru_list_.Contains(new_font)); |
| font_lru_list_.erase(new_font); |
| font_lru_list_.insert(new_font); |
| ModifiableState().SetFont( |
| i->value, canvas()->GetDocument().GetStyleEngine().FontSelector()); |
| } else { |
| MutableStylePropertySet* parsed_style = |
| canvas_font_cache->ParseFont(new_font); |
| if (!parsed_style) |
| return; |
| font_style = ComputedStyle::Create(); |
| FontDescription element_font_description( |
| computed_style->GetFontDescription()); |
| // Reset the computed size to avoid inheriting the zoom factor from the |
| // <canvas> element. |
| element_font_description.SetComputedSize( |
| element_font_description.SpecifiedSize()); |
| font_style->SetFontDescription(element_font_description); |
| font_style->GetFont().Update(font_style->GetFont().GetFontSelector()); |
| canvas()->GetDocument().EnsureStyleResolver().ComputeFont( |
| font_style.Get(), *parsed_style); |
| fonts_resolved_using_current_style_.insert(new_font, |
| font_style->GetFont()); |
| DCHECK(!font_lru_list_.Contains(new_font)); |
| font_lru_list_.insert(new_font); |
| PruneLocalFontCache(canvas_font_cache->HardMaxFonts()); // hard limit |
| should_prune_local_font_cache_ = true; // apply soft limit |
| ModifiableState().SetFont( |
| font_style->GetFont(), |
| canvas()->GetDocument().GetStyleEngine().FontSelector()); |
| } |
| } else { |
| Font resolved_font; |
| if (!canvas_font_cache->GetFontUsingDefaultStyle(new_font, resolved_font)) |
| return; |
| ModifiableState().SetFont( |
| resolved_font, canvas()->GetDocument().GetStyleEngine().FontSelector()); |
| } |
| |
| // The parse succeeded. |
| String new_font_safe_copy( |
| new_font); // Create a string copy since newFont can be |
| // deleted inside realizeSaves. |
| ModifiableState().SetUnparsedFont(new_font_safe_copy); |
| } |
| |
| void CanvasRenderingContext2D::DidProcessTask() { |
| CanvasRenderingContext::DidProcessTask(); |
| // This should be the only place where canvas() needs to be checked for |
| // nullness because the circular refence with HTMLCanvasElement means the |
| // canvas and the context keep each other alive. As long as the pair is |
| // referenced, the task observer is the only persistent refernce to this |
| // object |
| // that is not traced, so didProcessTask() may be called at a time when the |
| // canvas has been garbage collected but not the context. |
| if (should_prune_local_font_cache_ && canvas()) { |
| should_prune_local_font_cache_ = false; |
| PruneLocalFontCache( |
| canvas()->GetDocument().GetCanvasFontCache()->MaxFonts()); |
| } |
| } |
| |
| void CanvasRenderingContext2D::PruneLocalFontCache(size_t target_size) { |
| if (target_size == 0) { |
| // Short cut: LRU does not matter when evicting everything |
| font_lru_list_.clear(); |
| fonts_resolved_using_current_style_.clear(); |
| return; |
| } |
| while (font_lru_list_.size() > target_size) { |
| fonts_resolved_using_current_style_.erase(font_lru_list_.front()); |
| font_lru_list_.RemoveFirst(); |
| } |
| } |
| |
| void CanvasRenderingContext2D::StyleDidChange(const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| if (old_style && old_style->GetFont() == new_style.GetFont()) |
| return; |
| PruneLocalFontCache(0); |
| } |
| |
| TreeScope* CanvasRenderingContext2D::GetTreeScope() { |
| return &canvas()->GetTreeScope(); |
| } |
| |
| void CanvasRenderingContext2D::ClearFilterReferences() { |
| filter_operations_.RemoveClient(this); |
| filter_operations_.clear(); |
| } |
| |
| void CanvasRenderingContext2D::UpdateFilterReferences( |
| const FilterOperations& filters) { |
| ClearFilterReferences(); |
| filters.AddClient(this); |
| filter_operations_ = filters; |
| } |
| |
| void CanvasRenderingContext2D::ResourceContentChanged() { |
| ResourceElementChanged(); |
| } |
| |
| void CanvasRenderingContext2D::ResourceElementChanged() { |
| ClearFilterReferences(); |
| GetState().ClearResolvedFilter(); |
| } |
| |
| bool CanvasRenderingContext2D::OriginClean() const { |
| return host()->OriginClean(); |
| } |
| |
| void CanvasRenderingContext2D::SetOriginTainted() { |
| return host()->SetOriginTainted(); |
| } |
| |
| int CanvasRenderingContext2D::Width() const { |
| return host()->Size().Width(); |
| } |
| |
| int CanvasRenderingContext2D::Height() const { |
| return host()->Size().Height(); |
| } |
| |
| bool CanvasRenderingContext2D::HasImageBuffer() const { |
| return host()->GetImageBuffer(); |
| } |
| |
| ImageBuffer* CanvasRenderingContext2D::GetImageBuffer() const { |
| return const_cast<CanvasRenderingContextHost*>(host()) |
| ->GetOrCreateImageBuffer(); |
| } |
| |
| PassRefPtr<Image> blink::CanvasRenderingContext2D::GetImage( |
| AccelerationHint hint, |
| SnapshotReason reason) const { |
| if (!HasImageBuffer()) |
| return nullptr; |
| return GetImageBuffer()->NewImageSnapshot(hint, reason); |
| } |
| |
| bool CanvasRenderingContext2D::ParseColorOrCurrentColor( |
| Color& color, |
| const String& color_string) const { |
| return ::blink::ParseColorOrCurrentColor(color, color_string, canvas()); |
| } |
| |
| HitTestCanvasResult* CanvasRenderingContext2D::GetControlAndIdIfHitRegionExists( |
| const LayoutPoint& location) { |
| if (HitRegionsCount() <= 0) |
| return HitTestCanvasResult::Create(String(), nullptr); |
| |
| LayoutBox* box = canvas()->GetLayoutBox(); |
| FloatPoint local_pos = |
| box->AbsoluteToLocal(FloatPoint(location), kUseTransforms); |
| if (box->HasBorderOrPadding()) |
| local_pos.Move(-box->ContentBoxOffset()); |
| local_pos.Scale(canvas()->width() / box->ContentWidth(), |
| canvas()->height() / box->ContentHeight()); |
| |
| HitRegion* hit_region = HitRegionAtPoint(local_pos); |
| if (hit_region) { |
| Element* control = hit_region->Control(); |
| if (control && canvas()->IsSupportedInteractiveCanvasFallback(*control)) |
| return HitTestCanvasResult::Create(hit_region->Id(), |
| hit_region->Control()); |
| return HitTestCanvasResult::Create(hit_region->Id(), nullptr); |
| } |
| return HitTestCanvasResult::Create(String(), nullptr); |
| } |
| |
| String CanvasRenderingContext2D::GetIdFromControl(const Element* element) { |
| if (HitRegionsCount() <= 0) |
| return String(); |
| |
| if (HitRegion* hit_region = |
| hit_region_manager_->GetHitRegionByControl(element)) |
| return hit_region->Id(); |
| return String(); |
| } |
| |
| String CanvasRenderingContext2D::textAlign() const { |
| return TextAlignName(GetState().GetTextAlign()); |
| } |
| |
| void CanvasRenderingContext2D::setTextAlign(const String& s) { |
| TextAlign align; |
| if (!ParseTextAlign(s, align)) |
| return; |
| if (GetState().GetTextAlign() == align) |
| return; |
| ModifiableState().SetTextAlign(align); |
| } |
| |
| String CanvasRenderingContext2D::textBaseline() const { |
| return TextBaselineName(GetState().GetTextBaseline()); |
| } |
| |
| void CanvasRenderingContext2D::setTextBaseline(const String& s) { |
| TextBaseline baseline; |
| if (!ParseTextBaseline(s, baseline)) |
| return; |
| if (GetState().GetTextBaseline() == baseline) |
| return; |
| ModifiableState().SetTextBaseline(baseline); |
| } |
| |
| static inline TextDirection ToTextDirection( |
| CanvasRenderingContext2DState::Direction direction, |
| HTMLCanvasElement* canvas, |
| const ComputedStyle** computed_style = 0) { |
| const ComputedStyle* style = |
| (computed_style || |
| direction == CanvasRenderingContext2DState::kDirectionInherit) |
| ? canvas->EnsureComputedStyle() |
| : nullptr; |
| if (computed_style) |
| *computed_style = style; |
| switch (direction) { |
| case CanvasRenderingContext2DState::kDirectionInherit: |
| return style ? style->Direction() : TextDirection::kLtr; |
| case CanvasRenderingContext2DState::kDirectionRTL: |
| return TextDirection::kRtl; |
| case CanvasRenderingContext2DState::kDirectionLTR: |
| return TextDirection::kLtr; |
| } |
| NOTREACHED(); |
| return TextDirection::kLtr; |
| } |
| |
| String CanvasRenderingContext2D::direction() const { |
| if (GetState().GetDirection() == |
| CanvasRenderingContext2DState::kDirectionInherit) |
| canvas()->GetDocument().UpdateStyleAndLayoutTreeForNode(canvas()); |
| return ToTextDirection(GetState().GetDirection(), canvas()) == |
| TextDirection::kRtl |
| ? kRtlDirectionString |
| : kLtrDirectionString; |
| } |
| |
| void CanvasRenderingContext2D::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) |
| return; |
| |
| ModifiableState().SetDirection(direction); |
| } |
| |
| void CanvasRenderingContext2D::fillText(const String& text, |
| double x, |
| double y) { |
| DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kFillPaintType); |
| } |
| |
| void CanvasRenderingContext2D::fillText(const String& text, |
| double x, |
| double y, |
| double max_width) { |
| DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kFillPaintType, |
| &max_width); |
| } |
| |
| void CanvasRenderingContext2D::strokeText(const String& text, |
| double x, |
| double y) { |
| DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kStrokePaintType); |
| } |
| |
| void CanvasRenderingContext2D::strokeText(const String& text, |
| double x, |
| double y, |
| double max_width) { |
| DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kStrokePaintType, |
| &max_width); |
| } |
| |
| TextMetrics* CanvasRenderingContext2D::measureText(const String& text) { |
| TextMetrics* metrics = TextMetrics::Create(); |
| |
| // The style resolution required for fonts is not available in frame-less |
| // documents. |
| if (!canvas()->GetDocument().GetFrame()) |
| return metrics; |
| |
| canvas()->GetDocument().UpdateStyleAndLayoutTreeForNode(canvas()); |
| const Font& font = AccessFont(); |
| const SimpleFontData* font_data = font.PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return metrics; |
| |
| TextDirection direction; |
| if (GetState().GetDirection() == |
| CanvasRenderingContext2DState::kDirectionInherit) |
| direction = DetermineDirectionality(text); |
| else |
| direction = ToTextDirection(GetState().GetDirection(), canvas()); |
| TextRun text_run( |
| text, 0, 0, |
| TextRun::kAllowTrailingExpansion | TextRun::kForbidLeadingExpansion, |
| direction, false); |
| text_run.SetNormalizeSpace(true); |
| FloatRect text_bounds = font.SelectionRectForText( |
| text_run, FloatPoint(), font.GetFontDescription().ComputedSize(), 0, -1, |
| true); |
| |
| // x direction |
| metrics->SetWidth(font.Width(text_run)); |
| metrics->SetActualBoundingBoxLeft(-text_bounds.X()); |
| metrics->SetActualBoundingBoxRight(text_bounds.MaxX()); |
| |
| // y direction |
| const FontMetrics& font_metrics = font_data->GetFontMetrics(); |
| const float ascent = font_metrics.FloatAscent(); |
| const float descent = font_metrics.FloatDescent(); |
| const float baseline_y = GetFontBaseline(font_metrics); |
| |
| metrics->SetFontBoundingBoxAscent(ascent - baseline_y); |
| metrics->SetFontBoundingBoxDescent(descent + baseline_y); |
| metrics->SetActualBoundingBoxAscent(-text_bounds.Y() - baseline_y); |
| metrics->SetActualBoundingBoxDescent(text_bounds.MaxY() + baseline_y); |
| |
| // Note : top/bottom and ascend/descend are currently the same, so there's no |
| // difference between the EM box's top and bottom and the font's ascend and |
| // descend |
| metrics->SetEmHeightAscent(0); |
| metrics->SetEmHeightDescent(0); |
| |
| metrics->SetHangingBaseline(0.8f * ascent - baseline_y); |
| metrics->SetAlphabeticBaseline(-baseline_y); |
| metrics->SetIdeographicBaseline(-descent - baseline_y); |
| return metrics; |
| } |
| |
| void CanvasRenderingContext2D::DrawTextInternal( |
| const String& text, |
| double x, |
| double y, |
| CanvasRenderingContext2DState::PaintType paint_type, |
| double* max_width) { |
| // The style resolution required for fonts is not available in frame-less |
| // documents. |
| if (!canvas()->GetDocument().GetFrame()) |
| return; |
| |
| // accessFont needs the style to be up to date, but updating style can cause |
| // script to run, (e.g. due to autofocus) which can free the canvas (set size |
| // to 0, for example), so update style before grabbing the drawingCanvas. |
| canvas()->GetDocument().UpdateStyleAndLayoutTreeForNode(canvas()); |
| |
| PaintCanvas* c = DrawingCanvas(); |
| if (!c) |
| return; |
| |
| if (!std::isfinite(x) || !std::isfinite(y)) |
| return; |
| if (max_width && (!std::isfinite(*max_width) || *max_width <= 0)) |
| return; |
| |
| // Currently, SkPictureImageFilter does not support subpixel text |
| // anti-aliasing, which is expected when !creationAttributes().alpha(), so we |
| // need to fall out of display list mode when drawing text to an opaque |
| // canvas. crbug.com/583809 |
| if (!IsComposited()) { |
| canvas()->DisableDeferral( |
| kDisableDeferralReasonSubPixelTextAntiAliasingSupport); |
| } |
| |
| 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. |
| |
| const ComputedStyle* computed_style = 0; |
| TextDirection direction = |
| ToTextDirection(GetState().GetDirection(), canvas(), &computed_style); |
| bool is_rtl = direction == TextDirection::kRtl; |
| bool override = |
| computed_style ? IsOverride(computed_style->GetUnicodeBidi()) : false; |
| |
| TextRun text_run(text, 0, 0, TextRun::kAllowTrailingExpansion, direction, |
| override); |
| text_run.SetNormalizeSpace(true); |
| // Draw the item text at the correct point. |
| FloatPoint location(x, y + GetFontBaseline(font_metrics)); |
| 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; |
| } |
| |
| // The slop built in to this mask rect matches the heuristic used in |
| // FontCGWin.cpp for GDI text. |
| TextRunPaintInfo text_run_paint_info(text_run); |
| text_run_paint_info.bounds = |
| FloatRect(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(text_run_paint_info.bounds); |
| |
| CanvasRenderingContext2DAutoRestoreSkCanvas state_restorer(this); |
| if (use_max_width) { |
| DrawingCanvas()->save(); |
| DrawingCanvas()->translate(location.X(), location.Y()); |
| // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) |
| // still work. |
| DrawingCanvas()->scale((font_width > 0 ? (width / font_width) : 0), 1); |
| location = FloatPoint(); |
| } |
| |
| Draw( |
| [&font, &text_run_paint_info, &location]( |
| PaintCanvas* c, const PaintFlags* flags) // draw lambda |
| { |
| font.DrawBidiText(c, text_run_paint_info, location, |
| Font::kUseFallbackIfFontNotReady, kCDeviceScaleFactor, |
| *flags); |
| }, |
| [](const SkIRect& rect) // overdraw test lambda |
| { return false; }, |
| text_run_paint_info.bounds, paint_type); |
| } |
| |
| const Font& CanvasRenderingContext2D::AccessFont() { |
| if (!GetState().HasRealizedFont()) |
| setFont(GetState().UnparsedFont()); |
| canvas()->GetDocument().GetCanvasFontCache()->WillUseCurrentFont(); |
| return GetState().GetFont(); |
| } |
| |
| float CanvasRenderingContext2D::GetFontBaseline( |
| const FontMetrics& font_metrics) const { |
| // If the font is so tiny that the lroundf operations result in two |
| // different types of text baselines to return the same baseline, use |
| // floating point metrics (crbug.com/338908). |
| // If you changed the heuristic here, for consistency please also change it |
| // in SimpleFontData::platformInit(). |
| bool use_float_ascent_descent = |
| font_metrics.Ascent() < 3 || font_metrics.Height() < 2; |
| switch (GetState().GetTextBaseline()) { |
| case kTopTextBaseline: |
| return use_float_ascent_descent ? font_metrics.FloatAscent() |
| : font_metrics.Ascent(); |
| case kHangingTextBaseline: |
| // According to |
| // http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling |
| // "FOP (Formatting Objects Processor) puts the hanging baseline at 80% of |
| // the ascender height" |
| return use_float_ascent_descent ? (font_metrics.FloatAscent() * 4.0) / 5.0 |
| : (font_metrics.Ascent() * 4) / 5; |
| case kBottomTextBaseline: |
| case kIdeographicTextBaseline: |
| return use_float_ascent_descent ? -font_metrics.FloatDescent() |
| : -font_metrics.Descent(); |
| case kMiddleTextBaseline: |
| return use_float_ascent_descent |
| ? -font_metrics.FloatDescent() + |
| font_metrics.FloatHeight() / 2.0 |
| : -font_metrics.Descent() + font_metrics.Height() / 2; |
| case kAlphabeticTextBaseline: |
| default: |
| // Do nothing. |
| break; |
| } |
| return 0; |
| } |
| |
| void CanvasRenderingContext2D::SetIsHidden(bool hidden) { |
| if (HasImageBuffer()) |
| GetImageBuffer()->SetIsHidden(hidden); |
| if (hidden) { |
| PruneLocalFontCache(0); |
| } |
| } |
| |
| bool CanvasRenderingContext2D::IsTransformInvertible() const { |
| return GetState().IsTransformInvertible(); |
| } |
| |
| WebLayer* CanvasRenderingContext2D::PlatformLayer() const { |
| return GetImageBuffer() ? GetImageBuffer()->PlatformLayer() : 0; |
| } |
| |
| void CanvasRenderingContext2D::getContextAttributes( |
| CanvasRenderingContext2DSettings& settings) const { |
| settings.setAlpha(CreationAttributes().alpha()); |
| settings.setColorSpace(ColorSpaceAsString()); |
| settings.setPixelFormat(PixelFormatAsString()); |
| settings.setLinearPixelMath(color_params().LinearPixelMath()); |
| } |
| |
| void CanvasRenderingContext2D::drawFocusIfNeeded(Element* element) { |
| DrawFocusIfNeededInternal(path_, element); |
| } |
| |
| void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D* path2d, |
| Element* element) { |
| DrawFocusIfNeededInternal(path2d->GetPath(), element); |
| } |
| |
| void CanvasRenderingContext2D::DrawFocusIfNeededInternal(const Path& path, |
| Element* element) { |
| if (!FocusRingCallIsValid(path, element)) |
| return; |
| |
| // Note: we need to check document->focusedElement() rather than just calling |
| // element->focused(), because element->focused() isn't updated until after |
| // focus events fire. |
| if (element->GetDocument().FocusedElement() == element) { |
| ScrollPathIntoViewInternal(path); |
| DrawFocusRing(path); |
| } |
| |
| // Update its accessible bounds whether it's focused or not. |
| UpdateElementAccessibility(path, element); |
| } |
| |
| bool CanvasRenderingContext2D::FocusRingCallIsValid(const Path& path, |
| Element* element) { |
| DCHECK(element); |
| if (!GetState().IsTransformInvertible()) |
| return false; |
| if (path.IsEmpty()) |
| return false; |
| if (!element->IsDescendantOf(canvas())) |
| return false; |
| |
| return true; |
| } |
| |
| void CanvasRenderingContext2D::DrawFocusRing(const Path& path) { |
| usage_counters_.num_draw_focus_calls++; |
| if (!DrawingCanvas()) |
| return; |
| |
| SkColor color = LayoutTheme::GetTheme().FocusRingColor().Rgb(); |
| const int kFocusRingWidth = 5; |
| |
| DrawPlatformFocusRing(path.GetSkPath(), DrawingCanvas(), color, |
| kFocusRingWidth); |
| |
| // We need to add focusRingWidth to dirtyRect. |
| StrokeData stroke_data; |
| stroke_data.SetThickness(kFocusRingWidth); |
| |
| SkIRect dirty_rect; |
| if (!ComputeDirtyRect(path.StrokeBoundingRect(stroke_data), &dirty_rect)) |
| return; |
| |
| DidDraw(dirty_rect); |
| } |
| |
| void CanvasRenderingContext2D::UpdateElementAccessibility(const Path& path, |
| Element* element) { |
| element->GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| AXObjectCache* ax_object_cache = |
| element->GetDocument().ExistingAXObjectCache(); |
| LayoutBoxModelObject* lbmo = canvas()->GetLayoutBoxModelObject(); |
| LayoutObject* renderer = canvas()->GetLayoutObject(); |
| if (!ax_object_cache || !lbmo || !renderer) |
| return; |
| |
| // Get the transformed path. |
| Path transformed_path = path; |
| transformed_path.Transform(GetState().Transform()); |
| |
| // Add border and padding to the bounding rect. |
| LayoutRect element_rect = |
| EnclosingLayoutRect(transformed_path.BoundingRect()); |
| element_rect.Move(lbmo->BorderLeft() + lbmo->PaddingLeft(), |
| lbmo->BorderTop() + lbmo->PaddingTop()); |
| |
| // Update the accessible object. |
| ax_object_cache->SetCanvasObjectBounds(canvas(), element, element_rect); |
| } |
| |
| void CanvasRenderingContext2D::addHitRegion(const HitRegionOptions& options, |
| ExceptionState& exception_state) { |
| if (options.id().IsEmpty() && !options.control()) { |
| exception_state.ThrowDOMException(kNotSupportedError, |
| "Both id and control are null."); |
| return; |
| } |
| |
| if (options.control() && |
| !canvas()->IsSupportedInteractiveCanvasFallback(*options.control())) { |
| exception_state.ThrowDOMException(kNotSupportedError, |
| "The control is neither null nor a " |
| "supported interactive canvas fallback " |
| "element."); |
| return; |
| } |
| |
| Path hit_region_path = options.hasPath() ? options.path()->GetPath() : path_; |
| |
| PaintCanvas* c = DrawingCanvas(); |
| |
| if (hit_region_path.IsEmpty() || !c || !GetState().IsTransformInvertible() || |
| c->isClipEmpty()) { |
| exception_state.ThrowDOMException(kNotSupportedError, |
| "The specified path has no pixels."); |
| return; |
| } |
| |
| hit_region_path.Transform(GetState().Transform()); |
| |
| if (GetState().HasClip()) { |
| hit_region_path.IntersectPath(GetState().GetCurrentClipPath()); |
| if (hit_region_path.IsEmpty()) { |
| exception_state.ThrowDOMException(kNotSupportedError, |
| "The specified path has no pixels."); |
| } |
| } |
| |
| if (!hit_region_manager_) |
| hit_region_manager_ = HitRegionManager::Create(); |
| |
| // Remove previous region (with id or control) |
| hit_region_manager_->RemoveHitRegionById(options.id()); |
| hit_region_manager_->RemoveHitRegionByControl(options.control()); |
| |
| HitRegion* hit_region = HitRegion::Create(hit_region_path, options); |
| Element* element = hit_region->Control(); |
| if (element && element->IsDescendantOf(canvas())) |
| UpdateElementAccessibility(hit_region->GetPath(), hit_region->Control()); |
| hit_region_manager_->AddHitRegion(hit_region); |
| } |
| |
| void CanvasRenderingContext2D::removeHitRegion(const String& id) { |
| if (hit_region_manager_) |
| hit_region_manager_->RemoveHitRegionById(id); |
| } |
| |
| void CanvasRenderingContext2D::clearHitRegions() { |
| if (hit_region_manager_) |
| hit_region_manager_->RemoveAllHitRegions(); |
| } |
| |
| HitRegion* CanvasRenderingContext2D::HitRegionAtPoint(const FloatPoint& point) { |
| if (hit_region_manager_) |
| return hit_region_manager_->GetHitRegionAtPoint(point); |
| |
| return nullptr; |
| } |
| |
| unsigned CanvasRenderingContext2D::HitRegionsCount() const { |
| if (hit_region_manager_) |
| return hit_region_manager_->GetHitRegionsCount(); |
| |
| return 0; |
| } |
| |
| } // namespace blink |