blob: fd425ee8b25688deb075380d796f84f5b4e46de2 [file] [log] [blame]
// Copyright 2014 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/core/paint/svg_shape_painter.h"
#include "base/optional.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_shape.h"
#include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
#include "third_party/blink/renderer/core/layout/svg/svg_marker_data.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/svg_container_painter.h"
#include "third_party/blink/renderer/core/paint/svg_model_object_painter.h"
#include "third_party/blink/renderer/core/paint/svg_paint_context.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
namespace blink {
static bool SetupNonScalingStrokeContext(
AffineTransform& stroke_transform,
GraphicsContextStateSaver& state_saver) {
if (!stroke_transform.IsInvertible())
return false;
state_saver.Save();
state_saver.Context().ConcatCTM(stroke_transform.Inverse());
return true;
}
static SkPath::FillType FillRuleFromStyle(const PaintInfo& paint_info,
const SVGComputedStyle& svg_style) {
return WebCoreWindRuleToSkFillType(paint_info.IsRenderingClipPathAsMaskImage()
? svg_style.ClipRule()
: svg_style.FillRule());
}
void SVGShapePainter::RecordHitTestData(const PaintInfo& paint_info) {
// Hit test display items are only needed for compositing. This flag is used
// for for printing and drag images which do not need hit testing.
if (paint_info.GetGlobalPaintFlags() & kGlobalPaintFlattenCompositingLayers)
return;
auto touch_action = layout_svg_shape_.EffectiveWhitelistedTouchAction();
if (touch_action == TouchAction::kTouchActionAuto)
return;
auto rect = LayoutRect(layout_svg_shape_.VisualRectInLocalSVGCoordinates());
HitTestData::RecordTouchActionRect(paint_info.context, layout_svg_shape_,
TouchActionRect(rect, touch_action));
}
void SVGShapePainter::Paint(const PaintInfo& paint_info) {
if (paint_info.phase != PaintPhase::kForeground ||
layout_svg_shape_.StyleRef().Visibility() != EVisibility::kVisible ||
layout_svg_shape_.IsShapeEmpty())
return;
PaintInfo paint_info_before_filtering(paint_info);
if (SVGModelObjectPainter(layout_svg_shape_)
.CullRectSkipsPainting(paint_info_before_filtering)) {
return;
}
// Shapes cannot have children so do not call UpdateCullRect.
SVGTransformContext transform_context(paint_info_before_filtering,
layout_svg_shape_,
layout_svg_shape_.LocalSVGTransform());
{
SVGPaintContext paint_context(layout_svg_shape_,
paint_info_before_filtering);
if (paint_context.ApplyClipMaskAndFilterIfNecessary() &&
!DrawingRecorder::UseCachedDrawingIfPossible(
paint_context.GetPaintInfo().context, layout_svg_shape_,
paint_context.GetPaintInfo().phase)) {
if (RuntimeEnabledFeatures::PaintTouchActionRectsEnabled())
RecordHitTestData(paint_info);
DrawingRecorder recorder(paint_context.GetPaintInfo().context,
layout_svg_shape_,
paint_context.GetPaintInfo().phase);
const SVGComputedStyle& svg_style =
layout_svg_shape_.StyleRef().SvgStyle();
bool should_anti_alias = svg_style.ShapeRendering() != SR_CRISPEDGES &&
svg_style.ShapeRendering() != SR_OPTIMIZESPEED;
for (int i = 0; i < 3; i++) {
switch (svg_style.PaintOrderType(i)) {
case PT_FILL: {
PaintFlags fill_flags;
if (!SVGPaintContext::PaintForLayoutObject(
paint_context.GetPaintInfo(), layout_svg_shape_.StyleRef(),
layout_svg_shape_, kApplyToFillMode, fill_flags))
break;
fill_flags.setAntiAlias(should_anti_alias);
FillShape(
paint_context.GetPaintInfo().context, fill_flags,
FillRuleFromStyle(paint_context.GetPaintInfo(), svg_style));
break;
}
case PT_STROKE:
if (svg_style.HasVisibleStroke()) {
GraphicsContextStateSaver state_saver(
paint_context.GetPaintInfo().context, false);
AffineTransform non_scaling_transform;
const AffineTransform* additional_paint_server_transform =
nullptr;
if (layout_svg_shape_.HasNonScalingStroke()) {
non_scaling_transform =
layout_svg_shape_.NonScalingStrokeTransform();
if (!SetupNonScalingStrokeContext(non_scaling_transform,
state_saver))
return;
// Non-scaling stroke needs to reset the transform back to the
// host transform.
additional_paint_server_transform = &non_scaling_transform;
}
PaintFlags stroke_flags;
if (!SVGPaintContext::PaintForLayoutObject(
paint_context.GetPaintInfo(),
layout_svg_shape_.StyleRef(), layout_svg_shape_,
kApplyToStrokeMode, stroke_flags,
additional_paint_server_transform))
break;
stroke_flags.setAntiAlias(should_anti_alias);
StrokeData stroke_data;
SVGLayoutSupport::ApplyStrokeStyleToStrokeData(
stroke_data, layout_svg_shape_.StyleRef(), layout_svg_shape_,
layout_svg_shape_.DashScaleFactor());
stroke_data.SetupPaint(&stroke_flags);
StrokeShape(paint_context.GetPaintInfo().context, stroke_flags);
}
break;
case PT_MARKERS:
PaintMarkers(paint_context.GetPaintInfo(),
layout_svg_shape_.VisualRectInLocalSVGCoordinates());
break;
default:
NOTREACHED();
break;
}
}
}
}
SVGModelObjectPainter(layout_svg_shape_)
.PaintOutline(paint_info_before_filtering);
}
class PathWithTemporaryWindingRule {
public:
PathWithTemporaryWindingRule(Path& path, SkPath::FillType fill_type)
: path_(const_cast<SkPath&>(path.GetSkPath())) {
saved_fill_type_ = path_.getFillType();
path_.setFillType(fill_type);
}
~PathWithTemporaryWindingRule() { path_.setFillType(saved_fill_type_); }
const SkPath& GetSkPath() const { return path_; }
private:
SkPath& path_;
SkPath::FillType saved_fill_type_;
};
void SVGShapePainter::FillShape(GraphicsContext& context,
const PaintFlags& flags,
SkPath::FillType fill_type) {
switch (layout_svg_shape_.GeometryCodePath()) {
case kRectGeometryFastPath:
context.DrawRect(layout_svg_shape_.ObjectBoundingBox(), flags);
break;
case kEllipseGeometryFastPath:
context.DrawOval(layout_svg_shape_.ObjectBoundingBox(), flags);
break;
default: {
PathWithTemporaryWindingRule path_with_winding(
layout_svg_shape_.GetPath(), fill_type);
context.DrawPath(path_with_winding.GetSkPath(), flags);
}
}
}
void SVGShapePainter::StrokeShape(GraphicsContext& context,
const PaintFlags& flags) {
if (!layout_svg_shape_.StyleRef().SvgStyle().HasVisibleStroke())
return;
switch (layout_svg_shape_.GeometryCodePath()) {
case kRectGeometryFastPath:
context.DrawRect(layout_svg_shape_.ObjectBoundingBox(), flags);
break;
case kEllipseGeometryFastPath:
context.DrawOval(layout_svg_shape_.ObjectBoundingBox(), flags);
break;
default:
DCHECK(layout_svg_shape_.HasPath());
const Path* use_path = &layout_svg_shape_.GetPath();
if (layout_svg_shape_.HasNonScalingStroke())
use_path = &layout_svg_shape_.NonScalingStrokePath();
context.DrawPath(use_path->GetSkPath(), flags);
}
}
void SVGShapePainter::PaintMarkers(const PaintInfo& paint_info,
const FloatRect& bounding_box) {
const Vector<MarkerPosition>* marker_positions =
layout_svg_shape_.MarkerPositions();
if (!marker_positions || marker_positions->IsEmpty())
return;
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(layout_svg_shape_);
if (!resources)
return;
LayoutSVGResourceMarker* marker_start = resources->MarkerStart();
LayoutSVGResourceMarker* marker_mid = resources->MarkerMid();
LayoutSVGResourceMarker* marker_end = resources->MarkerEnd();
if (!marker_start && !marker_mid && !marker_end)
return;
float stroke_width = layout_svg_shape_.StrokeWidth();
for (const MarkerPosition& marker_position : *marker_positions) {
if (const LayoutSVGResourceMarker* marker = SVGMarkerData::MarkerForType(
marker_position.type, marker_start, marker_mid, marker_end)) {
PaintMarker(paint_info, *marker, marker_position, stroke_width);
}
}
}
void SVGShapePainter::PaintMarker(const PaintInfo& paint_info,
const LayoutSVGResourceMarker& marker,
const MarkerPosition& position,
float stroke_width) {
if (!marker.ShouldPaint())
return;
AffineTransform transform = marker.MarkerTransformation(
position.origin, position.angle, stroke_width);
cc::PaintCanvas* canvas = paint_info.context.Canvas();
canvas->save();
canvas->concat(AffineTransformToSkMatrix(transform));
if (SVGLayoutSupport::IsOverflowHidden(marker))
canvas->clipRect(marker.Viewport());
PaintRecordBuilder builder(nullptr, &paint_info.context);
PaintInfo marker_paint_info(builder.Context(), paint_info);
// It's expensive to track the transformed paint cull rect for each
// marker so just disable culling. The shape paint call will already
// be culled if it is outside the paint info cull rect.
marker_paint_info.cull_rect_ = CullRect(LayoutRect::InfiniteIntRect());
SVGContainerPainter(marker).Paint(marker_paint_info);
builder.EndRecording(*canvas);
canvas->restore();
}
} // namespace blink