| // 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 "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/paint/paint_info.h" |
| #include "third_party/blink/renderer/core/paint/paint_timing.h" |
| #include "third_party/blink/renderer/core/paint/scoped_svg_paint_state.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_object_painter.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 base::Optional<AffineTransform> SetupNonScalingStrokeContext( |
| const LayoutSVGShape& layout_svg_shape, |
| GraphicsContextStateSaver& state_saver) { |
| const AffineTransform& non_scaling_stroke_transform = |
| layout_svg_shape.NonScalingStrokeTransform(); |
| if (!non_scaling_stroke_transform.IsInvertible()) |
| return base::nullopt; |
| state_saver.Save(); |
| state_saver.Context().ConcatCTM(non_scaling_stroke_transform.Inverse()); |
| return non_scaling_stroke_transform; |
| } |
| |
| static SkPathFillType FillRuleFromStyle(const PaintInfo& paint_info, |
| const ComputedStyle& style) { |
| return WebCoreWindRuleToSkFillType(paint_info.IsRenderingClipPathAsMaskImage() |
| ? style.ClipRule() |
| : style.FillRule()); |
| } |
| |
| 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; |
| |
| if (SVGModelObjectPainter::CanUseCullRect(layout_svg_shape_.StyleRef())) { |
| if (!paint_info.GetCullRect().IntersectsTransformed( |
| layout_svg_shape_.LocalSVGTransform(), |
| layout_svg_shape_.VisualRectInLocalSVGCoordinates())) |
| return; |
| } |
| // Shapes cannot have children so do not call TransformCullRect. |
| |
| ScopedSVGTransformState transform_state(paint_info, layout_svg_shape_); |
| { |
| ScopedSVGPaintState paint_state(layout_svg_shape_, paint_info); |
| SVGModelObjectPainter::RecordHitTestData(layout_svg_shape_, paint_info); |
| if (!DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, layout_svg_shape_, paint_info.phase)) { |
| SVGDrawingRecorder recorder(paint_info.context, layout_svg_shape_, |
| paint_info.phase); |
| const ComputedStyle& style = layout_svg_shape_.StyleRef(); |
| |
| bool should_anti_alias = |
| style.ShapeRendering() != EShapeRendering::kCrispedges && |
| style.ShapeRendering() != EShapeRendering::kOptimizespeed; |
| |
| for (int i = 0; i < 3; i++) { |
| switch (style.PaintOrderType(i)) { |
| case PT_FILL: { |
| PaintFlags fill_flags; |
| if (!SVGObjectPainter(layout_svg_shape_) |
| .PreparePaint(paint_info, style, kApplyToFillMode, |
| fill_flags)) |
| break; |
| fill_flags.setAntiAlias(should_anti_alias); |
| FillShape(paint_info.context, fill_flags, |
| FillRuleFromStyle(paint_info, style)); |
| break; |
| } |
| case PT_STROKE: |
| if (style.HasVisibleStroke()) { |
| GraphicsContextStateSaver state_saver(paint_info.context, false); |
| base::Optional<AffineTransform> non_scaling_transform; |
| |
| if (layout_svg_shape_.HasNonScalingStroke()) { |
| // Non-scaling stroke needs to reset the transform back to the |
| // host transform. |
| non_scaling_transform = SetupNonScalingStrokeContext( |
| layout_svg_shape_, state_saver); |
| if (!non_scaling_transform) |
| return; |
| } |
| |
| PaintFlags stroke_flags; |
| if (!SVGObjectPainter(layout_svg_shape_) |
| .PreparePaint( |
| paint_info, style, kApplyToStrokeMode, stroke_flags, |
| base::OptionalOrNullptr(non_scaling_transform))) |
| break; |
| stroke_flags.setAntiAlias(should_anti_alias); |
| |
| StrokeData stroke_data; |
| SVGLayoutSupport::ApplyStrokeStyleToStrokeData( |
| stroke_data, style, layout_svg_shape_, |
| layout_svg_shape_.DashScaleFactor()); |
| stroke_data.SetupPaint(&stroke_flags); |
| |
| StrokeShape(paint_info.context, stroke_flags); |
| } |
| break; |
| case PT_MARKERS: |
| PaintMarkers(paint_info); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| } |
| |
| SVGModelObjectPainter(layout_svg_shape_).PaintOutline(paint_info); |
| } |
| |
| class PathWithTemporaryWindingRule { |
| public: |
| PathWithTemporaryWindingRule(Path& path, SkPathFillType 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_; |
| SkPathFillType saved_fill_type_; |
| }; |
| |
| void SVGShapePainter::FillShape(GraphicsContext& context, |
| const PaintFlags& flags, |
| SkPathFillType fill_type) { |
| switch (layout_svg_shape_.GeometryCodePath()) { |
| case kRectGeometryFastPath: |
| context.DrawRect(layout_svg_shape_.ObjectBoundingBox(), flags, |
| DarkModeFilter::ElementRole::kSVG); |
| break; |
| case kEllipseGeometryFastPath: |
| context.DrawOval(layout_svg_shape_.ObjectBoundingBox(), flags, |
| DarkModeFilter::ElementRole::kSVG); |
| break; |
| default: { |
| PathWithTemporaryWindingRule path_with_winding( |
| layout_svg_shape_.GetPath(), fill_type); |
| context.DrawPath(path_with_winding.GetSkPath(), flags, |
| DarkModeFilter::ElementRole::kSVG); |
| } |
| } |
| PaintTiming& timing = PaintTiming::From( |
| layout_svg_shape_.GetElement()->GetDocument().TopDocument()); |
| timing.MarkFirstContentfulPaint(); |
| } |
| |
| void SVGShapePainter::StrokeShape(GraphicsContext& context, |
| const PaintFlags& flags) { |
| DCHECK(layout_svg_shape_.StyleRef().HasVisibleStroke()); |
| |
| switch (layout_svg_shape_.GeometryCodePath()) { |
| case kRectGeometryFastPath: |
| context.DrawRect(layout_svg_shape_.ObjectBoundingBox(), flags, |
| DarkModeFilter::ElementRole::kSVG); |
| break; |
| case kEllipseGeometryFastPath: |
| context.DrawOval(layout_svg_shape_.ObjectBoundingBox(), flags, |
| DarkModeFilter::ElementRole::kSVG); |
| 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, |
| DarkModeFilter::ElementRole::kSVG); |
| } |
| PaintTiming& timing = PaintTiming::From( |
| layout_svg_shape_.GetElement()->GetDocument().TopDocument()); |
| timing.MarkFirstContentfulPaint(); |
| } |
| |
| void SVGShapePainter::PaintMarkers(const PaintInfo& paint_info) { |
| const Vector<MarkerPosition>* marker_positions = |
| layout_svg_shape_.MarkerPositions(); |
| if (!marker_positions || marker_positions->IsEmpty()) |
| return; |
| SVGResourceClient* client = SVGResources::GetClient(layout_svg_shape_); |
| const ComputedStyle& style = layout_svg_shape_.StyleRef(); |
| auto* marker_start = GetSVGResourceAsType<LayoutSVGResourceMarker>( |
| *client, style.MarkerStartResource()); |
| auto* marker_mid = GetSVGResourceAsType<LayoutSVGResourceMarker>( |
| *client, style.MarkerMidResource()); |
| auto* marker_end = GetSVGResourceAsType<LayoutSVGResourceMarker>( |
| *client, style.MarkerEndResource()); |
| if (!marker_start && !marker_mid && !marker_end) |
| return; |
| |
| const float stroke_width = layout_svg_shape_.StrokeWidthForMarkerUnits(); |
| |
| for (const MarkerPosition& marker_position : *marker_positions) { |
| if (LayoutSVGResourceMarker* marker = marker_position.SelectMarker( |
| marker_start, marker_mid, marker_end)) { |
| PaintMarker(paint_info, *marker, marker_position, stroke_width); |
| } |
| } |
| } |
| |
| void SVGShapePainter::PaintMarker(const PaintInfo& paint_info, |
| LayoutSVGResourceMarker& marker, |
| const MarkerPosition& position, |
| float stroke_width) { |
| marker.ClearInvalidationMask(); |
| |
| if (!marker.ShouldPaint()) |
| return; |
| |
| AffineTransform transform = |
| marker.MarkerTransformation(position, 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(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.ApplyInfiniteCullRect(); |
| |
| SVGContainerPainter(marker).Paint(marker_paint_info); |
| builder.EndRecording(*canvas); |
| |
| canvas->restore(); |
| } |
| |
| } // namespace blink |