gecko/gfx/thebes/gfxContext.cpp
Milan Sreckovic 7e8ba1c301 Bug 912794 - Separate out the CMS globals and prefs into a singleton gfxColorManagement. r=ncameron
Preferences are now initialized at startup, then updated with callbacks. The methods that access the cached values are not checking the preferences. This lets us better control which thread reads the prefs.

--HG--
rename : gfx/thebes/gfxPlatform.cpp => gfx/thebes/gfxColorManagement.cpp
rename : gfx/thebes/gfxPlatform.h => gfx/thebes/gfxColorManagement.h
2013-09-06 12:48:17 -07:00

2379 lines
64 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/. */
#ifdef _MSC_VER
#define _USE_MATH_DEFINES
#endif
#include <math.h>
#include "mozilla/Alignment.h"
#include "mozilla/Constants.h"
#include "cairo.h"
#include "gfxContext.h"
#include "gfxColor.h"
#include "gfxColorManagement.h"
#include "gfxMatrix.h"
#include "gfxASurface.h"
#include "gfxPattern.h"
#include "gfxPlatform.h"
#include "gfxTeeSurface.h"
#include "GeckoProfiler.h"
#include <algorithm>
#if CAIRO_HAS_DWRITE_FONT
#include "gfxWindowsPlatform.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
/* This class lives on the stack and allows gfxContext users to easily, and
* performantly get a gfx::Pattern to use for drawing in their current context.
*/
class GeneralPattern
{
public:
GeneralPattern(gfxContext *aContext) : mContext(aContext), mPattern(nullptr) {}
~GeneralPattern() { if (mPattern) { mPattern->~Pattern(); } }
operator mozilla::gfx::Pattern&()
{
gfxContext::AzureState &state = mContext->CurrentState();
if (state.pattern) {
return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
} else if (state.sourceSurface) {
Matrix transform = state.surfTransform;
if (state.patternTransformChanged) {
Matrix mat = mContext->mTransform;
mat.Invert();
transform = transform * state.patternTransform * mat;
}
mPattern = new (mSurfacePattern.addr())
SurfacePattern(state.sourceSurface, EXTEND_CLAMP, transform);
return *mPattern;
} else {
mPattern = new (mColorPattern.addr())
ColorPattern(state.color);
return *mPattern;
}
}
private:
union {
mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern;
mozilla::AlignedStorage2<mozilla::gfx::SurfacePattern> mSurfacePattern;
};
gfxContext *mContext;
Pattern *mPattern;
};
gfxContext::gfxContext(gfxASurface *surface)
: mRefCairo(nullptr)
, mSurface(surface)
{
MOZ_COUNT_CTOR(gfxContext);
mCairo = cairo_create(surface->CairoSurface());
mFlags = surface->GetDefaultContextFlags();
if (mSurface->GetRotateForLandscape()) {
// Rotate page 90 degrees to draw landscape page on portrait paper
gfxIntSize size = mSurface->GetSize();
Translate(gfxPoint(0, size.width));
gfxMatrix matrix(0, -1,
1, 0,
0, 0);
Multiply(matrix);
}
}
gfxContext::gfxContext(DrawTarget *aTarget)
: mPathIsRect(false)
, mTransformChanged(false)
, mCairo(nullptr)
, mRefCairo(nullptr)
, mSurface(nullptr)
, mFlags(0)
, mDT(aTarget)
, mOriginalDT(aTarget)
{
MOZ_COUNT_CTOR(gfxContext);
mStateStack.SetLength(1);
CurrentState().drawTarget = mDT;
mDT->SetTransform(Matrix());
}
gfxContext::~gfxContext()
{
if (mCairo) {
cairo_destroy(mCairo);
}
if (mRefCairo) {
cairo_destroy(mRefCairo);
}
if (mDT) {
for (int i = mStateStack.Length() - 1; i >= 0; i--) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
mDT->PopClip();
}
if (mStateStack[i].clipWasReset) {
break;
}
}
mDT->Flush();
}
MOZ_COUNT_DTOR(gfxContext);
}
gfxASurface *
gfxContext::OriginalSurface()
{
if (mCairo || mSurface) {
return mSurface;
}
if (mOriginalDT && mOriginalDT->GetType() == BACKEND_CAIRO) {
cairo_surface_t *s =
(cairo_surface_t*)mOriginalDT->GetNativeSurface(NATIVE_SURFACE_CAIRO_SURFACE);
if (s) {
mSurface = gfxASurface::Wrap(s);
return mSurface;
}
}
return nullptr;
}
already_AddRefed<gfxASurface>
gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy)
{
if (mCairo) {
cairo_surface_t *s = cairo_get_group_target(mCairo);
if (s == mSurface->CairoSurface()) {
if (dx && dy)
cairo_surface_get_device_offset(s, dx, dy);
nsRefPtr<gfxASurface> ret = mSurface;
return ret.forget();
}
if (dx && dy)
cairo_surface_get_device_offset(s, dx, dy);
return gfxASurface::Wrap(s);
} else {
if (mDT->GetType() == BACKEND_CAIRO) {
cairo_surface_t *s =
(cairo_surface_t*)mDT->GetNativeSurface(NATIVE_SURFACE_CAIRO_SURFACE);
if (s) {
if (dx && dy)
cairo_surface_get_device_offset(s, dx, dy);
return gfxASurface::Wrap(s);
}
}
if (dx && dy) {
*dx = *dy = 0;
}
// An Azure context doesn't have a surface backing it.
return nullptr;
}
}
cairo_t *
gfxContext::GetCairo()
{
if (mCairo) {
return mCairo;
}
if (mDT->GetType() == BACKEND_CAIRO) {
cairo_t *ctx =
(cairo_t*)mOriginalDT->GetNativeSurface(NATIVE_SURFACE_CAIRO_CONTEXT);
if (ctx) {
return ctx;
}
}
if (mRefCairo) {
// Set transform!
return mRefCairo;
}
mRefCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
return mRefCairo;
}
void
gfxContext::Save()
{
if (mCairo) {
cairo_save(mCairo);
} else {
CurrentState().transform = mTransform;
mStateStack.AppendElement(AzureState(CurrentState()));
CurrentState().clipWasReset = false;
CurrentState().pushedClips.Clear();
}
}
void
gfxContext::Restore()
{
if (mCairo) {
cairo_restore(mCairo);
} else {
for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) {
mDT->PopClip();
}
if (CurrentState().clipWasReset &&
CurrentState().drawTarget == mStateStack[mStateStack.Length() - 2].drawTarget) {
PushClipsToDT(mDT);
}
mStateStack.RemoveElementAt(mStateStack.Length() - 1);
mDT = CurrentState().drawTarget;
ChangeTransform(CurrentState().transform, false);
}
}
// drawing
void
gfxContext::NewPath()
{
if (mCairo) {
cairo_new_path(mCairo);
} else {
mPath = nullptr;
mPathBuilder = nullptr;
mPathIsRect = false;
mTransformChanged = false;
}
}
void
gfxContext::ClosePath()
{
if (mCairo) {
cairo_close_path(mCairo);
} else {
EnsurePathBuilder();
mPathBuilder->Close();
}
}
already_AddRefed<gfxPath> gfxContext::CopyPath() const
{
if (mCairo) {
nsRefPtr<gfxPath> path = new gfxPath(cairo_copy_path(mCairo));
return path.forget();
} else {
// XXX - This is not yet supported for Azure.
return nullptr;
}
}
void gfxContext::AppendPath(gfxPath* path)
{
if (mCairo) {
if (path->mPath->status == CAIRO_STATUS_SUCCESS && path->mPath->num_data != 0)
cairo_append_path(mCairo, path->mPath);
} else {
// XXX - This is not yet supported for Azure.
return;
}
}
gfxPoint
gfxContext::CurrentPoint()
{
if (mCairo) {
double x, y;
cairo_get_current_point(mCairo, &x, &y);
return gfxPoint(x, y);
} else {
EnsurePathBuilder();
return ThebesPoint(mPathBuilder->CurrentPoint());
}
}
void
gfxContext::Stroke()
{
if (mCairo) {
cairo_stroke_preserve(mCairo);
} else {
AzureState &state = CurrentState();
if (mPathIsRect) {
MOZ_ASSERT(!mTransformChanged);
mDT->StrokeRect(mRect, GeneralPattern(this),
state.strokeOptions,
DrawOptions(1.0f, GetOp(), state.aaMode));
} else {
EnsurePath();
mDT->Stroke(mPath, GeneralPattern(this), state.strokeOptions,
DrawOptions(1.0f, GetOp(), state.aaMode));
}
}
}
void
gfxContext::Fill()
{
PROFILER_LABEL("gfxContext", "Fill");
if (mCairo) {
cairo_fill_preserve(mCairo);
} else {
FillAzure(1.0f);
}
}
void
gfxContext::FillWithOpacity(gfxFloat aOpacity)
{
if (mCairo) {
// This method exists in the hope that one day cairo gets a direct
// API for this, and then we would change this method to use that
// API instead.
if (aOpacity != 1.0) {
gfxContextAutoSaveRestore saveRestore(this);
Clip();
Paint(aOpacity);
} else {
Fill();
}
} else {
FillAzure(Float(aOpacity));
}
}
void
gfxContext::MoveTo(const gfxPoint& pt)
{
if (mCairo) {
cairo_move_to(mCairo, pt.x, pt.y);
} else {
EnsurePathBuilder();
mPathBuilder->MoveTo(ToPoint(pt));
}
}
void
gfxContext::NewSubPath()
{
if (mCairo) {
cairo_new_sub_path(mCairo);
} else {
// XXX - This has no users, we should kill it, it should be equivelant to a
// MoveTo to the path's current point.
}
}
void
gfxContext::LineTo(const gfxPoint& pt)
{
if (mCairo) {
cairo_line_to(mCairo, pt.x, pt.y);
} else {
EnsurePathBuilder();
mPathBuilder->LineTo(ToPoint(pt));
}
}
void
gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3)
{
if (mCairo) {
cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
} else {
EnsurePathBuilder();
mPathBuilder->BezierTo(ToPoint(pt1), ToPoint(pt2), ToPoint(pt3));
}
}
void
gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
{
if (mCairo) {
double cx, cy;
cairo_get_current_point(mCairo, &cx, &cy);
cairo_curve_to(mCairo,
(cx + pt1.x * 2.0) / 3.0,
(cy + pt1.y * 2.0) / 3.0,
(pt1.x * 2.0 + pt2.x) / 3.0,
(pt1.y * 2.0 + pt2.y) / 3.0,
pt2.x,
pt2.y);
} else {
EnsurePathBuilder();
mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2));
}
}
void
gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
gfxFloat angle1, gfxFloat angle2)
{
if (mCairo) {
cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2);
} else {
EnsurePathBuilder();
mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2));
}
}
void
gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
gfxFloat angle1, gfxFloat angle2)
{
if (mCairo) {
cairo_arc_negative(mCairo, center.x, center.y, radius, angle1, angle2);
} else {
EnsurePathBuilder();
mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1));
}
}
void
gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
{
if (mCairo) {
MoveTo(start);
LineTo(end);
} else {
EnsurePathBuilder();
mPathBuilder->MoveTo(ToPoint(start));
mPathBuilder->LineTo(ToPoint(end));
}
}
// XXX snapToPixels is only valid when snapping for filled
// rectangles and for even-width stroked rectangles.
// For odd-width stroked rectangles, we need to offset x/y by
// 0.5...
void
gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels)
{
if (mCairo) {
if (snapToPixels) {
gfxRect snappedRect(rect);
if (UserToDevicePixelSnapped(snappedRect, true))
{
cairo_matrix_t mat;
cairo_get_matrix(mCairo, &mat);
cairo_identity_matrix(mCairo);
Rectangle(snappedRect);
cairo_set_matrix(mCairo, &mat);
return;
}
}
cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height());
} else {
Rect rec = ToRect(rect);
if (snapToPixels) {
gfxRect newRect(rect);
if (UserToDevicePixelSnapped(newRect, true)) {
gfxMatrix mat = ThebesMatrix(mTransform);
mat.Invert();
// We need the user space rect.
rec = ToRect(mat.TransformBounds(newRect));
}
}
if (!mPathBuilder && !mPathIsRect) {
mPathIsRect = true;
mRect = rec;
return;
}
EnsurePathBuilder();
mPathBuilder->MoveTo(rec.TopLeft());
mPathBuilder->LineTo(rec.TopRight());
mPathBuilder->LineTo(rec.BottomRight());
mPathBuilder->LineTo(rec.BottomLeft());
mPathBuilder->Close();
}
}
void
gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions)
{
gfxSize halfDim = dimensions / 2.0;
gfxRect r(center - gfxPoint(halfDim.width, halfDim.height), dimensions);
gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim);
RoundedRectangle (r, c);
}
void
gfxContext::Polygon(const gfxPoint *points, uint32_t numPoints)
{
if (mCairo) {
if (numPoints == 0)
return;
cairo_move_to(mCairo, points[0].x, points[0].y);
for (uint32_t i = 1; i < numPoints; ++i) {
cairo_line_to(mCairo, points[i].x, points[i].y);
}
} else {
if (numPoints == 0) {
return;
}
EnsurePathBuilder();
mPathBuilder->MoveTo(ToPoint(points[0]));
for (uint32_t i = 1; i < numPoints; i++) {
mPathBuilder->LineTo(ToPoint(points[i]));
}
}
}
void
gfxContext::DrawSurface(gfxASurface *surface, const gfxSize& size)
{
if (mCairo) {
cairo_save(mCairo);
cairo_set_source_surface(mCairo, surface->CairoSurface(), 0, 0);
cairo_new_path(mCairo);
// pixel-snap this
Rectangle(gfxRect(gfxPoint(0.0, 0.0), size), true);
cairo_fill(mCairo);
cairo_restore(mCairo);
} else {
// Lifetime needs to be limited here since we may wrap surface's data.
RefPtr<SourceSurface> surf =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
Rect rect(0, 0, Float(size.width), Float(size.height));
rect.Intersect(Rect(0, 0, Float(surf->GetSize().width), Float(surf->GetSize().height)));
// XXX - Should fix pixel snapping.
mDT->DrawSurface(surf, rect, rect);
}
}
// transform stuff
void
gfxContext::Translate(const gfxPoint& pt)
{
if (mCairo) {
cairo_translate(mCairo, pt.x, pt.y);
} else {
Matrix newMatrix = mTransform;
ChangeTransform(newMatrix.Translate(Float(pt.x), Float(pt.y)));
}
}
void
gfxContext::Scale(gfxFloat x, gfxFloat y)
{
if (mCairo) {
cairo_scale(mCairo, x, y);
} else {
Matrix newMatrix = mTransform;
ChangeTransform(newMatrix.Scale(Float(x), Float(y)));
}
}
void
gfxContext::Rotate(gfxFloat angle)
{
if (mCairo) {
cairo_rotate(mCairo, angle);
} else {
Matrix rotation = Matrix::Rotation(Float(angle));
ChangeTransform(rotation * mTransform);
}
}
void
gfxContext::Multiply(const gfxMatrix& matrix)
{
if (mCairo) {
const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
cairo_transform(mCairo, &mat);
} else {
ChangeTransform(ToMatrix(matrix) * mTransform);
}
}
void
gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix)
{
if (mCairo) {
const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
cairo_transform(mCairo, &mat);
// XXX nudging to integers not currently supported for Thebes
} else {
Matrix transform = ToMatrix(matrix) * mTransform;
transform.NudgeToIntegers();
ChangeTransform(transform);
}
}
void
gfxContext::SetMatrix(const gfxMatrix& matrix)
{
if (mCairo) {
const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
cairo_set_matrix(mCairo, &mat);
} else {
Matrix mat;
mat.Translate(-CurrentState().deviceOffset.x, -CurrentState().deviceOffset.y);
ChangeTransform(ToMatrix(matrix));
}
}
void
gfxContext::IdentityMatrix()
{
if (mCairo) {
cairo_identity_matrix(mCairo);
} else {
ChangeTransform(Matrix());
}
}
gfxMatrix
gfxContext::CurrentMatrix() const
{
if (mCairo) {
cairo_matrix_t mat;
cairo_get_matrix(mCairo, &mat);
return gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat));
} else {
return ThebesMatrix(mTransform);
}
}
void
gfxContext::NudgeCurrentMatrixToIntegers()
{
if (mCairo) {
cairo_matrix_t mat;
cairo_get_matrix(mCairo, &mat);
gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat)).NudgeToIntegers();
cairo_set_matrix(mCairo, &mat);
} else {
gfxMatrix matrix = ThebesMatrix(mTransform);
matrix.NudgeToIntegers();
ChangeTransform(ToMatrix(matrix));
}
}
gfxPoint
gfxContext::DeviceToUser(const gfxPoint& point) const
{
if (mCairo) {
gfxPoint ret = point;
cairo_device_to_user(mCairo, &ret.x, &ret.y);
return ret;
} else {
Matrix matrix = mTransform;
matrix.Invert();
return ThebesPoint(matrix * ToPoint(point));
}
}
gfxSize
gfxContext::DeviceToUser(const gfxSize& size) const
{
if (mCairo) {
gfxSize ret = size;
cairo_device_to_user_distance(mCairo, &ret.width, &ret.height);
return ret;
} else {
Matrix matrix = mTransform;
matrix.Invert();
return ThebesSize(matrix * ToSize(size));
}
}
gfxRect
gfxContext::DeviceToUser(const gfxRect& rect) const
{
if (mCairo) {
gfxRect ret = rect;
cairo_device_to_user(mCairo, &ret.x, &ret.y);
cairo_device_to_user_distance(mCairo, &ret.width, &ret.height);
return ret;
} else {
Matrix matrix = mTransform;
matrix.Invert();
return ThebesRect(matrix.TransformBounds(ToRect(rect)));
}
}
gfxPoint
gfxContext::UserToDevice(const gfxPoint& point) const
{
if (mCairo) {
gfxPoint ret = point;
cairo_user_to_device(mCairo, &ret.x, &ret.y);
return ret;
} else {
return ThebesPoint(mTransform * ToPoint(point));
}
}
gfxSize
gfxContext::UserToDevice(const gfxSize& size) const
{
if (mCairo) {
gfxSize ret = size;
cairo_user_to_device_distance(mCairo, &ret.width, &ret.height);
return ret;
} else {
const Matrix &matrix = mTransform;
gfxSize newSize;
newSize.width = size.width * matrix._11 + size.height * matrix._12;
newSize.height = size.width * matrix._21 + size.height * matrix._22;
return newSize;
}
}
gfxRect
gfxContext::UserToDevice(const gfxRect& rect) const
{
if (mCairo) {
double xmin = rect.X(), ymin = rect.Y(), xmax = rect.XMost(), ymax = rect.YMost();
double x[3], y[3];
x[0] = xmin; y[0] = ymax;
x[1] = xmax; y[1] = ymax;
x[2] = xmax; y[2] = ymin;
cairo_user_to_device(mCairo, &xmin, &ymin);
xmax = xmin;
ymax = ymin;
for (int i = 0; i < 3; i++) {
cairo_user_to_device(mCairo, &x[i], &y[i]);
xmin = std::min(xmin, x[i]);
xmax = std::max(xmax, x[i]);
ymin = std::min(ymin, y[i]);
ymax = std::max(ymax, y[i]);
}
return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
} else {
const Matrix &matrix = mTransform;
return ThebesRect(matrix.TransformBounds(ToRect(rect)));
}
}
bool
gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const
{
if (GetFlags() & FLAG_DISABLE_SNAPPING)
return false;
// if we're not at 1.0 scale, don't snap, unless we're
// ignoring the scale. If we're not -just- a scale,
// never snap.
const gfxFloat epsilon = 0.0000001;
#define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
if (mCairo) {
cairo_matrix_t mat;
cairo_get_matrix(mCairo, &mat);
if (!ignoreScale &&
(!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) ||
!WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0)))
return false;
} else {
Matrix mat = mTransform;
if (!ignoreScale &&
(!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) ||
!WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0)))
return false;
}
#undef WITHIN_E
gfxPoint p1 = UserToDevice(rect.TopLeft());
gfxPoint p2 = UserToDevice(rect.TopRight());
gfxPoint p3 = UserToDevice(rect.BottomRight());
// Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
// two opposite corners define the entire rectangle. So check if
// the axis-aligned rectangle with opposite corners p1 and p3
// define an axis-aligned rectangle whose other corners are p2 and p4.
// We actually only need to check one of p2 and p4, since an affine
// transform maps parallelograms to parallelograms.
if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
p1.Round();
p3.Round();
rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
std::max(p1.y, p3.y) - rect.Y()));
return true;
}
return false;
}
bool
gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const
{
if (GetFlags() & FLAG_DISABLE_SNAPPING)
return false;
// if we're not at 1.0 scale, don't snap, unless we're
// ignoring the scale. If we're not -just- a scale,
// never snap.
const gfxFloat epsilon = 0.0000001;
#define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
if (mCairo) {
cairo_matrix_t mat;
cairo_get_matrix(mCairo, &mat);
if (!ignoreScale &&
(!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) ||
!WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0)))
return false;
} else {
Matrix mat = mTransform;
if (!ignoreScale &&
(!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) ||
!WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0)))
return false;
}
#undef WITHIN_E
pt = UserToDevice(pt);
pt.Round();
return true;
}
void
gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect& rect,
gfxPattern *pattern)
{
gfxRect r(rect);
// Bob attempts to pixel-snap the rectangle, and returns true if
// the snapping succeeds. If it does, we need to set up an
// identity matrix, because the rectangle given back is in device
// coordinates.
//
// We then have to call a translate to dr.pos afterwards, to make
// sure the image lines up in the right place with our pixel
// snapped rectangle.
//
// If snapping wasn't successful, we just translate to where the
// pattern would normally start (in app coordinates) and do the
// same thing.
Rectangle(r, true);
SetPattern(pattern);
}
void
gfxContext::SetAntialiasMode(AntialiasMode mode)
{
if (mCairo) {
if (mode == MODE_ALIASED) {
cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_NONE);
} else if (mode == MODE_COVERAGE) {
cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_DEFAULT);
}
} else {
if (mode == MODE_ALIASED) {
CurrentState().aaMode = AA_NONE;
} else if (mode == MODE_COVERAGE) {
CurrentState().aaMode = AA_SUBPIXEL;
}
}
}
gfxContext::AntialiasMode
gfxContext::CurrentAntialiasMode() const
{
if (mCairo) {
cairo_antialias_t aa = cairo_get_antialias(mCairo);
if (aa == CAIRO_ANTIALIAS_NONE)
return MODE_ALIASED;
return MODE_COVERAGE;
} else {
if (CurrentState().aaMode == AA_NONE) {
return MODE_ALIASED;
}
return MODE_COVERAGE;
}
}
void
gfxContext::SetDash(gfxLineType ltype)
{
static double dash[] = {5.0, 5.0};
static double dot[] = {1.0, 1.0};
switch (ltype) {
case gfxLineDashed:
SetDash(dash, 2, 0.0);
break;
case gfxLineDotted:
SetDash(dot, 2, 0.0);
break;
case gfxLineSolid:
default:
SetDash(nullptr, 0, 0.0);
break;
}
}
void
gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset)
{
if (mCairo) {
cairo_set_dash(mCairo, dashes, ndash, offset);
} else {
AzureState &state = CurrentState();
state.dashPattern.SetLength(ndash);
for (int i = 0; i < ndash; i++) {
state.dashPattern[i] = Float(dashes[i]);
}
state.strokeOptions.mDashLength = ndash;
state.strokeOptions.mDashOffset = Float(offset);
state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements()
: nullptr;
}
}
bool
gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const
{
if (mCairo) {
int count = cairo_get_dash_count(mCairo);
if (count <= 0 || !dashes.SetLength(count)) {
return false;
}
cairo_get_dash(mCairo, dashes.Elements(), offset);
return true;
} else {
const AzureState &state = CurrentState();
int count = state.strokeOptions.mDashLength;
if (count <= 0 || !dashes.SetLength(count)) {
return false;
}
for (int i = 0; i < count; i++) {
dashes[i] = state.dashPattern[i];
}
*offset = state.strokeOptions.mDashOffset;
return true;
}
}
gfxFloat
gfxContext::CurrentDashOffset() const
{
if (mCairo) {
if (cairo_get_dash_count(mCairo) <= 0) {
return 0.0;
}
gfxFloat offset;
cairo_get_dash(mCairo, nullptr, &offset);
return offset;
} else {
return CurrentState().strokeOptions.mDashOffset;
}
}
void
gfxContext::SetLineWidth(gfxFloat width)
{
if (mCairo) {
cairo_set_line_width(mCairo, width);
} else {
CurrentState().strokeOptions.mLineWidth = Float(width);
}
}
gfxFloat
gfxContext::CurrentLineWidth() const
{
if (mCairo) {
return cairo_get_line_width(mCairo);
} else {
return CurrentState().strokeOptions.mLineWidth;
}
}
void
gfxContext::SetOperator(GraphicsOperator op)
{
if (mCairo) {
if (mFlags & FLAG_SIMPLIFY_OPERATORS) {
if (op != OPERATOR_SOURCE &&
op != OPERATOR_CLEAR &&
op != OPERATOR_OVER)
op = OPERATOR_OVER;
}
cairo_set_operator(mCairo, (cairo_operator_t)op);
} else {
if (op == OPERATOR_CLEAR) {
CurrentState().opIsClear = true;
return;
}
CurrentState().opIsClear = false;
CurrentState().op = CompositionOpForOp(op);
}
}
gfxContext::GraphicsOperator
gfxContext::CurrentOperator() const
{
if (mCairo) {
return (GraphicsOperator)cairo_get_operator(mCairo);
} else {
return ThebesOp(CurrentState().op);
}
}
void
gfxContext::SetLineCap(GraphicsLineCap cap)
{
if (mCairo) {
cairo_set_line_cap(mCairo, (cairo_line_cap_t)cap);
} else {
CurrentState().strokeOptions.mLineCap = ToCapStyle(cap);
}
}
gfxContext::GraphicsLineCap
gfxContext::CurrentLineCap() const
{
if (mCairo) {
return (GraphicsLineCap)cairo_get_line_cap(mCairo);
} else {
return ThebesLineCap(CurrentState().strokeOptions.mLineCap);
}
}
void
gfxContext::SetLineJoin(GraphicsLineJoin join)
{
if (mCairo) {
cairo_set_line_join(mCairo, (cairo_line_join_t)join);
} else {
CurrentState().strokeOptions.mLineJoin = ToJoinStyle(join);
}
}
gfxContext::GraphicsLineJoin
gfxContext::CurrentLineJoin() const
{
if (mCairo) {
return (GraphicsLineJoin)cairo_get_line_join(mCairo);
} else {
return ThebesLineJoin(CurrentState().strokeOptions.mLineJoin);
}
}
void
gfxContext::SetMiterLimit(gfxFloat limit)
{
if (mCairo) {
cairo_set_miter_limit(mCairo, limit);
} else {
CurrentState().strokeOptions.mMiterLimit = Float(limit);
}
}
gfxFloat
gfxContext::CurrentMiterLimit() const
{
if (mCairo) {
return cairo_get_miter_limit(mCairo);
} else {
return CurrentState().strokeOptions.mMiterLimit;
}
}
void
gfxContext::SetFillRule(FillRule rule)
{
if (mCairo) {
cairo_set_fill_rule(mCairo, (cairo_fill_rule_t)rule);
} else {
CurrentState().fillRule = rule == FILL_RULE_WINDING ? FILL_WINDING : FILL_EVEN_ODD;
}
}
gfxContext::FillRule
gfxContext::CurrentFillRule() const
{
if (mCairo) {
return (FillRule)cairo_get_fill_rule(mCairo);
} else {
return FILL_RULE_WINDING;
}
}
// clipping
void
gfxContext::Clip(const gfxRect& rect)
{
if (mCairo) {
cairo_new_path(mCairo);
cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height());
cairo_clip(mCairo);
} else {
AzureState::PushedClip clip = { nullptr, ToRect(rect), mTransform };
CurrentState().pushedClips.AppendElement(clip);
mDT->PushClipRect(ToRect(rect));
NewPath();
}
}
void
gfxContext::Clip()
{
if (mCairo) {
cairo_clip_preserve(mCairo);
} else {
if (mPathIsRect) {
MOZ_ASSERT(!mTransformChanged);
AzureState::PushedClip clip = { nullptr, mRect, mTransform };
CurrentState().pushedClips.AppendElement(clip);
mDT->PushClipRect(mRect);
} else {
EnsurePath();
mDT->PushClip(mPath);
AzureState::PushedClip clip = { mPath, Rect(), mTransform };
CurrentState().pushedClips.AppendElement(clip);
}
}
}
void
gfxContext::ResetClip()
{
if (mCairo) {
cairo_reset_clip(mCairo);
} else {
for (int i = mStateStack.Length() - 1; i >= 0; i--) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
mDT->PopClip();
}
if (mStateStack[i].clipWasReset) {
break;
}
}
CurrentState().pushedClips.Clear();
CurrentState().clipWasReset = true;
}
}
void
gfxContext::UpdateSurfaceClip()
{
if (mCairo) {
NewPath();
// we paint an empty rectangle to ensure the clip is propagated to
// the destination surface
SetDeviceColor(gfxRGBA(0,0,0,0));
Rectangle(gfxRect(0,1,1,0));
Fill();
}
}
gfxRect
gfxContext::GetClipExtents()
{
if (mCairo) {
double xmin, ymin, xmax, ymax;
cairo_clip_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
} else {
Rect rect = GetAzureDeviceSpaceClipBounds();
if (rect.width == 0 || rect.height == 0) {
return gfxRect(0, 0, 0, 0);
}
Matrix mat = mTransform;
mat.Invert();
rect = mat.TransformBounds(rect);
return ThebesRect(rect);
}
}
bool
gfxContext::ClipContainsRect(const gfxRect& aRect)
{
if (mCairo) {
cairo_rectangle_list_t *clip =
cairo_copy_clip_rectangle_list(mCairo);
bool result = false;
if (clip->status == CAIRO_STATUS_SUCCESS) {
for (int i = 0; i < clip->num_rectangles; i++) {
gfxRect rect(clip->rectangles[i].x, clip->rectangles[i].y,
clip->rectangles[i].width, clip->rectangles[i].height);
if (rect.Contains(aRect)) {
result = true;
break;
}
}
}
cairo_rectangle_list_destroy(clip);
return result;
} else {
unsigned int lastReset = 0;
for (int i = mStateStack.Length() - 2; i > 0; i--) {
if (mStateStack[i].clipWasReset) {
lastReset = i;
break;
}
}
// Since we always return false when the clip list contains a
// non-rectangular clip or a non-rectilinear transform, our 'total' clip
// is always a rectangle if we hit the end of this function.
Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
for (unsigned int i = lastReset; i < mStateStack.Length(); i++) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
if (clip.path || !clip.transform.IsRectilinear()) {
// Cairo behavior is we return false if the clip contains a non-
// rectangle.
return false;
} else {
Rect clipRect = mTransform.TransformBounds(clip.rect);
clipBounds.IntersectRect(clipBounds, clipRect);
}
}
}
return clipBounds.Contains(ToRect(aRect));
}
}
// rendering sources
void
gfxContext::SetColor(const gfxRGBA& c)
{
const gfxColorManagement& colorManagement = gfxColorManagement::Instance();
if (mCairo) {
if (colorManagement.GetMode() == eCMSMode_All) {
gfxRGBA cms;
qcms_transform *transform = colorManagement.GetRGBTransform();
if (transform) {
colorManagement.TransformPixel(c, cms, transform);
}
// Use the original alpha to avoid unnecessary float->byte->float
// conversion errors
cairo_set_source_rgba(mCairo, cms.r, cms.g, cms.b, c.a);
}
else
cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a);
} else {
CurrentState().pattern = nullptr;
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
if (colorManagement.GetMode() == eCMSMode_All) {
gfxRGBA cms;
qcms_transform *transform = colorManagement.GetRGBTransform();
if (transform) {
colorManagement.TransformPixel(c, cms, transform);
}
// Use the original alpha to avoid unnecessary float->byte->float
// conversion errors
CurrentState().color = ToColor(cms);
}
else
CurrentState().color = ToColor(c);
}
}
void
gfxContext::SetDeviceColor(const gfxRGBA& c)
{
if (mCairo) {
cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a);
} else {
CurrentState().pattern = nullptr;
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().color = ToColor(c);
}
}
bool
gfxContext::GetDeviceColor(gfxRGBA& c)
{
if (mCairo) {
return cairo_pattern_get_rgba(cairo_get_source(mCairo),
&c.r,
&c.g,
&c.b,
&c.a) == CAIRO_STATUS_SUCCESS;
} else {
if (CurrentState().sourceSurface) {
return false;
}
if (CurrentState().pattern) {
gfxRGBA color;
return CurrentState().pattern->GetSolidColor(c);
}
c = ThebesRGBA(CurrentState().color);
return true;
}
}
void
gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset)
{
if (mCairo) {
NS_ASSERTION(surface->GetAllowUseAsSource(), "Surface not allowed to be used as source!");
cairo_set_source_surface(mCairo, surface->CairoSurface(), offset.x, offset.y);
} else {
CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y));
CurrentState().pattern = nullptr;
CurrentState().patternTransformChanged = false;
// Keep the underlying cairo surface around while we keep the
// sourceSurface.
CurrentState().sourceSurfCairo = surface;
CurrentState().sourceSurface =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
}
}
void
gfxContext::SetPattern(gfxPattern *pattern)
{
if (mCairo) {
cairo_set_source(mCairo, pattern->CairoPattern());
} else {
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().patternTransformChanged = false;
CurrentState().pattern = pattern;
}
}
already_AddRefed<gfxPattern>
gfxContext::GetPattern()
{
if (mCairo) {
cairo_pattern_t *pat = cairo_get_source(mCairo);
NS_ASSERTION(pat, "I was told this couldn't be null");
nsRefPtr<gfxPattern> wrapper;
if (pat)
wrapper = new gfxPattern(pat);
else
wrapper = new gfxPattern(gfxRGBA(0,0,0,0));
return wrapper.forget();
} else {
nsRefPtr<gfxPattern> pat;
AzureState &state = CurrentState();
if (state.pattern) {
pat = state.pattern;
} else if (state.sourceSurface) {
NS_ASSERTION(false, "Ugh, this isn't good.");
} else {
pat = new gfxPattern(ThebesRGBA(state.color));
}
return pat.forget();
}
}
// masking
void
gfxContext::Mask(gfxPattern *pattern)
{
if (mCairo) {
cairo_mask(mCairo, pattern->CairoPattern());
} else {
if (pattern->Extend() == gfxPattern::EXTEND_NONE) {
// In this situation the mask will be fully transparent (i.e. nothing
// will be drawn) outside of the bounds of the surface. We can support
// that by clipping out drawing to that area.
Point offset;
if (pattern->IsAzure()) {
// This is an Azure pattern. i.e. this was the result of a PopGroup and
// then the extend mode was changed to EXTEND_NONE.
// XXX - We may need some additional magic here in theory to support
// device offsets in these patterns, but no problems have been observed
// yet because of this. And it would complicate things a little further.
offset = Point(0.f, 0.f);
} else if (pattern->GetType() == gfxPattern::PATTERN_SURFACE) {
nsRefPtr<gfxASurface> asurf = pattern->GetSurface();
gfxPoint deviceOffset = asurf->GetDeviceOffset();
offset = Point(-deviceOffset.x, -deviceOffset.y);
// this lets GetAzureSurface work
pattern->GetPattern(mDT);
}
if (pattern->IsAzure() || pattern->GetType() == gfxPattern::PATTERN_SURFACE) {
RefPtr<SourceSurface> mask = pattern->GetAzureSurface();
Matrix mat = ToMatrix(pattern->GetInverseMatrix());
Matrix old = mTransform;
// add in the inverse of the pattern transform so that when we
// MaskSurface we are transformed to the place matching the pattern transform
mat = mat * mTransform;
ChangeTransform(mat);
mDT->MaskSurface(GeneralPattern(this), mask, offset, DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
ChangeTransform(old);
return;
}
}
mDT->Mask(GeneralPattern(this), *pattern->GetPattern(mDT), DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
}
}
void
gfxContext::Mask(gfxASurface *surface, const gfxPoint& offset)
{
PROFILER_LABEL("gfxContext", "Mask");
if (mCairo) {
cairo_mask_surface(mCairo, surface->CairoSurface(), offset.x, offset.y);
} else {
// Lifetime needs to be limited here as we may simply wrap surface's data.
RefPtr<SourceSurface> sourceSurf =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
gfxPoint pt = surface->GetDeviceOffset();
// We clip here to bind to the mask surface bounds, see above.
mDT->MaskSurface(GeneralPattern(this),
sourceSurf,
Point(offset.x - pt.x, offset.y - pt.y),
DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
}
}
void
gfxContext::Paint(gfxFloat alpha)
{
PROFILER_LABEL("gfxContext", "Paint");
if (mCairo) {
cairo_paint_with_alpha(mCairo, alpha);
} else {
AzureState &state = CurrentState();
if (state.sourceSurface && !state.sourceSurfCairo &&
!state.patternTransformChanged && !state.opIsClear)
{
// This is the case where a PopGroupToSource has been done and this
// paint is executed without changing the transform or the source.
Matrix oldMat = mDT->GetTransform();
IntSize surfSize = state.sourceSurface->GetSize();
Matrix mat;
mat.Translate(-state.deviceOffset.x, -state.deviceOffset.y);
mDT->SetTransform(mat);
mDT->DrawSurface(state.sourceSurface,
Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)),
Rect(Point(), Size(surfSize.width, surfSize.height)),
DrawSurfaceOptions(), DrawOptions(alpha, GetOp()));
mDT->SetTransform(oldMat);
return;
}
Matrix mat = mDT->GetTransform();
mat.Invert();
Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
if (state.opIsClear) {
mDT->ClearRect(paintRect);
} else {
mDT->FillRect(paintRect, GeneralPattern(this),
DrawOptions(Float(alpha), GetOp()));
}
}
}
// groups
void
gfxContext::PushGroup(gfxASurface::gfxContentType content)
{
if (mCairo) {
cairo_push_group_with_content(mCairo, (cairo_content_t) content);
} else {
PushNewDT(content);
PushClipsToDT(mDT);
mDT->SetTransform(GetDTTransform());
}
}
static gfxRect
GetRoundOutDeviceClipExtents(gfxContext* aCtx)
{
gfxContextMatrixAutoSaveRestore save(aCtx);
aCtx->IdentityMatrix();
gfxRect r = aCtx->GetClipExtents();
r.RoundOut();
return r;
}
/**
* Copy the contents of aSrc to aDest, translated by aTranslation.
*/
static void
CopySurface(gfxASurface* aSrc, gfxASurface* aDest, const gfxPoint& aTranslation)
{
cairo_t *cr = cairo_create(aDest->CairoSurface());
cairo_set_source_surface(cr, aSrc->CairoSurface(), aTranslation.x, aTranslation.y);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_destroy(cr);
}
void
gfxContext::PushGroupAndCopyBackground(gfxASurface::gfxContentType content)
{
if (mCairo) {
if (content == gfxASurface::CONTENT_COLOR_ALPHA &&
!(GetFlags() & FLAG_DISABLE_COPY_BACKGROUND)) {
nsRefPtr<gfxASurface> s = CurrentSurface();
if ((s->GetAllowUseAsSource() || s->GetType() == gfxASurface::SurfaceTypeTee) &&
(s->GetContentType() == gfxASurface::CONTENT_COLOR ||
s->GetOpaqueRect().Contains(GetRoundOutDeviceClipExtents(this)))) {
cairo_push_group_with_content(mCairo, CAIRO_CONTENT_COLOR);
nsRefPtr<gfxASurface> d = CurrentSurface();
if (d->GetType() == gfxASurface::SurfaceTypeTee) {
NS_ASSERTION(s->GetType() == gfxASurface::SurfaceTypeTee, "Mismatched types");
nsAutoTArray<nsRefPtr<gfxASurface>,2> ss;
nsAutoTArray<nsRefPtr<gfxASurface>,2> ds;
static_cast<gfxTeeSurface*>(s.get())->GetSurfaces(&ss);
static_cast<gfxTeeSurface*>(d.get())->GetSurfaces(&ds);
NS_ASSERTION(ss.Length() == ds.Length(), "Mismatched lengths");
gfxPoint translation = d->GetDeviceOffset() - s->GetDeviceOffset();
for (uint32_t i = 0; i < ss.Length(); ++i) {
CopySurface(ss[i], ds[i], translation);
}
} else {
CopySurface(s, d, gfxPoint(0, 0));
}
d->SetOpaqueRect(s->GetOpaqueRect());
return;
}
}
} else {
IntRect clipExtents;
if (mDT->GetFormat() != FORMAT_B8G8R8X8) {
gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
}
if (mDT->GetFormat() == FORMAT_B8G8R8X8 ||
mDT->GetOpaqueRect().Contains(clipExtents)) {
DrawTarget *oldDT = mDT;
RefPtr<SourceSurface> source = mDT->Snapshot();
Point oldDeviceOffset = CurrentState().deviceOffset;
PushNewDT(gfxASurface::CONTENT_COLOR);
Point offset = CurrentState().deviceOffset - oldDeviceOffset;
Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
Rect sourceRect = surfRect;
sourceRect.x += offset.x;
sourceRect.y += offset.y;
mDT->SetTransform(Matrix());
mDT->DrawSurface(source, surfRect, sourceRect);
mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
PushClipsToDT(mDT);
mDT->SetTransform(GetDTTransform());
return;
}
}
PushGroup(content);
}
already_AddRefed<gfxPattern>
gfxContext::PopGroup()
{
if (mCairo) {
cairo_pattern_t *pat = cairo_pop_group(mCairo);
nsRefPtr<gfxPattern> wrapper = new gfxPattern(pat);
cairo_pattern_destroy(pat);
return wrapper.forget();
} else {
RefPtr<SourceSurface> src = mDT->Snapshot();
Point deviceOffset = CurrentState().deviceOffset;
Restore();
Matrix mat = mTransform;
mat.Invert();
Matrix deviceOffsetTranslation;
deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y);
nsRefPtr<gfxPattern> pat = new gfxPattern(src, deviceOffsetTranslation * mat);
return pat.forget();
}
}
void
gfxContext::PopGroupToSource()
{
if (mCairo) {
cairo_pop_group_to_source(mCairo);
} else {
RefPtr<SourceSurface> src = mDT->Snapshot();
Point deviceOffset = CurrentState().deviceOffset;
Restore();
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = src;
CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
CurrentState().pattern = nullptr;
CurrentState().patternTransformChanged = false;
Matrix mat = mTransform;
mat.Invert();
Matrix deviceOffsetTranslation;
deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y);
CurrentState().surfTransform = deviceOffsetTranslation * mat;
}
}
bool
gfxContext::PointInFill(const gfxPoint& pt)
{
if (mCairo) {
return cairo_in_fill(mCairo, pt.x, pt.y);
} else {
return mPath->ContainsPoint(ToPoint(pt), mTransform);
}
}
bool
gfxContext::PointInStroke(const gfxPoint& pt)
{
if (mCairo) {
return cairo_in_stroke(mCairo, pt.x, pt.y);
} else {
return mPath->StrokeContainsPoint(CurrentState().strokeOptions,
ToPoint(pt),
mTransform);
}
}
gfxRect
gfxContext::GetUserPathExtent()
{
if (mCairo) {
double xmin, ymin, xmax, ymax;
cairo_path_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
} else {
return ThebesRect(mPath->GetBounds());
}
}
gfxRect
gfxContext::GetUserFillExtent()
{
if (mCairo) {
double xmin, ymin, xmax, ymax;
cairo_fill_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
} else {
return ThebesRect(mPath->GetBounds());
}
}
gfxRect
gfxContext::GetUserStrokeExtent()
{
if (mCairo) {
double xmin, ymin, xmax, ymax;
cairo_stroke_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
} else {
return ThebesRect(mPath->GetStrokedBounds(CurrentState().strokeOptions, mTransform));
}
}
already_AddRefed<gfxFlattenedPath>
gfxContext::GetFlattenedPath()
{
if (mCairo) {
nsRefPtr<gfxFlattenedPath> path =
new gfxFlattenedPath(cairo_copy_path_flat(mCairo));
return path.forget();
} else {
// XXX - Used by SVG, needs fixing.
return nullptr;
}
}
bool
gfxContext::HasError()
{
if (mCairo) {
return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS;
} else {
// As far as this is concerned, an Azure context is never in error.
return false;
}
}
void
gfxContext::RoundedRectangle(const gfxRect& rect,
const gfxCornerSizes& corners,
bool draw_clockwise)
{
//
// For CW drawing, this looks like:
//
// ...******0** 1 C
// ****
// *** 2
// **
// *
// *
// 3
// *
// *
//
// Where 0, 1, 2, 3 are the control points of the Bezier curve for
// the corner, and C is the actual corner point.
//
// At the start of the loop, the current point is assumed to be
// the point adjacent to the top left corner on the top
// horizontal. Note that corner indices start at the top left and
// continue clockwise, whereas in our loop i = 0 refers to the top
// right corner.
//
// When going CCW, the control points are swapped, and the first
// corner that's drawn is the top left (along with the top segment).
//
// There is considerable latitude in how one chooses the four
// control points for a Bezier curve approximation to an ellipse.
// For the overall path to be continuous and show no corner at the
// endpoints of the arc, points 0 and 3 must be at the ends of the
// straight segments of the rectangle; points 0, 1, and C must be
// collinear; and points 3, 2, and C must also be collinear. This
// leaves only two free parameters: the ratio of the line segments
// 01 and 0C, and the ratio of the line segments 32 and 3C. See
// the following papers for extensive discussion of how to choose
// these ratios:
//
// Dokken, Tor, et al. "Good approximation of circles by
// curvature-continuous Bezier curves." Computer-Aided
// Geometric Design 7(1990) 33--41.
// Goldapp, Michael. "Approximation of circular arcs by cubic
// polynomials." Computer-Aided Geometric Design 8(1991) 227--238.
// Maisonobe, Luc. "Drawing an elliptical arc using polylines,
// quadratic, or cubic Bezier curves."
// http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
//
// We follow the approach in section 2 of Goldapp (least-error,
// Hermite-type approximation) and make both ratios equal to
//
// 2 2 + n - sqrt(2n + 28)
// alpha = - * ---------------------
// 3 n - 4
//
// where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ).
//
// This is the result of Goldapp's equation (10b) when the angle
// swept out by the arc is pi/2, and the parameter "a-bar" is the
// expression given immediately below equation (21).
//
// Using this value, the maximum radial error for a circle, as a
// fraction of the radius, is on the order of 0.2 x 10^-3.
// Neither Dokken nor Goldapp discusses error for a general
// ellipse; Maisonobe does, but his choice of control points
// follows different constraints, and Goldapp's expression for
// 'alpha' gives much smaller radial error, even for very flat
// ellipses, than Maisonobe's equivalent.
//
// For the various corners and for each axis, the sign of this
// constant changes, or it might be 0 -- it's multiplied by the
// appropriate multiplier from the list before using.
if (mCairo) {
const gfxFloat alpha = 0.55191497064665766025;
typedef struct { gfxFloat a, b; } twoFloats;
twoFloats cwCornerMults[4] = { { -1, 0 },
{ 0, -1 },
{ +1, 0 },
{ 0, +1 } };
twoFloats ccwCornerMults[4] = { { +1, 0 },
{ 0, -1 },
{ -1, 0 },
{ 0, +1 } };
twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults;
gfxPoint pc, p0, p1, p2, p3;
if (draw_clockwise)
cairo_move_to(mCairo, rect.X() + corners[NS_CORNER_TOP_LEFT].width, rect.Y());
else
cairo_move_to(mCairo, rect.X() + rect.Width() - corners[NS_CORNER_TOP_RIGHT].width, rect.Y());
NS_FOR_CSS_CORNERS(i) {
// the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
mozilla::css::Corner c = mozilla::css::Corner(draw_clockwise ? ((i+1) % 4) : ((4-i) % 4));
// i+2 and i+3 respectively. These are used to index into the corner
// multiplier table, and were deduced by calculating out the long form
// of each corner and finding a pattern in the signs and values.
int i2 = (i+2) % 4;
int i3 = (i+3) % 4;
pc = rect.AtCorner(c);
if (corners[c].width > 0.0 && corners[c].height > 0.0) {
p0.x = pc.x + cornerMults[i].a * corners[c].width;
p0.y = pc.y + cornerMults[i].b * corners[c].height;
p3.x = pc.x + cornerMults[i3].a * corners[c].width;
p3.y = pc.y + cornerMults[i3].b * corners[c].height;
p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width;
p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height;
p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width;
p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height;
cairo_line_to (mCairo, p0.x, p0.y);
cairo_curve_to (mCairo,
p1.x, p1.y,
p2.x, p2.y,
p3.x, p3.y);
} else {
cairo_line_to (mCairo, pc.x, pc.y);
}
}
cairo_close_path (mCairo);
} else {
EnsurePathBuilder();
const gfxFloat alpha = 0.55191497064665766025;
typedef struct { gfxFloat a, b; } twoFloats;
twoFloats cwCornerMults[4] = { { -1, 0 },
{ 0, -1 },
{ +1, 0 },
{ 0, +1 } };
twoFloats ccwCornerMults[4] = { { +1, 0 },
{ 0, -1 },
{ -1, 0 },
{ 0, +1 } };
twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults;
gfxPoint pc, p0, p1, p2, p3;
if (draw_clockwise)
mPathBuilder->MoveTo(Point(Float(rect.X() + corners[NS_CORNER_TOP_LEFT].width), Float(rect.Y())));
else
mPathBuilder->MoveTo(Point(Float(rect.X() + rect.Width() - corners[NS_CORNER_TOP_RIGHT].width), Float(rect.Y())));
NS_FOR_CSS_CORNERS(i) {
// the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
mozilla::css::Corner c = mozilla::css::Corner(draw_clockwise ? ((i+1) % 4) : ((4-i) % 4));
// i+2 and i+3 respectively. These are used to index into the corner
// multiplier table, and were deduced by calculating out the long form
// of each corner and finding a pattern in the signs and values.
int i2 = (i+2) % 4;
int i3 = (i+3) % 4;
pc = rect.AtCorner(c);
if (corners[c].width > 0.0 && corners[c].height > 0.0) {
p0.x = pc.x + cornerMults[i].a * corners[c].width;
p0.y = pc.y + cornerMults[i].b * corners[c].height;
p3.x = pc.x + cornerMults[i3].a * corners[c].width;
p3.y = pc.y + cornerMults[i3].b * corners[c].height;
p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width;
p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height;
p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width;
p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height;
mPathBuilder->LineTo(ToPoint(p0));
mPathBuilder->BezierTo(ToPoint(p1), ToPoint(p2), ToPoint(p3));
} else {
mPathBuilder->LineTo(ToPoint(pc));
}
}
mPathBuilder->Close();
}
}
#ifdef MOZ_DUMP_PAINTING
void
gfxContext::WriteAsPNG(const char* aFile)
{
nsRefPtr<gfxASurface> surf = CurrentSurface();
if (surf) {
surf->WriteAsPNG(aFile);
} else {
NS_WARNING("No surface found!");
}
}
void
gfxContext::DumpAsDataURL()
{
nsRefPtr<gfxASurface> surf = CurrentSurface();
if (surf) {
surf->DumpAsDataURL();
} else {
NS_WARNING("No surface found!");
}
}
void
gfxContext::CopyAsDataURL()
{
nsRefPtr<gfxASurface> surf = CurrentSurface();
if (surf) {
surf->CopyAsDataURL();
} else {
NS_WARNING("No surface found!");
}
}
#endif
void
gfxContext::EnsurePath()
{
if (mPathBuilder) {
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
}
if (mPath) {
if (mTransformChanged) {
Matrix mat = mTransform;
mat.Invert();
mat = mPathTransform * mat;
mPathBuilder = mPath->TransformedCopyToBuilder(mat, CurrentState().fillRule);
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
mTransformChanged = false;
}
if (CurrentState().fillRule == mPath->GetFillRule()) {
return;
}
mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
return;
}
EnsurePathBuilder();
mPath = mPathBuilder->Finish();
mPathBuilder = nullptr;
}
void
gfxContext::EnsurePathBuilder()
{
if (mPathBuilder && !mTransformChanged) {
return;
}
if (mPath) {
if (!mTransformChanged) {
mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
mPath = nullptr;
} else {
Matrix invTransform = mTransform;
invTransform.Invert();
Matrix toNewUS = mPathTransform * invTransform;
mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule);
}
return;
}
DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
if (!mPathBuilder) {
mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule);
if (mPathIsRect) {
mPathBuilder->MoveTo(mRect.TopLeft());
mPathBuilder->LineTo(mRect.TopRight());
mPathBuilder->LineTo(mRect.BottomRight());
mPathBuilder->LineTo(mRect.BottomLeft());
mPathBuilder->Close();
}
}
if (mTransformChanged) {
// This could be an else if since this should never happen when
// mPathBuilder is nullptr and mPath is nullptr. But this way we can
// assert if all the state is as expected.
MOZ_ASSERT(oldPath);
MOZ_ASSERT(!mPathIsRect);
Matrix invTransform = mTransform;
invTransform.Invert();
Matrix toNewUS = mPathTransform * invTransform;
RefPtr<Path> path = mPathBuilder->Finish();
mPathBuilder = path->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule);
}
mPathIsRect = false;
}
void
gfxContext::FillAzure(Float aOpacity)
{
AzureState &state = CurrentState();
CompositionOp op = GetOp();
if (mPathIsRect) {
MOZ_ASSERT(!mTransformChanged);
if (state.opIsClear) {
mDT->ClearRect(mRect);
} else if (op == OP_SOURCE) {
// Emulate cairo operator source which is bound by mask!
mDT->ClearRect(mRect);
mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity));
} else {
mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode));
}
} else {
EnsurePath();
NS_ASSERTION(!state.opIsClear, "We shouldn't be clearing complex paths!");
mDT->Fill(mPath, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode));
}
}
void
gfxContext::PushClipsToDT(DrawTarget *aDT)
{
// Tricky, we have to restore all clips -since the last time- the clip
// was reset. If we didn't reset the clip, just popping the clips we
// added was fine.
unsigned int lastReset = 0;
for (int i = mStateStack.Length() - 2; i > 0; i--) {
if (mStateStack[i].clipWasReset) {
lastReset = i;
break;
}
}
// Don't need to save the old transform, we'll be setting a new one soon!
// Push all clips from the last state on the stack where the clip was
// reset to the clip before ours.
for (unsigned int i = lastReset; i < mStateStack.Length() - 1; i++) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform());
if (mStateStack[i].pushedClips[c].path) {
aDT->PushClip(mStateStack[i].pushedClips[c].path);
} else {
aDT->PushClipRect(mStateStack[i].pushedClips[c].rect);
}
}
}
}
CompositionOp
gfxContext::GetOp()
{
if (CurrentState().op != OP_SOURCE) {
return CurrentState().op;
}
AzureState &state = CurrentState();
if (state.pattern) {
if (state.pattern->IsOpaque()) {
return OP_OVER;
} else {
return OP_SOURCE;
}
} else if (state.sourceSurface) {
if (state.sourceSurface->GetFormat() == FORMAT_B8G8R8X8) {
return OP_OVER;
} else {
return OP_SOURCE;
}
} else {
if (state.color.a > 0.999) {
return OP_OVER;
} else {
return OP_SOURCE;
}
}
}
/* SVG font code can change the transform after having set the pattern on the
* context. When the pattern is set it is in user space, if the transform is
* changed after doing so the pattern needs to be converted back into userspace.
* We just store the old pattern transform here so that we only do the work
* needed here if the pattern is actually used.
* We need to avoid doing this when this ChangeTransform comes from a restore,
* since the current pattern and the current transform are both part of the
* state we know the new CurrentState()'s values are valid. But if we assume
* a change they might become invalid since patternTransformChanged is part of
* the state and might be false for the restored AzureState.
*/
void
gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform)
{
AzureState &state = CurrentState();
if (aUpdatePatternTransform && (state.pattern || state.sourceSurface)
&& !state.patternTransformChanged) {
state.patternTransform = mTransform;
state.patternTransformChanged = true;
}
if (mPathIsRect) {
Matrix invMatrix = aNewMatrix;
invMatrix.Invert();
Matrix toNewUS = mTransform * invMatrix;
if (toNewUS.IsRectilinear()) {
mRect = toNewUS.TransformBounds(mRect);
mRect.NudgeToIntegers();
} else {
mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule);
mPathBuilder->MoveTo(toNewUS * mRect.TopLeft());
mPathBuilder->LineTo(toNewUS * mRect.TopRight());
mPathBuilder->LineTo(toNewUS * mRect.BottomRight());
mPathBuilder->LineTo(toNewUS * mRect.BottomLeft());
mPathBuilder->Close();
mPathIsRect = false;
}
// No need to consider the transform changed now!
mTransformChanged = false;
} else if ((mPath || mPathBuilder) && !mTransformChanged) {
mTransformChanged = true;
mPathTransform = mTransform;
}
mTransform = aNewMatrix;
mDT->SetTransform(GetDTTransform());
}
Rect
gfxContext::GetAzureDeviceSpaceClipBounds()
{
unsigned int lastReset = 0;
for (int i = mStateStack.Length() - 1; i > 0; i--) {
if (mStateStack[i].clipWasReset) {
lastReset = i;
break;
}
}
Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y,
Float(mDT->GetSize().width), Float(mDT->GetSize().height));
for (unsigned int i = lastReset; i < mStateStack.Length(); i++) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
if (clip.path) {
Rect bounds = clip.path->GetBounds(clip.transform);
rect.IntersectRect(rect, bounds);
} else {
rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect));
}
}
}
return rect;
}
Matrix
gfxContext::GetDeviceTransform() const
{
Matrix mat;
mat.Translate(-CurrentState().deviceOffset.x, -CurrentState().deviceOffset.y);
return mat;
}
Matrix
gfxContext::GetDTTransform() const
{
Matrix mat = mTransform;
mat._31 -= CurrentState().deviceOffset.x;
mat._32 -= CurrentState().deviceOffset.y;
return mat;
}
void
gfxContext::PushNewDT(gfxASurface::gfxContentType content)
{
Rect clipBounds = GetAzureDeviceSpaceClipBounds();
clipBounds.RoundOut();
clipBounds.width = std::max(1.0f, clipBounds.width);
clipBounds.height = std::max(1.0f, clipBounds.height);
RefPtr<DrawTarget> newDT =
mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content));
Save();
CurrentState().drawTarget = newDT;
CurrentState().deviceOffset = clipBounds.TopLeft();
mDT = newDT;
}
/**
* Work out whether cairo will snap inter-glyph spacing to pixels.
*
* Layout does not align text to pixel boundaries, so, with font drawing
* backends that snap glyph positions to pixels, it is important that
* inter-glyph spacing within words is always an integer number of pixels.
* This ensures that the drawing backend snaps all of the word's glyphs in the
* same direction and so inter-glyph spacing remains the same.
*/
void
gfxContext::GetRoundOffsetsToPixels(bool *aRoundX, bool *aRoundY)
{
*aRoundX = false;
// Could do something fancy here for ScaleFactors of
// AxisAlignedTransforms, but we leave things simple.
// Not much point rounding if a matrix will mess things up anyway.
// Also return false for non-cairo contexts.
if (CurrentMatrix().HasNonTranslation() || mDT) {
*aRoundY = false;
return;
}
// All raster backends snap glyphs to pixels vertically.
// Print backends set CAIRO_HINT_METRICS_OFF.
*aRoundY = true;
cairo_t *cr = GetCairo();
cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr);
// Sometimes hint metrics gets set for us, most notably for printing.
cairo_font_options_t *font_options = cairo_font_options_create();
cairo_scaled_font_get_font_options(scaled_font, font_options);
cairo_hint_metrics_t hint_metrics =
cairo_font_options_get_hint_metrics(font_options);
cairo_font_options_destroy(font_options);
switch (hint_metrics) {
case CAIRO_HINT_METRICS_OFF:
*aRoundY = false;
return;
case CAIRO_HINT_METRICS_DEFAULT:
// Here we mimic what cairo surface/font backends do. Printing
// surfaces have already been handled by hint_metrics. The
// fallback show_glyphs implementation composites pixel-aligned
// glyph surfaces, so we just pick surface/font combinations that
// override this.
switch (cairo_scaled_font_get_type(scaled_font)) {
#if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
case CAIRO_FONT_TYPE_DWRITE:
// show_glyphs is implemented on the font and so is used for
// all surface types; however, it may pixel-snap depending on
// the dwrite rendering mode
if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) &&
gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
DWRITE_MEASURING_MODE_NATURAL) {
return;
}
#endif
case CAIRO_FONT_TYPE_QUARTZ:
// Quartz surfaces implement show_glyphs for Quartz fonts
if (cairo_surface_get_type(cairo_get_target(cr)) ==
CAIRO_SURFACE_TYPE_QUARTZ) {
return;
}
default:
break;
}
// fall through:
case CAIRO_HINT_METRICS_ON:
break;
}
*aRoundX = true;
return;
}