| // 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 "core/paint/SVGShapePainter.h" |
| |
| #include "core/layout/svg/LayoutSVGResourceMarker.h" |
| #include "core/layout/svg/LayoutSVGShape.h" |
| #include "core/layout/svg/SVGLayoutSupport.h" |
| #include "core/layout/svg/SVGMarkerData.h" |
| #include "core/layout/svg/SVGResources.h" |
| #include "core/layout/svg/SVGResourcesCache.h" |
| #include "core/paint/FloatClipRecorder.h" |
| #include "core/paint/LayoutObjectDrawingRecorder.h" |
| #include "core/paint/ObjectPainter.h" |
| #include "core/paint/PaintInfo.h" |
| #include "core/paint/SVGContainerPainter.h" |
| #include "core/paint/SVGPaintContext.h" |
| #include "core/paint/TransformRecorder.h" |
| #include "platform/graphics/GraphicsContextStateSaver.h" |
| #include "platform/graphics/paint/SkPictureBuilder.h" |
| #include "third_party/skia/include/core/SkPaint.h" |
| #include "third_party/skia/include/core/SkPicture.h" |
| #include "wtf/Optional.h" |
| |
| namespace blink { |
| |
| static bool setupNonScalingStrokeContext(AffineTransform& strokeTransform, GraphicsContextStateSaver& stateSaver) |
| { |
| if (!strokeTransform.isInvertible()) |
| return false; |
| |
| stateSaver.save(); |
| stateSaver.context().concatCTM(strokeTransform.inverse()); |
| return true; |
| } |
| |
| static SkPath::FillType fillRuleFromStyle(const PaintInfo& paintInfo, const SVGComputedStyle& svgStyle) |
| { |
| return WebCoreWindRuleToSkFillType(paintInfo.isRenderingClipPathAsMaskImage() ? svgStyle.clipRule() : svgStyle.fillRule()); |
| } |
| |
| void SVGShapePainter::paint(const PaintInfo& paintInfo) |
| { |
| if (paintInfo.phase != PaintPhaseForeground |
| || m_layoutSVGShape.style()->visibility() != EVisibility::Visible |
| || m_layoutSVGShape.isShapeEmpty()) |
| return; |
| |
| FloatRect boundingBox = m_layoutSVGShape.paintInvalidationRectInLocalSVGCoordinates(); |
| if (!paintInfo.cullRect().intersectsCullRect(m_layoutSVGShape.localSVGTransform(), boundingBox)) |
| return; |
| |
| PaintInfo paintInfoBeforeFiltering(paintInfo); |
| // Shapes cannot have children so do not call updateCullRect. |
| SVGTransformContext transformContext(paintInfoBeforeFiltering.context, m_layoutSVGShape, m_layoutSVGShape.localSVGTransform()); |
| { |
| SVGPaintContext paintContext(m_layoutSVGShape, paintInfoBeforeFiltering); |
| if (paintContext.applyClipMaskAndFilterIfNecessary() && !LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(paintContext.paintInfo().context, m_layoutSVGShape, paintContext.paintInfo().phase)) { |
| LayoutObjectDrawingRecorder recorder(paintContext.paintInfo().context, m_layoutSVGShape, paintContext.paintInfo().phase, boundingBox); |
| const SVGComputedStyle& svgStyle = m_layoutSVGShape.style()->svgStyle(); |
| |
| bool shouldAntiAlias = svgStyle.shapeRendering() != SR_CRISPEDGES && svgStyle.shapeRendering() != SR_OPTIMIZESPEED; |
| |
| for (int i = 0; i < 3; i++) { |
| switch (svgStyle.paintOrderType(i)) { |
| case PT_FILL: { |
| SkPaint fillPaint; |
| if (!SVGPaintContext::paintForLayoutObject(paintContext.paintInfo(), m_layoutSVGShape.styleRef(), m_layoutSVGShape, ApplyToFillMode, fillPaint)) |
| break; |
| fillPaint.setAntiAlias(shouldAntiAlias); |
| fillShape(paintContext.paintInfo().context, fillPaint, fillRuleFromStyle(paintContext.paintInfo(), svgStyle)); |
| break; |
| } |
| case PT_STROKE: |
| if (svgStyle.hasVisibleStroke()) { |
| GraphicsContextStateSaver stateSaver(paintContext.paintInfo().context, false); |
| AffineTransform nonScalingTransform; |
| const AffineTransform* additionalPaintServerTransform = 0; |
| |
| if (m_layoutSVGShape.hasNonScalingStroke()) { |
| nonScalingTransform = m_layoutSVGShape.nonScalingStrokeTransform(); |
| if (!setupNonScalingStrokeContext(nonScalingTransform, stateSaver)) |
| return; |
| |
| // Non-scaling stroke needs to reset the transform back to the host transform. |
| additionalPaintServerTransform = &nonScalingTransform; |
| } |
| |
| SkPaint strokePaint; |
| if (!SVGPaintContext::paintForLayoutObject(paintContext.paintInfo(), m_layoutSVGShape.styleRef(), m_layoutSVGShape, ApplyToStrokeMode, strokePaint, additionalPaintServerTransform)) |
| break; |
| strokePaint.setAntiAlias(shouldAntiAlias); |
| |
| StrokeData strokeData; |
| SVGLayoutSupport::applyStrokeStyleToStrokeData(strokeData, m_layoutSVGShape.styleRef(), m_layoutSVGShape, m_layoutSVGShape.dashScaleFactor()); |
| strokeData.setupPaint(&strokePaint); |
| |
| strokeShape(paintContext.paintInfo().context, strokePaint); |
| } |
| break; |
| case PT_MARKERS: |
| paintMarkers(paintContext.paintInfo(), boundingBox); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| } |
| } |
| |
| if (m_layoutSVGShape.style()->outlineWidth()) { |
| PaintInfo outlinePaintInfo(paintInfoBeforeFiltering); |
| outlinePaintInfo.phase = PaintPhaseSelfOutlineOnly; |
| ObjectPainter(m_layoutSVGShape).paintOutline(outlinePaintInfo, LayoutPoint(boundingBox.location())); |
| } |
| } |
| |
| class PathWithTemporaryWindingRule { |
| public: |
| PathWithTemporaryWindingRule(Path& path, SkPath::FillType fillType) |
| : m_path(const_cast<SkPath&>(path.getSkPath())) |
| { |
| m_savedFillType = m_path.getFillType(); |
| m_path.setFillType(fillType); |
| } |
| ~PathWithTemporaryWindingRule() |
| { |
| m_path.setFillType(m_savedFillType); |
| } |
| |
| const SkPath& getSkPath() const { return m_path; } |
| |
| private: |
| SkPath& m_path; |
| SkPath::FillType m_savedFillType; |
| }; |
| |
| void SVGShapePainter::fillShape(GraphicsContext& context, const SkPaint& paint, SkPath::FillType fillType) |
| { |
| switch (m_layoutSVGShape.geometryCodePath()) { |
| case RectGeometryFastPath: |
| context.drawRect(m_layoutSVGShape.objectBoundingBox(), paint); |
| break; |
| case EllipseGeometryFastPath: |
| context.drawOval(m_layoutSVGShape.objectBoundingBox(), paint); |
| break; |
| default: { |
| PathWithTemporaryWindingRule pathWithWinding(m_layoutSVGShape.path(), fillType); |
| context.drawPath(pathWithWinding.getSkPath(), paint); |
| } |
| } |
| } |
| |
| void SVGShapePainter::strokeShape(GraphicsContext& context, const SkPaint& paint) |
| { |
| if (!m_layoutSVGShape.style()->svgStyle().hasVisibleStroke()) |
| return; |
| |
| switch (m_layoutSVGShape.geometryCodePath()) { |
| case RectGeometryFastPath: |
| context.drawRect(m_layoutSVGShape.objectBoundingBox(), paint); |
| break; |
| case EllipseGeometryFastPath: |
| context.drawOval(m_layoutSVGShape.objectBoundingBox(), paint); |
| break; |
| default: |
| ASSERT(m_layoutSVGShape.hasPath()); |
| Path* usePath = &m_layoutSVGShape.path(); |
| if (m_layoutSVGShape.hasNonScalingStroke()) |
| usePath = m_layoutSVGShape.nonScalingStrokePath(usePath, m_layoutSVGShape.nonScalingStrokeTransform()); |
| context.drawPath(usePath->getSkPath(), paint); |
| } |
| } |
| |
| void SVGShapePainter::paintMarkers(const PaintInfo& paintInfo, const FloatRect& boundingBox) |
| { |
| const Vector<MarkerPosition>* markerPositions = m_layoutSVGShape.markerPositions(); |
| if (!markerPositions || markerPositions->isEmpty()) |
| return; |
| |
| SVGResources* resources = SVGResourcesCache::cachedResourcesForLayoutObject(&m_layoutSVGShape); |
| if (!resources) |
| return; |
| |
| LayoutSVGResourceMarker* markerStart = resources->markerStart(); |
| LayoutSVGResourceMarker* markerMid = resources->markerMid(); |
| LayoutSVGResourceMarker* markerEnd = resources->markerEnd(); |
| if (!markerStart && !markerMid && !markerEnd) |
| return; |
| |
| float strokeWidth = m_layoutSVGShape.strokeWidth(); |
| |
| for (const MarkerPosition& markerPosition : *markerPositions) { |
| if (const LayoutSVGResourceMarker* marker = SVGMarkerData::markerForType(markerPosition.type, markerStart, markerMid, markerEnd)) { |
| SkPictureBuilder pictureBuilder(boundingBox, nullptr, &paintInfo.context); |
| PaintInfo markerPaintInfo(pictureBuilder.context(), paintInfo); |
| |
| // 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. |
| markerPaintInfo.m_cullRect.m_rect = LayoutRect::infiniteIntRect(); |
| |
| paintMarker(markerPaintInfo, *marker, markerPosition, strokeWidth); |
| pictureBuilder.endRecording()->playback(paintInfo.context.canvas()); |
| } |
| } |
| } |
| |
| void SVGShapePainter::paintMarker( |
| const PaintInfo& paintInfo, const LayoutSVGResourceMarker& marker, const MarkerPosition& position, float strokeWidth) |
| { |
| if (!marker.shouldPaint()) |
| return; |
| |
| TransformRecorder transformRecorder(paintInfo.context, marker, marker.markerTransformation(position.origin, position.angle, strokeWidth)); |
| Optional<FloatClipRecorder> clipRecorder; |
| if (SVGLayoutSupport::isOverflowHidden(&marker)) |
| clipRecorder.emplace(paintInfo.context, marker, paintInfo.phase, marker.viewport()); |
| SVGContainerPainter(marker).paint(paintInfo); |
| } |
| |
| } // namespace blink |