mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 07:13:20 +00:00
259d24796a
MozReview-Commit-ID: KBZSqIdx0OC
485 lines
13 KiB
C++
485 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "PathD2D.h"
|
|
#include "HelpersD2D.h"
|
|
#include <math.h>
|
|
#include "DrawTargetD2D1.h"
|
|
#include "Logging.h"
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows
|
|
// a geometry to be duplicated into a geometry sink, while removing the final
|
|
// figure end and thus allowing a figure that was implicitly closed to be
|
|
// continued.
|
|
class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink
|
|
{
|
|
public:
|
|
OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink)
|
|
: mSink(aSink)
|
|
, mNeedsFigureEnded(false)
|
|
{
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
|
|
{
|
|
if (!aPtr) {
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (aIID == IID_IUnknown) {
|
|
*aPtr = static_cast<IUnknown*>(this);
|
|
return S_OK;
|
|
} else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
|
|
*aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// We ignore SetFillMode, the copier will decide.
|
|
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
|
|
{ EnsureFigureEnded(); return; }
|
|
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
|
|
{ EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); }
|
|
STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
|
|
{ EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); }
|
|
STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
|
|
{ EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); }
|
|
STDMETHOD(Close)()
|
|
{ /* Should never be called! */ return S_OK; }
|
|
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
|
|
{ return mSink->SetSegmentFlags(aFlags); }
|
|
|
|
// This function is special - it's the reason this class exists.
|
|
// It needs to intercept the very last endfigure. So that a user can
|
|
// continue writing to this sink as if they never stopped.
|
|
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
|
|
{
|
|
if (aEnd == D2D1_FIGURE_END_CLOSED) {
|
|
return mSink->EndFigure(aEnd);
|
|
} else {
|
|
mNeedsFigureEnded = true;
|
|
}
|
|
}
|
|
private:
|
|
void EnsureFigureEnded()
|
|
{
|
|
if (mNeedsFigureEnded) {
|
|
mSink->EndFigure(D2D1_FIGURE_END_OPEN);
|
|
mNeedsFigureEnded = false;
|
|
}
|
|
}
|
|
|
|
ID2D1SimplifiedGeometrySink *mSink;
|
|
bool mNeedsFigureEnded;
|
|
};
|
|
|
|
class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink
|
|
{
|
|
public:
|
|
StreamingGeometrySink(PathSink *aSink)
|
|
: mSink(aSink)
|
|
{
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
|
|
{
|
|
if (!aPtr) {
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (aIID == IID_IUnknown) {
|
|
*aPtr = static_cast<IUnknown*>(this);
|
|
return S_OK;
|
|
} else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
|
|
*aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// We ignore SetFillMode, this depends on the destination sink.
|
|
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
|
|
{ return; }
|
|
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
|
|
{ mSink->MoveTo(ToPoint(aPoint)); }
|
|
STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
|
|
{ for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } }
|
|
STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
|
|
{
|
|
for (UINT i = 0; i < aCount; i++) {
|
|
mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3));
|
|
}
|
|
}
|
|
STDMETHOD(Close)()
|
|
{ /* Should never be called! */ return S_OK; }
|
|
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
|
|
{ /* Should never be called! */ }
|
|
|
|
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
|
|
{
|
|
if (aEnd == D2D1_FIGURE_END_CLOSED) {
|
|
return mSink->Close();
|
|
}
|
|
}
|
|
private:
|
|
|
|
PathSink *mSink;
|
|
};
|
|
|
|
PathBuilderD2D::~PathBuilderD2D()
|
|
{
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::MoveTo(const Point &aPoint)
|
|
{
|
|
if (mFigureActive) {
|
|
mSink->EndFigure(D2D1_FIGURE_END_OPEN);
|
|
mFigureActive = false;
|
|
}
|
|
EnsureActive(aPoint);
|
|
mCurrentPoint = aPoint;
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::LineTo(const Point &aPoint)
|
|
{
|
|
EnsureActive(aPoint);
|
|
mSink->AddLine(D2DPoint(aPoint));
|
|
|
|
mCurrentPoint = aPoint;
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::BezierTo(const Point &aCP1,
|
|
const Point &aCP2,
|
|
const Point &aCP3)
|
|
{
|
|
EnsureActive(aCP1);
|
|
mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1),
|
|
D2DPoint(aCP2),
|
|
D2DPoint(aCP3)));
|
|
|
|
mCurrentPoint = aCP3;
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::QuadraticBezierTo(const Point &aCP1,
|
|
const Point &aCP2)
|
|
{
|
|
EnsureActive(aCP1);
|
|
mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1),
|
|
D2DPoint(aCP2)));
|
|
|
|
mCurrentPoint = aCP2;
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::Close()
|
|
{
|
|
if (mFigureActive) {
|
|
mSink->EndFigure(D2D1_FIGURE_END_CLOSED);
|
|
|
|
mFigureActive = false;
|
|
|
|
EnsureActive(mBeginPoint);
|
|
}
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
|
|
Float aEndAngle, bool aAntiClockwise)
|
|
{
|
|
if (aAntiClockwise && aStartAngle < aEndAngle) {
|
|
// D2D does things a little differently, and draws the arc by specifying an
|
|
// beginning and an end point. This means the circle will be the wrong way
|
|
// around if the start angle is smaller than the end angle. It might seem
|
|
// tempting to invert aAntiClockwise but that would change the sweeping
|
|
// direction of the arc so instead we exchange start/begin.
|
|
Float oldStart = aStartAngle;
|
|
aStartAngle = aEndAngle;
|
|
aEndAngle = oldStart;
|
|
}
|
|
|
|
// XXX - Workaround for now, D2D does not appear to do the desired thing when
|
|
// the angle sweeps a complete circle.
|
|
bool fullCircle = false;
|
|
if (aEndAngle - aStartAngle >= 2 * M_PI) {
|
|
fullCircle = true;
|
|
aEndAngle = Float(aStartAngle + M_PI * 1.9999);
|
|
} else if (aStartAngle - aEndAngle >= 2 * M_PI) {
|
|
fullCircle = true;
|
|
aStartAngle = Float(aEndAngle + M_PI * 1.9999);
|
|
}
|
|
|
|
Point startPoint;
|
|
startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
|
|
startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
|
|
|
|
if (!mFigureActive) {
|
|
EnsureActive(startPoint);
|
|
} else {
|
|
mSink->AddLine(D2DPoint(startPoint));
|
|
}
|
|
|
|
Point endPoint;
|
|
endPoint.x = aOrigin.x + aRadius * cosf(aEndAngle);
|
|
endPoint.y = aOrigin.y + aRadius * sinf(aEndAngle);
|
|
|
|
D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL;
|
|
D2D1_SWEEP_DIRECTION direction =
|
|
aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE :
|
|
D2D1_SWEEP_DIRECTION_CLOCKWISE;
|
|
|
|
// if startPoint and endPoint of our circle are too close there are D2D issues
|
|
// with drawing the circle as a single arc
|
|
const Float kEpsilon = 1e-5f;
|
|
if (!fullCircle ||
|
|
(std::abs(startPoint.x - endPoint.x) +
|
|
std::abs(startPoint.y - endPoint.y) > kEpsilon)) {
|
|
|
|
if (aAntiClockwise) {
|
|
if (aStartAngle - aEndAngle > M_PI) {
|
|
arcSize = D2D1_ARC_SIZE_LARGE;
|
|
}
|
|
} else {
|
|
if (aEndAngle - aStartAngle > M_PI) {
|
|
arcSize = D2D1_ARC_SIZE_LARGE;
|
|
}
|
|
}
|
|
|
|
mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint),
|
|
D2D1::SizeF(aRadius, aRadius),
|
|
0.0f,
|
|
direction,
|
|
arcSize));
|
|
}
|
|
else {
|
|
// our first workaround attempt didn't work, so instead draw the circle as
|
|
// two half-circles
|
|
Float midAngle = aEndAngle > aStartAngle ?
|
|
Float(aStartAngle + M_PI) : Float(aEndAngle + M_PI);
|
|
Point midPoint;
|
|
midPoint.x = aOrigin.x + aRadius * cosf(midAngle);
|
|
midPoint.y = aOrigin.y + aRadius * sinf(midAngle);
|
|
|
|
mSink->AddArc(D2D1::ArcSegment(D2DPoint(midPoint),
|
|
D2D1::SizeF(aRadius, aRadius),
|
|
0.0f,
|
|
direction,
|
|
arcSize));
|
|
|
|
// if the adjusted endPoint computed above is used here and endPoint !=
|
|
// startPoint then this half of the circle won't render...
|
|
mSink->AddArc(D2D1::ArcSegment(D2DPoint(startPoint),
|
|
D2D1::SizeF(aRadius, aRadius),
|
|
0.0f,
|
|
direction,
|
|
arcSize));
|
|
}
|
|
|
|
mCurrentPoint = endPoint;
|
|
}
|
|
|
|
Point
|
|
PathBuilderD2D::CurrentPoint() const
|
|
{
|
|
return mCurrentPoint;
|
|
}
|
|
|
|
void
|
|
PathBuilderD2D::EnsureActive(const Point &aPoint)
|
|
{
|
|
if (!mFigureActive) {
|
|
mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED);
|
|
mBeginPoint = aPoint;
|
|
mFigureActive = true;
|
|
}
|
|
}
|
|
|
|
already_AddRefed<Path>
|
|
PathBuilderD2D::Finish()
|
|
{
|
|
if (mFigureActive) {
|
|
mSink->EndFigure(D2D1_FIGURE_END_OPEN);
|
|
}
|
|
|
|
HRESULT hr = mSink->Close();
|
|
if (FAILED(hr)) {
|
|
gfxDebug() << "Failed to close PathSink. Code: " << hexa(hr);
|
|
return nullptr;
|
|
}
|
|
|
|
return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mCurrentPoint, mFillRule, mBackendType);
|
|
}
|
|
|
|
already_AddRefed<PathBuilder>
|
|
PathD2D::CopyToBuilder(FillRule aFillRule) const
|
|
{
|
|
return TransformedCopyToBuilder(Matrix(), aFillRule);
|
|
}
|
|
|
|
already_AddRefed<PathBuilder>
|
|
PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
|
|
{
|
|
RefPtr<ID2D1PathGeometry> path;
|
|
HRESULT hr = DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
|
|
|
|
if (FAILED(hr)) {
|
|
gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr);
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<ID2D1GeometrySink> sink;
|
|
hr = path->Open(getter_AddRefs(sink));
|
|
if (FAILED(hr)) {
|
|
gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr);
|
|
return nullptr;
|
|
}
|
|
|
|
if (aFillRule == FillRule::FILL_WINDING) {
|
|
sink->SetFillMode(D2D1_FILL_MODE_WINDING);
|
|
}
|
|
|
|
if (mEndedActive) {
|
|
OpeningGeometrySink wrapSink(sink);
|
|
mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
|
|
D2DMatrix(aTransform),
|
|
&wrapSink);
|
|
} else {
|
|
mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
|
|
D2DMatrix(aTransform),
|
|
sink);
|
|
}
|
|
|
|
RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule, mBackendType);
|
|
|
|
pathBuilder->mCurrentPoint = aTransform * mEndPoint;
|
|
|
|
if (mEndedActive) {
|
|
pathBuilder->mFigureActive = true;
|
|
}
|
|
|
|
return pathBuilder.forget();
|
|
}
|
|
|
|
void
|
|
PathD2D::StreamToSink(PathSink *aSink) const
|
|
{
|
|
HRESULT hr;
|
|
|
|
StreamingGeometrySink sink(aSink);
|
|
|
|
hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
|
|
D2D1::IdentityMatrix(), &sink);
|
|
|
|
if (FAILED(hr)) {
|
|
gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool
|
|
PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
|
|
{
|
|
BOOL result;
|
|
|
|
HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
|
|
|
|
if (FAILED(hr)) {
|
|
// Log
|
|
return false;
|
|
}
|
|
|
|
return !!result;
|
|
}
|
|
|
|
bool
|
|
PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
|
|
const Point &aPoint,
|
|
const Matrix &aTransform) const
|
|
{
|
|
BOOL result;
|
|
|
|
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
|
|
HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint),
|
|
aStrokeOptions.mLineWidth,
|
|
strokeStyle,
|
|
D2DMatrix(aTransform),
|
|
&result);
|
|
|
|
if (FAILED(hr)) {
|
|
// Log
|
|
return false;
|
|
}
|
|
|
|
return !!result;
|
|
}
|
|
|
|
Rect
|
|
PathD2D::GetBounds(const Matrix &aTransform) const
|
|
{
|
|
D2D1_RECT_F d2dBounds;
|
|
|
|
HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds);
|
|
|
|
Rect bounds = ToRect(d2dBounds);
|
|
if (FAILED(hr) || !bounds.IsFinite()) {
|
|
gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr);
|
|
return Rect();
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
Rect
|
|
PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
|
|
const Matrix &aTransform) const
|
|
{
|
|
D2D1_RECT_F d2dBounds;
|
|
|
|
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
|
|
HRESULT hr =
|
|
mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle,
|
|
D2DMatrix(aTransform), &d2dBounds);
|
|
|
|
Rect bounds = ToRect(d2dBounds);
|
|
if (FAILED(hr) || !bounds.IsFinite()) {
|
|
gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr);
|
|
return Rect();
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
}
|
|
}
|