tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gfxContext.cpp (18262B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include <math.h>
      8 
      9 #include "cairo.h"
     10 
     11 #include "gfxContext.h"
     12 
     13 #include "gfxMatrix.h"
     14 #include "gfxUtils.h"
     15 #include "gfxPattern.h"
     16 #include "gfxPlatform.h"
     17 
     18 #include "gfx2DGlue.h"
     19 #include "mozilla/gfx/PathHelpers.h"
     20 #include "mozilla/ProfilerLabels.h"
     21 #include <algorithm>
     22 #include "TextDrawTarget.h"
     23 
     24 #if XP_WIN
     25 #  include "gfxWindowsPlatform.h"
     26 #  include "mozilla/gfx/DeviceManagerDx.h"
     27 #endif
     28 
     29 using namespace mozilla;
     30 using namespace mozilla::gfx;
     31 
     32 #ifdef DEBUG
     33 #  define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true;
     34 #else
     35 #  define CURRENTSTATE_CHANGED()
     36 #endif
     37 
     38 PatternFromState::operator Pattern&() {
     39  const gfxContext::AzureState& state = mContext->mAzureState;
     40 
     41  if (state.pattern) {
     42    return *state.pattern->GetPattern(
     43        mContext->mDT,
     44        state.patternTransformChanged ? &state.patternTransform : nullptr);
     45  }
     46 
     47  mPattern = new (mColorPattern.addr()) ColorPattern(state.color);
     48  return *mPattern;
     49 }
     50 
     51 /* static */
     52 UniquePtr<gfxContext> gfxContext::CreateOrNull(DrawTarget* aTarget) {
     53  if (!aTarget || !aTarget->IsValid()) {
     54    gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull "
     55                    << hexa(aTarget);
     56    return nullptr;
     57  }
     58 
     59  return MakeUnique<gfxContext>(aTarget);
     60 }
     61 
     62 gfxContext::~gfxContext() {
     63  while (!mSavedStates.IsEmpty()) {
     64    Restore();
     65  }
     66  for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) {
     67    mDT->PopClip();
     68  }
     69 }
     70 
     71 mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() const {
     72  if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) {
     73    return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT);
     74  }
     75  return nullptr;
     76 }
     77 
     78 void gfxContext::Save() {
     79  mSavedStates.AppendElement(mAzureState);
     80  mAzureState.pushedClips.Clear();
     81 #ifdef DEBUG
     82  mAzureState.mContentChanged = false;
     83 #endif
     84 }
     85 
     86 void gfxContext::Restore() {
     87 #ifdef DEBUG
     88  // gfxContext::Restore is used to restore AzureState. We need to restore it
     89  // only if it was altered. The following APIs do change the content of
     90  // AzureState, a user should save the state before using them and restore it
     91  // after finishing painting:
     92  // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All
     93  //    gfxContext SetXXXX public functions belong to this category, except
     94  //    gfxContext::SetPath & gfxContext::SetMatrix.
     95  // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip()
     96  //    directly instead of using gfxContext::Save if the clip region is the
     97  //    only thing that you altered in the target context.
     98  // 3. Function of setup transform matrix, such as Multiply() and
     99  //    SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended
    100  //    if transform data is the only thing that you are going to alter.
    101  //
    102  // You will hit the assertion message below if there is no above functions
    103  // been used between a pair of gfxContext::Save and gfxContext::Restore.
    104  // Considerate to remove that pair of Save/Restore if hitting that assertion.
    105  //
    106  // In the other hand, the following APIs do not alter the content of the
    107  // current AzureState, therefore, there is no need to save & restore
    108  // AzureState:
    109  // 1. constant member functions of gfxContext.
    110  // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the
    111  //    content of drawing buffer, which is not part of AzureState.
    112  // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath().
    113  //    Surprisingly, path information is not stored in AzureState either.
    114  // Save current AzureState before using these type of APIs does nothing but
    115  // make performance worse.
    116  NS_ASSERTION(
    117      mAzureState.mContentChanged || mAzureState.pushedClips.Length() > 0,
    118      "The context of the current AzureState is not altered after "
    119      "Save() been called. you may consider to remove this pair of "
    120      "gfxContext::Save/Restore.");
    121 #endif
    122 
    123  for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) {
    124    mDT->PopClip();
    125  }
    126 
    127  mAzureState = mSavedStates.PopLastElement();
    128 
    129  ChangeTransform(mAzureState.transform, false);
    130 }
    131 
    132 // drawing
    133 
    134 void gfxContext::Fill(const Pattern& aPattern) {
    135  AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS);
    136 
    137  CompositionOp op = GetOp();
    138 
    139  if (mPathIsRect) {
    140    MOZ_ASSERT(!mTransformChanged);
    141 
    142    if (op == CompositionOp::OP_SOURCE) {
    143      // Emulate cairo operator source which is bound by mask!
    144      mDT->ClearRect(mRect);
    145      mDT->FillRect(mRect, aPattern, DrawOptions(1.0f));
    146    } else {
    147      mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode));
    148    }
    149  } else {
    150    EnsurePath();
    151    mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode));
    152  }
    153 }
    154 
    155 // XXX snapToPixels is only valid when snapping for filled
    156 // rectangles and for even-width stroked rectangles.
    157 // For odd-width stroked rectangles, we need to offset x/y by
    158 // 0.5...
    159 void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) {
    160  Rect rec = ToRect(rect);
    161 
    162  if (snapToPixels) {
    163    gfxRect newRect(rect);
    164    if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) {
    165      gfxMatrix mat = CurrentMatrixDouble();
    166      if (mat.Invert()) {
    167        // We need the user space rect.
    168        rec = ToRect(mat.TransformBounds(newRect));
    169      } else {
    170        rec = Rect();
    171      }
    172    }
    173  }
    174 
    175  if (!mPathBuilder && !mPathIsRect) {
    176    mPathIsRect = true;
    177    mRect = rec;
    178    return;
    179  }
    180 
    181  EnsurePathBuilder();
    182 
    183  mPathBuilder->MoveTo(rec.TopLeft());
    184  mPathBuilder->LineTo(rec.TopRight());
    185  mPathBuilder->LineTo(rec.BottomRight());
    186  mPathBuilder->LineTo(rec.BottomLeft());
    187  mPathBuilder->Close();
    188 }
    189 
    190 void gfxContext::SnappedClip(const gfxRect& rect) {
    191  Rect rec = ToRect(rect);
    192 
    193  gfxRect newRect(rect);
    194  if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) {
    195    gfxMatrix mat = CurrentMatrixDouble();
    196    if (mat.Invert()) {
    197      // We need the user space rect.
    198      rec = ToRect(mat.TransformBounds(newRect));
    199    } else {
    200      rec = Rect();
    201    }
    202  }
    203 
    204  Clip(rec);
    205 }
    206 
    207 bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect,
    208                                          SnapOptions aOptions) const {
    209  if (mDT->GetUserData(&sDisablePixelSnapping)) {
    210    return false;
    211  }
    212 
    213  // if we're not at 1.0 scale, don't snap, unless we're
    214  // ignoring the scale.  If we're not -just- a scale,
    215  // never snap.
    216  const gfxFloat epsilon = 0.0000001;
    217 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
    218  Matrix mat = mAzureState.transform;
    219  if (!aOptions.contains(SnapOption::IgnoreScale) &&
    220      (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
    221       !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) {
    222    return false;
    223  }
    224 #undef WITHIN_E
    225 
    226  gfxPoint p1 = UserToDevice(rect.TopLeft());
    227  gfxPoint p2 = UserToDevice(rect.TopRight());
    228  gfxPoint p3 = UserToDevice(rect.BottomRight());
    229 
    230  // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
    231  // two opposite corners define the entire rectangle. So check if
    232  // the axis-aligned rectangle with opposite corners p1 and p3
    233  // define an axis-aligned rectangle whose other corners are p2 and p4.
    234  // We actually only need to check one of p2 and p4, since an affine
    235  // transform maps parallelograms to parallelograms.
    236  if (!(p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y))) {
    237    return false;
    238  }
    239 
    240  if (aOptions.contains(SnapOption::PrioritizeSize)) {
    241    // Snap the dimensions of the rect, to minimize distortion; only after that
    242    // will we snap its position. In particular, this guarantees that a square
    243    // remains square after snapping, which may not be the case if each edge is
    244    // independently snapped to device pixels.
    245 
    246    // Use the same rounding approach as gfx::BasePoint::Round.
    247    rect.SizeTo(std::floor(rect.width + 0.5), std::floor(rect.height + 0.5));
    248 
    249    // Find the top-left corner based on the original center and the snapped
    250    // size, then snap this new corner to the grid.
    251    gfxPoint center = (p1 + p3) / 2;
    252    gfxPoint topLeft = center - gfxPoint(rect.width / 2.0, rect.height / 2.0);
    253    topLeft.Round();
    254    rect.MoveTo(topLeft);
    255  } else {
    256    p1.Round();
    257    p3.Round();
    258    rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
    259    rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
    260                        std::max(p1.y, p3.y) - rect.Y()));
    261  }
    262 
    263  return true;
    264 }
    265 
    266 bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt,
    267                                          bool ignoreScale) const {
    268  if (mDT->GetUserData(&sDisablePixelSnapping)) {
    269    return false;
    270  }
    271 
    272  // if we're not at 1.0 scale, don't snap, unless we're
    273  // ignoring the scale.  If we're not -just- a scale,
    274  // never snap.
    275  const gfxFloat epsilon = 0.0000001;
    276 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
    277  Matrix mat = mAzureState.transform;
    278  if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
    279                       !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) {
    280    return false;
    281  }
    282 #undef WITHIN_E
    283 
    284  pt = UserToDevice(pt);
    285  pt.Round();
    286  return true;
    287 }
    288 
    289 void gfxContext::SetDash(const Float* dashes, int ndash, Float offset,
    290                         Float devPxScale) {
    291  CURRENTSTATE_CHANGED()
    292 
    293  mAzureState.dashPattern.SetLength(ndash);
    294  for (int i = 0; i < ndash; i++) {
    295    mAzureState.dashPattern[i] = dashes[i] * devPxScale;
    296  }
    297  mAzureState.strokeOptions.mDashLength = ndash;
    298  mAzureState.strokeOptions.mDashOffset = offset * devPxScale;
    299  mAzureState.strokeOptions.mDashPattern =
    300      ndash ? mAzureState.dashPattern.Elements() : nullptr;
    301 }
    302 
    303 bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes,
    304                             Float* offset) const {
    305  if (mAzureState.strokeOptions.mDashLength == 0 ||
    306      !dashes.Assign(mAzureState.dashPattern, fallible)) {
    307    return false;
    308  }
    309 
    310  *offset = mAzureState.strokeOptions.mDashOffset;
    311 
    312  return true;
    313 }
    314 
    315 // clipping
    316 void gfxContext::Clip(const Rect& rect) {
    317  AzureState::PushedClip clip = {nullptr, rect, mAzureState.transform};
    318  mAzureState.pushedClips.AppendElement(clip);
    319  mDT->PushClipRect(rect);
    320  NewPath();
    321 }
    322 
    323 void gfxContext::Clip(Path* aPath) {
    324  mDT->PushClip(aPath);
    325  AzureState::PushedClip clip = {aPath, Rect(), mAzureState.transform};
    326  mAzureState.pushedClips.AppendElement(clip);
    327 }
    328 
    329 void gfxContext::Clip() {
    330  if (mPathIsRect) {
    331    MOZ_ASSERT(!mTransformChanged);
    332 
    333    AzureState::PushedClip clip = {nullptr, mRect, mAzureState.transform};
    334    mAzureState.pushedClips.AppendElement(clip);
    335    mDT->PushClipRect(mRect);
    336  } else {
    337    EnsurePath();
    338    mDT->PushClip(mPath);
    339    AzureState::PushedClip clip = {mPath, Rect(), mAzureState.transform};
    340    mAzureState.pushedClips.AppendElement(clip);
    341  }
    342 }
    343 
    344 gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const {
    345  Rect rect = GetAzureDeviceSpaceClipBounds();
    346 
    347  if (rect.IsZeroArea()) {
    348    return gfxRect(0, 0, 0, 0);
    349  }
    350 
    351  if (aSpace == eUserSpace) {
    352    Matrix mat = mAzureState.transform;
    353    mat.Invert();
    354    rect = mat.TransformBounds(rect);
    355  }
    356 
    357  return ThebesRect(rect);
    358 }
    359 
    360 bool gfxContext::ExportClip(ClipExporter& aExporter) const {
    361  ForAllClips([&](const AzureState::PushedClip& aClip) -> void {
    362    gfx::Matrix transform = aClip.transform;
    363    transform.PostTranslate(-GetDeviceOffset());
    364 
    365    aExporter.BeginClip(transform);
    366    if (aClip.path) {
    367      aClip.path->StreamToSink(&aExporter);
    368    } else {
    369      aExporter.MoveTo(aClip.rect.TopLeft());
    370      aExporter.LineTo(aClip.rect.TopRight());
    371      aExporter.LineTo(aClip.rect.BottomRight());
    372      aExporter.LineTo(aClip.rect.BottomLeft());
    373      aExporter.Close();
    374    }
    375    aExporter.EndClip();
    376  });
    377 
    378  return true;
    379 }
    380 
    381 // rendering sources
    382 
    383 bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) const {
    384  if (mAzureState.pattern) {
    385    return mAzureState.pattern->GetSolidColor(aColorOut);
    386  }
    387 
    388  aColorOut = mAzureState.color;
    389  return true;
    390 }
    391 
    392 already_AddRefed<gfxPattern> gfxContext::GetPattern() const {
    393  RefPtr<gfxPattern> pat;
    394 
    395  if (mAzureState.pattern) {
    396    pat = mAzureState.pattern;
    397  } else {
    398    pat = new gfxPattern(mAzureState.color);
    399  }
    400  return pat.forget();
    401 }
    402 
    403 void gfxContext::Paint(Float alpha) const {
    404  AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS);
    405 
    406  Matrix mat = mDT->GetTransform();
    407  mat.Invert();
    408  Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
    409 
    410  mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp()));
    411 }
    412 
    413 #ifdef MOZ_DUMP_PAINTING
    414 void gfxContext::WriteAsPNG(const char* aFile) {
    415  gfxUtils::WriteAsPNG(mDT, aFile);
    416 }
    417 
    418 void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); }
    419 
    420 void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); }
    421 #endif
    422 
    423 void gfxContext::EnsurePath() {
    424  if (mPathBuilder) {
    425    mPath = mPathBuilder->Finish();
    426    mPathBuilder = nullptr;
    427  }
    428 
    429  if (mPath) {
    430    if (mTransformChanged) {
    431      Matrix mat = mAzureState.transform;
    432      mat.Invert();
    433      mat = mPathTransform * mat;
    434      Path::Transform(mPath, mat);
    435 
    436      mTransformChanged = false;
    437    }
    438    return;
    439  }
    440 
    441  EnsurePathBuilder();
    442  mPath = mPathBuilder->Finish();
    443  mPathBuilder = nullptr;
    444 }
    445 
    446 void gfxContext::EnsurePathBuilder() {
    447  if (mPathBuilder && !mTransformChanged) {
    448    return;
    449  }
    450 
    451  if (mPath) {
    452    if (!mTransformChanged) {
    453      mPathBuilder = Path::ToBuilder(mPath.forget());
    454    } else {
    455      Matrix invTransform = mAzureState.transform;
    456      invTransform.Invert();
    457      Matrix toNewUS = mPathTransform * invTransform;
    458      mPathBuilder = Path::ToBuilder(mPath.forget(), toNewUS);
    459    }
    460    return;
    461  }
    462 
    463  DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
    464 
    465  if (!mPathBuilder) {
    466    mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
    467 
    468    if (mPathIsRect) {
    469      mPathBuilder->MoveTo(mRect.TopLeft());
    470      mPathBuilder->LineTo(mRect.TopRight());
    471      mPathBuilder->LineTo(mRect.BottomRight());
    472      mPathBuilder->LineTo(mRect.BottomLeft());
    473      mPathBuilder->Close();
    474    }
    475  }
    476 
    477  if (mTransformChanged) {
    478    // This could be an else if since this should never happen when
    479    // mPathBuilder is nullptr and mPath is nullptr. But this way we can
    480    // assert if all the state is as expected.
    481    MOZ_ASSERT(oldPath);
    482    MOZ_ASSERT(!mPathIsRect);
    483 
    484    Matrix invTransform = mAzureState.transform;
    485    invTransform.Invert();
    486    Matrix toNewUS = mPathTransform * invTransform;
    487 
    488    RefPtr<Path> path = mPathBuilder->Finish();
    489    if (!path) {
    490      gfxCriticalError()
    491          << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish";
    492    }
    493    mPathBuilder = Path::ToBuilder(path.forget(), toNewUS);
    494  }
    495 
    496  mPathIsRect = false;
    497 }
    498 
    499 CompositionOp gfxContext::GetOp() const {
    500  if (mAzureState.op != CompositionOp::OP_SOURCE) {
    501    return mAzureState.op;
    502  }
    503 
    504  if (mAzureState.pattern) {
    505    if (mAzureState.pattern->IsOpaque()) {
    506      return CompositionOp::OP_OVER;
    507    } else {
    508      return CompositionOp::OP_SOURCE;
    509    }
    510  } else {
    511    if (mAzureState.color.a > 0.999) {
    512      return CompositionOp::OP_OVER;
    513    } else {
    514      return CompositionOp::OP_SOURCE;
    515    }
    516  }
    517 }
    518 
    519 /* SVG font code can change the transform after having set the pattern on the
    520 * context. When the pattern is set it is in user space, if the transform is
    521 * changed after doing so the pattern needs to be converted back into userspace.
    522 * We just store the old pattern transform here so that we only do the work
    523 * needed here if the pattern is actually used.
    524 * We need to avoid doing this when this ChangeTransform comes from a restore,
    525 * since the current pattern and the current transform are both part of the
    526 * state we know the new mAzureState's values are valid. But if we assume
    527 * a change they might become invalid since patternTransformChanged is part of
    528 * the state and might be false for the restored AzureState.
    529 */
    530 void gfxContext::ChangeTransform(const Matrix& aNewMatrix,
    531                                 bool aUpdatePatternTransform) {
    532  if (aUpdatePatternTransform && (mAzureState.pattern) &&
    533      !mAzureState.patternTransformChanged) {
    534    mAzureState.patternTransform = GetDTTransform();
    535    mAzureState.patternTransformChanged = true;
    536  }
    537 
    538  if (mPathIsRect) {
    539    Matrix invMatrix = aNewMatrix;
    540 
    541    invMatrix.Invert();
    542 
    543    Matrix toNewUS = mAzureState.transform * invMatrix;
    544 
    545    if (toNewUS.IsRectilinear()) {
    546      mRect = toNewUS.TransformBounds(mRect);
    547      mRect.NudgeToIntegers();
    548    } else {
    549      mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
    550 
    551      mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft()));
    552      mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight()));
    553      mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight()));
    554      mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft()));
    555      mPathBuilder->Close();
    556 
    557      mPathIsRect = false;
    558    }
    559 
    560    // No need to consider the transform changed now!
    561    mTransformChanged = false;
    562  } else if ((mPath || mPathBuilder) && !mTransformChanged) {
    563    mTransformChanged = true;
    564    mPathTransform = mAzureState.transform;
    565  }
    566 
    567  mAzureState.transform = aNewMatrix;
    568 
    569  mDT->SetTransform(GetDTTransform());
    570 }
    571 
    572 Rect gfxContext::GetAzureDeviceSpaceClipBounds() const {
    573  Rect rect(mAzureState.deviceOffset.x + Float(mDT->GetRect().x),
    574            mAzureState.deviceOffset.y + Float(mDT->GetRect().y),
    575            Float(mDT->GetSize().width), Float(mDT->GetSize().height));
    576  ForAllClips([&](const AzureState::PushedClip& aClip) -> void {
    577    if (aClip.path) {
    578      rect.IntersectRect(rect, aClip.path->GetBounds(aClip.transform));
    579    } else {
    580      rect.IntersectRect(rect, aClip.transform.TransformBounds(aClip.rect));
    581    }
    582  });
    583 
    584  return rect;
    585 }
    586 
    587 template <typename F>
    588 void gfxContext::ForAllClips(F&& aLambda) const {
    589  for (const auto& state : mSavedStates) {
    590    for (const auto& clip : state.pushedClips) {
    591      aLambda(clip);
    592    }
    593  }
    594  for (const auto& clip : mAzureState.pushedClips) {
    595    aLambda(clip);
    596  }
    597 }