gecko-dev/gfx/thebes/gfxContext.cpp
Phil Ringnalda e63feb3849 Backed out 8 changesets (bug 982338, bug 1057212, bug 1059033) for build bustage
CLOSED TREE

Backed out changeset 4df1bd30364d (bug 982338)
Backed out changeset b68664a02904 (bug 1057212)
Backed out changeset 07b3695aa02f (bug 1059033)
Backed out changeset bb3885b57d48 (bug 1059033)
Backed out changeset 76897f52ac2c (bug 1059033)
Backed out changeset 31e89a2a409f (bug 1059033)
Backed out changeset 8e81f6f74182 (bug 1059033)
Backed out changeset c5bbf22f2f28 (bug 1059033)
2014-09-01 18:07:57 -07:00

1721 lines
45 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 "cairo.h"
#include "gfxContext.h"
#include "gfxColor.h"
#include "gfxMatrix.h"
#include "gfxUtils.h"
#include "gfxASurface.h"
#include "gfxPattern.h"
#include "gfxPlatform.h"
#include "gfxTeeSurface.h"
#include "GeckoProfiler.h"
#include "gfx2DGlue.h"
#include "mozilla/gfx/PathHelpers.h"
#include <algorithm>
#if CAIRO_HAS_DWRITE_FONT
#include "gfxWindowsPlatform.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
UserDataKey gfxContext::sDontUseAsSourceKey;
/* 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:
explicit 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->GetDTTransform();
if (!mat.Invert()) {
mPattern = new (mColorPattern.addr())
ColorPattern(Color()); // transparent black to paint nothing
return *mPattern;
}
transform = transform * state.patternTransform * mat;
}
mPattern = new (mSurfacePattern.addr())
SurfacePattern(state.sourceSurface, ExtendMode::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(DrawTarget *aTarget, const Point& aDeviceOffset)
: mPathIsRect(false)
, mTransformChanged(false)
, mRefCairo(nullptr)
, mSurface(nullptr)
, mFlags(0)
, mDT(aTarget)
, mOriginalDT(aTarget)
{
MOZ_ASSERT(aTarget, "Don't create a gfxContext without a DrawTarget");
MOZ_COUNT_CTOR(gfxContext);
mStateStack.SetLength(1);
CurrentState().drawTarget = mDT;
CurrentState().deviceOffset = aDeviceOffset;
mDT->SetTransform(Matrix());
}
/* static */ already_AddRefed<gfxContext>
gfxContext::ContextForDrawTarget(DrawTarget* aTarget)
{
Matrix transform = aTarget->GetTransform();
nsRefPtr<gfxContext> result = new gfxContext(aTarget);
result->SetMatrix(ThebesMatrix(transform));
return result.forget();
}
gfxContext::~gfxContext()
{
if (mRefCairo) {
cairo_destroy(mRefCairo);
}
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 (mSurface) {
return mSurface;
}
if (mOriginalDT && mOriginalDT->GetBackendType() == BackendType::CAIRO) {
cairo_surface_t *s =
(cairo_surface_t*)mOriginalDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
if (s) {
mSurface = gfxASurface::Wrap(s);
return mSurface;
}
}
return nullptr;
}
already_AddRefed<gfxASurface>
gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy)
{
if (mDT->GetBackendType() == BackendType::CAIRO) {
cairo_surface_t *s =
(cairo_surface_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
if (s) {
if (dx && dy) {
*dx = -CurrentState().deviceOffset.x;
*dy = -CurrentState().deviceOffset.y;
}
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 (mDT->GetBackendType() == BackendType::CAIRO) {
cairo_t *ctx =
(cairo_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT);
if (ctx) {
return ctx;
}
}
if (mRefCairo) {
// Set transform!
return mRefCairo;
}
mRefCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
return mRefCairo;
}
void
gfxContext::Save()
{
CurrentState().transform = mTransform;
mStateStack.AppendElement(AzureState(CurrentState()));
CurrentState().clipWasReset = false;
CurrentState().pushedClips.Clear();
}
void
gfxContext::Restore()
{
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()
{
mPath = nullptr;
mPathBuilder = nullptr;
mPathIsRect = false;
mTransformChanged = false;
}
void
gfxContext::ClosePath()
{
EnsurePathBuilder();
mPathBuilder->Close();
}
TemporaryRef<Path> gfxContext::GetPath()
{
EnsurePath();
return mPath;
}
void gfxContext::SetPath(Path* path)
{
MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType());
mPath = path;
mPathBuilder = nullptr;
mPathIsRect = false;
mTransformChanged = false;
}
gfxPoint
gfxContext::CurrentPoint()
{
EnsurePathBuilder();
return ThebesPoint(mPathBuilder->CurrentPoint());
}
void
gfxContext::Stroke()
{
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",
js::ProfileEntry::Category::GRAPHICS);
FillAzure(1.0f);
}
void
gfxContext::FillWithOpacity(gfxFloat aOpacity)
{
FillAzure(Float(aOpacity));
}
void
gfxContext::MoveTo(const gfxPoint& pt)
{
EnsurePathBuilder();
mPathBuilder->MoveTo(ToPoint(pt));
}
void
gfxContext::NewSubPath()
{
// 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)
{
EnsurePathBuilder();
mPathBuilder->LineTo(ToPoint(pt));
}
void
gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3)
{
EnsurePathBuilder();
mPathBuilder->BezierTo(ToPoint(pt1), ToPoint(pt2), ToPoint(pt3));
}
void
gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
{
EnsurePathBuilder();
mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2));
}
void
gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
gfxFloat angle1, gfxFloat angle2)
{
EnsurePathBuilder();
mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2));
}
void
gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
gfxFloat angle1, gfxFloat angle2)
{
EnsurePathBuilder();
mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1));
}
void
gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
{
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)
{
Rect rec = ToRect(rect);
if (snapToPixels) {
gfxRect newRect(rect);
if (UserToDevicePixelSnapped(newRect, true)) {
gfxMatrix mat = ThebesMatrix(mTransform);
if (mat.Invert()) {
// We need the user space rect.
rec = ToRect(mat.TransformBounds(newRect));
} else {
rec = Rect();
}
}
}
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 (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)
{
// Lifetime needs to be limited here since we may wrap surface's data.
RefPtr<SourceSurface> surf =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
if (!surf) {
return;
}
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)
{
Matrix newMatrix = mTransform;
ChangeTransform(newMatrix.Translate(Float(pt.x), Float(pt.y)));
}
void
gfxContext::Scale(gfxFloat x, gfxFloat y)
{
Matrix newMatrix = mTransform;
ChangeTransform(newMatrix.Scale(Float(x), Float(y)));
}
void
gfxContext::Rotate(gfxFloat angle)
{
Matrix rotation = Matrix::Rotation(Float(angle));
ChangeTransform(rotation * mTransform);
}
void
gfxContext::Multiply(const gfxMatrix& matrix)
{
ChangeTransform(ToMatrix(matrix) * mTransform);
}
void
gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix)
{
Matrix transform = ToMatrix(matrix) * mTransform;
transform.NudgeToIntegers();
ChangeTransform(transform);
}
void
gfxContext::SetMatrix(const gfxMatrix& matrix)
{
ChangeTransform(ToMatrix(matrix));
}
void
gfxContext::IdentityMatrix()
{
ChangeTransform(Matrix());
}
gfxMatrix
gfxContext::CurrentMatrix() const
{
return ThebesMatrix(mTransform);
}
void
gfxContext::NudgeCurrentMatrixToIntegers()
{
gfxMatrix matrix = ThebesMatrix(mTransform);
matrix.NudgeToIntegers();
ChangeTransform(ToMatrix(matrix));
}
gfxPoint
gfxContext::DeviceToUser(const gfxPoint& point) const
{
Matrix matrix = mTransform;
matrix.Invert();
return ThebesPoint(matrix * ToPoint(point));
}
gfxSize
gfxContext::DeviceToUser(const gfxSize& size) const
{
Matrix matrix = mTransform;
matrix.Invert();
return ThebesSize(matrix * ToSize(size));
}
gfxRect
gfxContext::DeviceToUser(const gfxRect& rect) const
{
Matrix matrix = mTransform;
matrix.Invert();
return ThebesRect(matrix.TransformBounds(ToRect(rect)));
}
gfxPoint
gfxContext::UserToDevice(const gfxPoint& point) const
{
return ThebesPoint(mTransform * ToPoint(point));
}
gfxSize
gfxContext::UserToDevice(const gfxSize& size) const
{
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
{
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)
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)
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 (mode == MODE_ALIASED) {
CurrentState().aaMode = gfx::AntialiasMode::NONE;
} else if (mode == MODE_COVERAGE) {
CurrentState().aaMode = gfx::AntialiasMode::SUBPIXEL;
}
}
gfxContext::AntialiasMode
gfxContext::CurrentAntialiasMode() const
{
if (CurrentState().aaMode == gfx::AntialiasMode::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)
{
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
{
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
{
return CurrentState().strokeOptions.mDashOffset;
}
void
gfxContext::SetLineWidth(gfxFloat width)
{
CurrentState().strokeOptions.mLineWidth = Float(width);
}
gfxFloat
gfxContext::CurrentLineWidth() const
{
return CurrentState().strokeOptions.mLineWidth;
}
void
gfxContext::SetOperator(GraphicsOperator op)
{
if (op == OPERATOR_CLEAR) {
CurrentState().opIsClear = true;
return;
}
CurrentState().opIsClear = false;
CurrentState().op = CompositionOpForOp(op);
}
gfxContext::GraphicsOperator
gfxContext::CurrentOperator() const
{
return ThebesOp(CurrentState().op);
}
void
gfxContext::SetLineCap(GraphicsLineCap cap)
{
CurrentState().strokeOptions.mLineCap = ToCapStyle(cap);
}
gfxContext::GraphicsLineCap
gfxContext::CurrentLineCap() const
{
return ThebesLineCap(CurrentState().strokeOptions.mLineCap);
}
void
gfxContext::SetLineJoin(GraphicsLineJoin join)
{
CurrentState().strokeOptions.mLineJoin = ToJoinStyle(join);
}
gfxContext::GraphicsLineJoin
gfxContext::CurrentLineJoin() const
{
return ThebesLineJoin(CurrentState().strokeOptions.mLineJoin);
}
void
gfxContext::SetMiterLimit(gfxFloat limit)
{
CurrentState().strokeOptions.mMiterLimit = Float(limit);
}
gfxFloat
gfxContext::CurrentMiterLimit() const
{
return CurrentState().strokeOptions.mMiterLimit;
}
void
gfxContext::SetFillRule(FillRule rule)
{
CurrentState().fillRule = rule == FILL_RULE_WINDING ? gfx::FillRule::FILL_WINDING : gfx::FillRule::FILL_EVEN_ODD;
}
gfxContext::FillRule
gfxContext::CurrentFillRule() const
{
return FILL_RULE_WINDING;
}
// clipping
void
gfxContext::Clip(const gfxRect& rect)
{
AzureState::PushedClip clip = { nullptr, ToRect(rect), mTransform };
CurrentState().pushedClips.AppendElement(clip);
mDT->PushClipRect(ToRect(rect));
NewPath();
}
void
gfxContext::Clip()
{
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()
{
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()
{
}
gfxRect
gfxContext::GetClipExtents()
{
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)
{
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)
{
CurrentState().pattern = nullptr;
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
gfxRGBA cms;
qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
if (transform)
gfxPlatform::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)
{
CurrentState().pattern = nullptr;
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().color = ToColor(c);
}
bool
gfxContext::GetDeviceColor(gfxRGBA& c)
{
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)
{
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);
CurrentState().color = Color(0, 0, 0, 0);
}
void
gfxContext::SetPattern(gfxPattern *pattern)
{
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().patternTransformChanged = false;
CurrentState().pattern = pattern;
}
already_AddRefed<gfxPattern>
gfxContext::GetPattern()
{
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 (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",
js::ProfileEntry::Category::GRAPHICS);
// Lifetime needs to be limited here as we may simply wrap surface's data.
RefPtr<SourceSurface> sourceSurf =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
if (!sourceSurf) {
return;
}
gfxPoint pt = surface->GetDeviceOffset();
Mask(sourceSurf, Point(offset.x - pt.x, offset.y - pt.y));
}
void
gfxContext::Mask(SourceSurface *surface, const Point& offset)
{
// We clip here to bind to the mask surface bounds, see above.
mDT->MaskSurface(GeneralPattern(this),
surface,
offset,
DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
}
void
gfxContext::Paint(gfxFloat alpha)
{
PROFILER_LABEL("gfxContext", "Paint",
js::ProfileEntry::Category::GRAPHICS);
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(gfxContentType content)
{
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;
}
void
gfxContext::PushGroupAndCopyBackground(gfxContentType content)
{
IntRect clipExtents;
if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
}
if ((mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
mDT->GetOpaqueRect().Contains(clipExtents)) &&
!mDT->GetUserData(&sDontUseAsSourceKey)) {
DrawTarget *oldDT = mDT;
RefPtr<SourceSurface> source = mDT->Snapshot();
Point oldDeviceOffset = CurrentState().deviceOffset;
PushNewDT(gfxContentType::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()
{
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()
{
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)
{
EnsurePath();
return mPath->ContainsPoint(ToPoint(pt), Matrix());
}
bool
gfxContext::PointInStroke(const gfxPoint& pt)
{
EnsurePath();
return mPath->StrokeContainsPoint(CurrentState().strokeOptions,
ToPoint(pt),
Matrix());
}
gfxRect
gfxContext::GetUserPathExtent()
{
if (mPathIsRect) {
return ThebesRect(mTransform.TransformBounds(mRect));
}
EnsurePath();
return ThebesRect(mPath->GetBounds());
}
gfxRect
gfxContext::GetUserFillExtent()
{
if (mPathIsRect) {
return ThebesRect(mTransform.TransformBounds(mRect));
}
EnsurePath();
return ThebesRect(mPath->GetBounds());
}
gfxRect
gfxContext::GetUserStrokeExtent()
{
if (mPathIsRect) {
Rect rect = mRect;
rect.Inflate(CurrentState().strokeOptions.mLineWidth / 2);
return ThebesRect(mTransform.TransformBounds(rect));
}
EnsurePath();
return ThebesRect(mPath->GetStrokedBounds(CurrentState().strokeOptions, mTransform));
}
bool
gfxContext::HasError()
{
// 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.
EnsurePathBuilder();
Size radii[] = { ToSize(corners[NS_CORNER_TOP_LEFT]),
ToSize(corners[NS_CORNER_TOP_RIGHT]),
ToSize(corners[NS_CORNER_BOTTOM_RIGHT]),
ToSize(corners[NS_CORNER_BOTTOM_LEFT]) };
AppendRoundedRectToPath(mPathBuilder, ToRect(rect), radii, draw_clockwise);
}
#ifdef MOZ_DUMP_PAINTING
void
gfxContext::WriteAsPNG(const char* aFile)
{
gfxUtils::WriteAsPNG(mDT, aFile);
}
void
gfxContext::DumpAsDataURI()
{
gfxUtils::DumpAsDataURI(mDT);
}
void
gfxContext::CopyAsDataURI()
{
gfxUtils::CopyAsDataURI(mDT);
}
#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 == CompositionOp::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 != CompositionOp::OP_SOURCE) {
return CurrentState().op;
}
AzureState &state = CurrentState();
if (state.pattern) {
if (state.pattern->IsOpaque()) {
return CompositionOp::OP_OVER;
} else {
return CompositionOp::OP_SOURCE;
}
} else if (state.sourceSurface) {
if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) {
return CompositionOp::OP_OVER;
} else {
return CompositionOp::OP_SOURCE;
}
} else {
if (state.color.a > 0.999) {
return CompositionOp::OP_OVER;
} else {
return CompositionOp::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 = GetDTTransform();
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;
}
Point
gfxContext::GetDeviceOffset() const
{
return CurrentState().deviceOffset;
}
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(gfxContentType content)
{
Rect clipBounds = GetAzureDeviceSpaceClipBounds();
clipBounds.RoundOut();
clipBounds.width = std::max(1.0f, clipBounds.width);
clipBounds.height = std::max(1.0f, clipBounds.height);
SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content);
RefPtr<DrawTarget> newDT =
mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
format);
if (!newDT) {
NS_WARNING("Failed to create DrawTarget of sufficient size.");
newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format);
if (!newDT) {
// If even this fails.. we're most likely just out of memory!
NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64);
}
}
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()) {
*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;
}