| /* |
| * Copyright (C) 2016-2019 Apple Inc. 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 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 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 "config.h" |
| #include "Path.h" |
| |
| #if USE(DIRECT2D) |
| |
| #include "AffineTransform.h" |
| #include "COMPtr.h" |
| #include "FloatRect.h" |
| #include "GraphicsContext.h" |
| #include "IntRect.h" |
| #include "NotImplemented.h" |
| #include "PlatformContextDirect2D.h" |
| #include "StrokeStyleApplier.h" |
| #include <d2d1.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/RetainPtr.h> |
| #include <wtf/text/WTFString.h> |
| |
| namespace WebCore { |
| |
| static inline ID2D1RenderTarget* scratchRenderTarget() |
| { |
| static COMPtr<ID2D1RenderTarget> renderTarget = adoptCOM(GraphicsContext::defaultRenderTarget()); |
| return renderTarget.get(); |
| } |
| |
| Path Path::polygonPathFromPoints(const Vector<FloatPoint>& points) |
| { |
| Path path; |
| if (points.size() < 2) |
| return path; |
| |
| Vector<D2D1_POINT_2F, 32> d2dPoints; |
| d2dPoints.reserveInitialCapacity(points.size() - 1); |
| for (auto point : points) |
| d2dPoints.uncheckedAppend(point); |
| |
| path.moveTo(points.first()); |
| |
| ASSERT(path.m_activePath); |
| |
| path.m_activePath->AddLines(d2dPoints.data(), d2dPoints.size()); |
| path.closeSubpath(); |
| |
| return path; |
| } |
| |
| Path::Path() = default; |
| |
| Path::~Path() |
| { |
| clearGeometries(); |
| } |
| |
| PlatformPathPtr Path::ensurePlatformPath() |
| { |
| if (!m_path) { |
| HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(D2D1_FILL_MODE_WINDING, nullptr, 0, &m_path); |
| ASSERT(SUCCEEDED(hr)); |
| if (FAILED(hr)) |
| return nullptr; |
| ASSERT(refCount(m_path.get()) == 1); |
| } |
| |
| return m_path.get(); |
| } |
| |
| void Path::clearGeometries() |
| { |
| // Manually clean these up so that we can use this vector with Direct2D APIs. |
| for (auto* geometry : m_geometries) |
| geometry->Release(); |
| m_geometries.clear(); |
| } |
| |
| void Path::appendGeometry(ID2D1Geometry* geometry) |
| { |
| #if ASSERT_ENABLED |
| unsigned before = refCount(geometry); |
| #endif |
| |
| unsigned geometryCount = m_path ? m_path->GetSourceGeometryCount() : 0; |
| |
| RELEASE_ASSERT(m_geometries.size() == geometryCount); |
| |
| geometry->AddRef(); |
| m_geometries.append(geometry); |
| |
| auto fillMode = m_path ? m_path->GetFillMode() : D2D1_FILL_MODE_WINDING; |
| |
| m_path = nullptr; |
| |
| HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(fillMode, m_geometries.data(), m_geometries.size(), &m_path); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| |
| ASSERT(refCount(geometry) == before + 1); |
| } |
| |
| void Path::createGeometryWithFillMode(WindRule webkitFillMode, COMPtr<ID2D1GeometryGroup>& path) const |
| { |
| RELEASE_ASSERT(m_path); |
| |
| auto fillMode = (webkitFillMode == WindRule::EvenOdd) ? D2D1_FILL_MODE_ALTERNATE : D2D1_FILL_MODE_WINDING; |
| |
| if (fillMode == m_path->GetFillMode()) { |
| path = m_path; |
| return; |
| } |
| |
| unsigned geometryCount = m_path->GetSourceGeometryCount(); |
| |
| ASSERT(m_geometries.size() == geometryCount); |
| |
| HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(fillMode, const_cast<ID2D1Geometry**>(m_geometries.data()), m_geometries.size(), &path); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| ASSERT(refCount(path.get()) == 1); |
| } |
| |
| Path::Path(const Path& other) |
| { |
| #if ASSERT_ENABLED |
| unsigned pathCount = refCount(other.m_path.get()); |
| unsigned activePathCount = refCount(other.m_activePath.get()); |
| #endif |
| m_path = other.m_path; |
| m_activePath = other.m_activePath; |
| m_geometries = other.m_geometries; |
| for (auto* geometry : m_geometries) |
| geometry->AddRef(); |
| |
| ASSERT(!m_path || refCount(m_path.get()) == pathCount + 1); |
| ASSERT(!m_activePath || refCount(m_activePath.get()) == activePathCount + 1); |
| } |
| |
| Path::Path(Path&& other) |
| { |
| #if ASSERT_ENABLED |
| unsigned pathCount = refCount(other.m_path.get()); |
| unsigned activePathCount = refCount(other.m_activePath.get()); |
| #endif |
| m_path = WTFMove(other.m_path); |
| m_activePath = WTFMove(other.m_activePath); |
| m_geometries = WTFMove(other.m_geometries); |
| |
| ASSERT(refCount(m_path.get()) == pathCount); |
| ASSERT(refCount(m_activePath.get()) == activePathCount); |
| } |
| |
| Path& Path::operator=(const Path& other) |
| { |
| if (this == &other) |
| return *this; |
| #if ASSERT_ENABLED |
| unsigned pathCount = refCount(other.m_path.get()); |
| unsigned activePathCount = refCount(other.m_activePath.get()); |
| #endif |
| m_path = other.m_path; |
| m_activePath = other.m_activePath; |
| m_geometries = other.m_geometries; |
| for (auto* geometry : m_geometries) |
| geometry->AddRef(); |
| |
| ASSERT(!m_path || refCount(m_path.get()) == pathCount + 1); |
| ASSERT(!m_activePath || refCount(m_activePath.get()) == activePathCount + 1); |
| |
| return *this; |
| } |
| |
| Path& Path::operator=(Path&& other) |
| { |
| if (this == &other) |
| return *this; |
| |
| #if ASSERT_ENABLED |
| unsigned pathCount = refCount(other.m_path.get()); |
| unsigned activePathCount = refCount(other.m_activePath.get()); |
| #endif |
| m_path = WTFMove(other.m_path); |
| m_activePath = WTFMove(other.m_activePath); |
| m_geometries = WTFMove(other.m_geometries); |
| |
| ASSERT(refCount(m_path.get()) == pathCount); |
| ASSERT(refCount(m_activePath.get()) == activePathCount); |
| |
| return *this; |
| } |
| |
| bool Path::contains(const FloatPoint& point, WindRule rule) const |
| { |
| if (isNull()) |
| return false; |
| |
| if (!fastBoundingRect().contains(point)) |
| return false; |
| |
| BOOL contains; |
| if (!SUCCEEDED(m_path->FillContainsPoint(point, nullptr, &contains))) |
| return false; |
| |
| return contains; |
| } |
| |
| bool Path::strokeContains(StrokeStyleApplier& applier, const FloatPoint& point) const |
| { |
| if (isNull()) |
| return false; |
| |
| PlatformContextDirect2D scratchContextD2D(scratchRenderTarget()); |
| GraphicsContext scratchContext(&scratchContextD2D, GraphicsContext::BitmapRenderingContextType::GPUMemory); |
| applier.strokeStyle(&scratchContext); |
| |
| #if ASSERT_ENABLED |
| unsigned before = refCount(m_path.get()); |
| #endif |
| |
| BOOL containsPoint = false; |
| HRESULT hr = m_path->StrokeContainsPoint(point, scratchContext.strokeThickness(), scratchContext.platformStrokeStyle(), nullptr, 1.0f, &containsPoint); |
| if (!SUCCEEDED(hr)) |
| return false; |
| |
| ASSERT(refCount(m_path.get()) == before); |
| return containsPoint; |
| } |
| |
| void Path::closeAnyOpenGeometries(unsigned figureEndStyle) const |
| { |
| if (m_figureIsOpened) { |
| ASSERT(m_activePath); |
| |
| auto endStyle = static_cast<D2D1_FIGURE_END>(figureEndStyle); |
| m_activePath->EndFigure(endStyle); |
| m_figureIsOpened = false; |
| |
| HRESULT hr = m_activePath->Close(); |
| ASSERT(SUCCEEDED(hr)); |
| } |
| |
| m_activePath = nullptr; |
| } |
| |
| void Path::translate(const FloatSize& size) |
| { |
| transform(AffineTransform(1, 0, 0, 1, size.width(), size.height())); |
| } |
| |
| void Path::transform(const AffineTransform& transform) |
| { |
| if (transform.isIdentity() || isEmpty()) |
| return; |
| |
| if (m_activePath) |
| closeAnyOpenGeometries(D2D1_FIGURE_END_OPEN); |
| |
| #if ASSERT_ENABLED |
| unsigned before = refCount(m_path.get()); |
| #endif |
| |
| COMPtr<ID2D1TransformedGeometry> transformedPath; |
| if (!SUCCEEDED(GraphicsContext::systemFactory()->CreateTransformedGeometry(m_path.get(), transform, &transformedPath))) |
| return; |
| |
| ASSERT(refCount(m_path.get()) == before); |
| ASSERT(refCount(transformedPath.get()) == 1); |
| |
| auto fillMode = m_path->GetFillMode(); |
| |
| m_path = nullptr; |
| |
| clearGeometries(); |
| |
| transformedPath->AddRef(); |
| m_geometries.append(transformedPath.get()); |
| |
| HRESULT hr = GraphicsContext::systemFactory()->CreateGeometryGroup(fillMode, m_geometries.data(), m_geometries.size(), &m_path); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| ASSERT(refCount(m_path.get()) == 1); |
| } |
| |
| FloatRect Path::boundingRectSlowCase() const |
| { |
| D2D1_RECT_F bounds = { }; |
| if (!SUCCEEDED(m_path->GetBounds(nullptr, &bounds))) |
| return FloatRect(); |
| |
| return bounds; |
| } |
| |
| FloatRect Path::fastBoundingRectSlowCase() const |
| { |
| D2D1_RECT_F bounds = { }; |
| if (!SUCCEEDED(m_path->GetBounds(nullptr, &bounds))) |
| return FloatRect(); |
| |
| return bounds; |
| } |
| |
| FloatRect Path::fastBoundingRectForStroke(const PlatformContextDirect2D& platformContext) const |
| { |
| if (isNull()) |
| return FloatRect(); |
| |
| float tolerance = 10; |
| |
| auto* strokeStyle = const_cast<PlatformContextDirect2D&>(platformContext).strokeStyle(); |
| |
| D2D1_RECT_F bounds = { }; |
| if (!SUCCEEDED(m_path->GetWidenedBounds(platformContext.strokeThickness(), strokeStyle, nullptr, tolerance, &bounds))) |
| return FloatRect(); |
| |
| return bounds; |
| } |
| |
| FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const |
| { |
| if (isNull()) |
| return FloatRect(); |
| |
| PlatformContextDirect2D scratchContextD2D(scratchRenderTarget()); |
| GraphicsContext scratchContext(&scratchContextD2D, GraphicsContext::BitmapRenderingContextType::GPUMemory); |
| |
| if (applier) |
| applier->strokeStyle(&scratchContext); |
| |
| return fastBoundingRectForStroke(scratchContextD2D); |
| } |
| |
| void Path::openFigureAtCurrentPointIfNecessary() |
| { |
| if (m_figureIsOpened) |
| return; |
| |
| if (!m_activePath) { |
| COMPtr<ID2D1PathGeometry> activePathGeometry; |
| GraphicsContext::systemFactory()->CreatePathGeometry(&activePathGeometry); |
| HRESULT hr = activePathGeometry->Open(&m_activePath); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| appendGeometry(activePathGeometry.leakRef()); |
| ASSERT(refCount(m_activePath.get()) == 1); |
| } |
| |
| m_activePath->SetFillMode(D2D1_FILL_MODE_WINDING); |
| m_activePath->BeginFigure(currentPoint(), D2D1_FIGURE_BEGIN_FILLED); |
| m_figureIsOpened = true; |
| } |
| |
| void Path::moveToSlowCase(const FloatPoint& point) |
| { |
| if (m_activePath) |
| closeAnyOpenGeometries(D2D1_FIGURE_END_OPEN); |
| |
| COMPtr<ID2D1PathGeometry> activePathGeometry; |
| GraphicsContext::systemFactory()->CreatePathGeometry(&activePathGeometry); |
| |
| if (!SUCCEEDED(activePathGeometry->Open(&m_activePath))) |
| return; |
| |
| appendGeometry(activePathGeometry.leakRef()); |
| |
| ASSERT(refCount(m_activePath.get()) == 1); |
| |
| m_activePath->SetFillMode(D2D1_FILL_MODE_WINDING); |
| m_activePath->BeginFigure(point, D2D1_FIGURE_BEGIN_FILLED); |
| m_figureIsOpened = true; |
| } |
| |
| void Path::addLineToSlowCase(const FloatPoint& point) |
| { |
| openFigureAtCurrentPointIfNecessary(); |
| m_activePath->AddLine(point); |
| } |
| |
| void Path::addQuadCurveToSlowCase(const FloatPoint& cp, const FloatPoint& p) |
| { |
| openFigureAtCurrentPointIfNecessary(); |
| m_activePath->AddQuadraticBezier(D2D1::QuadraticBezierSegment(cp, p)); |
| } |
| |
| void Path::addBezierCurveToSlowCase(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p) |
| { |
| openFigureAtCurrentPointIfNecessary(); |
| m_activePath->AddBezier(D2D1::BezierSegment(cp1, cp2, p)); |
| } |
| |
| void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) |
| { |
| FloatPoint p0 = currentPoint(); |
| |
| if (p1 == p0 || p1 == p2 || WTF::areEssentiallyEqual(radius, 0.0f)) |
| return addLineTo(p1); |
| |
| float direction = (p2.x() - p1.x()) * (p0.y() - p1.y()) + (p2.y() - p1.y()) * (p1.x() - p0.x()); |
| if (WTF::areEssentiallyEqual(direction, 0.0f)) |
| return addLineTo(p1); |
| |
| auto a2 = toFloatPoint(p0 - p1).lengthSquared(); |
| auto b2 = toFloatPoint(p1 - p2).lengthSquared(); |
| auto c2 = toFloatPoint(p0 - p2).lengthSquared(); |
| |
| double cosx = (a2 + b2 - c2) / (2.0 * std::sqrt(a2 * b2)); |
| double sinx = std::sqrt(1.0 - cosx * cosx); |
| double d = radius / ((1 - cosx) / sinx); |
| |
| auto an = toFloatPoint(p1 - p0).scaled(1.0 / std::sqrt(a2)); |
| auto bn = toFloatPoint(p1 - p2).scaled(1.0 / std::sqrt(b2)); |
| |
| auto startPoint = toFloatPoint(p1 - an.scaled(d)); |
| auto p4 = toFloatPoint(p1 - bn.scaled(d)); |
| |
| bool anticlockwise = (direction < 0); |
| an.scale(radius * (anticlockwise ? 1 : -1)); |
| |
| FloatPoint center(startPoint.x() + an.y(), startPoint.y() - an.x()); |
| |
| double angle0 = atan2(startPoint.y() - center.y(), startPoint.x() - center.x()); |
| double angle1 = atan2(p4.y() - center.y(), p4.x() - center.x()); |
| |
| openFigureAtCurrentPointIfNecessary(); |
| addLineTo(startPoint); |
| addArc(center, radius, angle0, angle1, anticlockwise); |
| } |
| |
| static bool equalRadiusWidths(const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) |
| { |
| return topLeftRadius.width() == topRightRadius.width() |
| && topRightRadius.width() == bottomLeftRadius.width() |
| && bottomLeftRadius.width() == bottomRightRadius.width(); |
| } |
| |
| static bool equalRadiusHeights(const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) |
| { |
| return topLeftRadius.height() == bottomLeftRadius.height() |
| && bottomLeftRadius.height() == topRightRadius.height() |
| && topRightRadius.height() == bottomRightRadius.height(); |
| } |
| |
| void Path::platformAddPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius) |
| { |
| bool equalWidths = equalRadiusWidths(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| bool equalHeights = equalRadiusHeights(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| |
| if (equalWidths && equalHeights) { |
| // Ensure that CG can render the rounded rect. |
| float radiusWidth = topLeftRadius.width(); |
| float radiusHeight = topLeftRadius.height(); |
| auto rectToDraw = D2D1::RectF(rect.x(), rect.y(), rect.maxX(), rect.maxY()); |
| |
| COMPtr<ID2D1RoundedRectangleGeometry> roundRect; |
| HRESULT hr = GraphicsContext::systemFactory()->CreateRoundedRectangleGeometry(D2D1::RoundedRect(rectToDraw, radiusWidth, radiusHeight), &roundRect); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| appendGeometry(roundRect.get()); |
| ASSERT(refCount(roundRect.get()) == 2); |
| return; |
| } |
| |
| addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius); |
| } |
| |
| void Path::closeSubpath() |
| { |
| if (isNull()) { |
| m_activePath = nullptr; |
| return; |
| } |
| |
| if (!m_activePath) { |
| ASSERT(!m_figureIsOpened); |
| return; |
| } |
| |
| closeAnyOpenGeometries(D2D1_FIGURE_END_CLOSED); |
| } |
| |
| static FloatPoint arcStart(const FloatPoint& center, float radius, float startAngle) |
| { |
| FloatPoint startingPoint = center; |
| float startX = radius * std::cos(startAngle); |
| float startY = radius * std::sin(startAngle); |
| startingPoint.move(startX, startY); |
| return startingPoint; |
| } |
| |
| const float twoPi = 2.0f * piFloat; |
| |
| static void drawArcSection(ID2D1GeometrySink* sink, const FloatPoint& center, float radius, float startAngle, float endAngle, bool anticlockwise) |
| { |
| // Direct2D wants us to specify the end point of the arc, not the center. It will be drawn from |
| // whatever the current point in the 'm_activePath' is. |
| FloatPoint endPoint = center; |
| float endX = radius * std::cos(endAngle); |
| float endY = radius * std::sin(endAngle); |
| endPoint.move(endX, endY); |
| |
| D2D1_SWEEP_DIRECTION direction = anticlockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : D2D1_SWEEP_DIRECTION_CLOCKWISE; |
| sink->AddArc(D2D1::ArcSegment(endPoint, D2D1::SizeF(radius, radius), 0, direction, D2D1_ARC_SIZE_SMALL)); |
| } |
| |
| void Path::addArcSlowCase(const FloatPoint& center, float radius, float startAngle, float endAngle, bool anticlockwise) |
| { |
| auto arcStartPoint = arcStart(center, radius, startAngle); |
| if (!m_activePath) |
| moveTo(arcStartPoint); |
| else if (!areEssentiallyEqual(currentPoint(), arcStartPoint)) { |
| // If the arc defined by the center and radius does not intersect the current position, |
| // we need to draw a line from the current position to the starting point of the arc. |
| addLineTo(arcStartPoint); |
| } |
| |
| if (WTF::areEssentiallyEqual(std::abs(endAngle - startAngle), twoPi)) |
| return addEllipse(FloatRect(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius)); |
| |
| if (anticlockwise) { |
| if (endAngle > startAngle) { |
| endAngle -= twoPi * std::ceil((endAngle - startAngle) / twoPi); |
| ASSERT(endAngle <= startAngle); |
| } |
| } else { |
| if (startAngle > endAngle) { |
| startAngle -= twoPi * std::ceil((startAngle - endAngle) / twoPi); |
| ASSERT(startAngle <= endAngle); |
| } |
| } |
| |
| const float delta = anticlockwise ? -piOverTwoFloat : piOverTwoFloat; |
| float remainingArcAngle = endAngle - startAngle; |
| |
| while ((remainingArcAngle > 0 && remainingArcAngle > delta) || (remainingArcAngle < 0 && remainingArcAngle < delta)) { |
| const double currentEndAngle = startAngle + delta; |
| drawArcSection(m_activePath.get(), center, radius, startAngle, currentEndAngle, anticlockwise); |
| startAngle = currentEndAngle; |
| remainingArcAngle -= delta; |
| } |
| |
| // Handle any remaining part of the arc: |
| if (std::abs(remainingArcAngle) > 1e-6) |
| drawArcSection(m_activePath.get(), center, radius, startAngle, startAngle + remainingArcAngle, anticlockwise); |
| } |
| |
| void Path::addRect(const FloatRect& r) |
| { |
| if (!m_activePath) |
| moveTo(r.location()); |
| |
| COMPtr<ID2D1RectangleGeometry> rectangle; |
| HRESULT hr = GraphicsContext::systemFactory()->CreateRectangleGeometry(r, &rectangle); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| appendGeometry(rectangle.get()); |
| ASSERT(refCount(rectangle.get()) == 2); |
| } |
| |
| void Path::addEllipse(FloatPoint p, float radiusX, float radiusY, float rotation, float startAngle, float endAngle, bool anticlockwise) |
| { |
| AffineTransform transform; |
| transform.translate(p.x(), p.y()).rotate(rad2deg(rotation)).scale(radiusX, radiusY); |
| |
| notImplemented(); |
| } |
| |
| void Path::addEllipse(const FloatRect& r) |
| { |
| if (!m_activePath) |
| moveTo(r.location()); |
| |
| COMPtr<ID2D1EllipseGeometry> ellipse; |
| // Note: The radii of the ellipse contained within a rectange are half the width and height of the rect. |
| HRESULT hr = GraphicsContext::systemFactory()->CreateEllipseGeometry(D2D1::Ellipse(r.center(), 0.5 * r.width(), 0.5 * r.height()), &ellipse); |
| RELEASE_ASSERT(SUCCEEDED(hr)); |
| appendGeometry(ellipse.get()); |
| ASSERT(refCount(ellipse.get()) == 2); |
| } |
| |
| void Path::addPath(const Path& path, const AffineTransform& transform) |
| { |
| if (!path.platformPath()) |
| return; |
| |
| if (!transform.isInvertible()) |
| return; |
| |
| notImplemented(); |
| } |
| |
| |
| void Path::clear() |
| { |
| if (isNull()) |
| return; |
| |
| m_path = nullptr; |
| m_activePath = nullptr; |
| clearGeometries(); |
| } |
| |
| bool Path::isEmptySlowCase() const |
| { |
| if (!m_path->GetSourceGeometryCount()) |
| return true; |
| |
| ASSERT(m_path->GetSourceGeometryCount() == m_geometries.size()); |
| |
| return false; |
| } |
| |
| FloatPoint Path::currentPointSlowCase() const |
| { |
| float length = 0; |
| HRESULT hr = m_path->ComputeLength(nullptr, &length); |
| if (!SUCCEEDED(hr)) |
| return FloatPoint(); |
| |
| D2D1_POINT_2F point = { }; |
| D2D1_POINT_2F tangent = { }; |
| hr = m_path->ComputePointAtLength(length, nullptr, &point, &tangent); |
| if (!SUCCEEDED(hr)) |
| return FloatPoint(); |
| |
| return point; |
| } |
| |
| float Path::length() const |
| { |
| float length = 0; |
| HRESULT hr = m_path->ComputeLength(nullptr, &length); |
| if (!SUCCEEDED(hr)) |
| return 0; |
| |
| return length; |
| } |
| |
| void Path::applySlowCase(const PathApplierFunction&) const |
| { |
| notImplemented(); |
| } |
| |
| bool Path::isNull() const |
| { |
| return !m_path; |
| } |
| |
| } |
| |
| #endif // USE(DIRECT2D) |