summaryrefslogtreecommitdiffstats
path: root/gfx/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/src
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/src')
-rw-r--r--gfx/src/AppUnits.h18
-rw-r--r--gfx/src/ArrayView.h37
-rw-r--r--gfx/src/CompositorHitTestInfo.h113
-rw-r--r--gfx/src/DriverCrashGuard.cpp528
-rw-r--r--gfx/src/DriverCrashGuard.h172
-rw-r--r--gfx/src/FilterDescription.h142
-rw-r--r--gfx/src/FilterSupport.cpp1953
-rw-r--r--gfx/src/FilterSupport.h466
-rw-r--r--gfx/src/FontPropertyTypes.h490
-rw-r--r--gfx/src/RegionBuilder.h30
-rw-r--r--gfx/src/RelativeLuminanceUtils.h65
-rw-r--r--gfx/src/TiledRegion.cpp353
-rw-r--r--gfx/src/TiledRegion.h195
-rw-r--r--gfx/src/X11UndefineNone.h49
-rw-r--r--gfx/src/X11Util.cpp81
-rw-r--r--gfx/src/X11Util.h143
-rw-r--r--gfx/src/components.conf14
-rw-r--r--gfx/src/gfxCrashReporterUtils.cpp127
-rw-r--r--gfx/src/gfxCrashReporterUtils.h56
-rw-r--r--gfx/src/gfxTelemetry.cpp97
-rw-r--r--gfx/src/gfxTelemetry.h93
-rw-r--r--gfx/src/moz.build97
-rw-r--r--gfx/src/nsBoundingMetrics.h83
-rw-r--r--gfx/src/nsColor.cpp262
-rw-r--r--gfx/src/nsColor.h94
-rw-r--r--gfx/src/nsColorNameList.h180
-rw-r--r--gfx/src/nsColorNames.h16
-rw-r--r--gfx/src/nsCoord.h393
-rw-r--r--gfx/src/nsDeviceContext.cpp717
-rw-r--r--gfx/src/nsDeviceContext.h310
-rw-r--r--gfx/src/nsFont.cpp289
-rw-r--r--gfx/src/nsFont.h128
-rw-r--r--gfx/src/nsFontMetrics.cpp383
-rw-r--r--gfx/src/nsFontMetrics.h276
-rw-r--r--gfx/src/nsGfxCIID.h31
-rw-r--r--gfx/src/nsIFontEnumerator.idl78
-rw-r--r--gfx/src/nsITheme.h239
-rw-r--r--gfx/src/nsMargin.h26
-rw-r--r--gfx/src/nsPoint.h95
-rw-r--r--gfx/src/nsRect.cpp34
-rw-r--r--gfx/src/nsRect.h493
-rw-r--r--gfx/src/nsRectAbsolute.h55
-rw-r--r--gfx/src/nsRegion.cpp1024
-rw-r--r--gfx/src/nsRegion.h2524
-rw-r--r--gfx/src/nsRegionFwd.h27
-rw-r--r--gfx/src/nsSize.h69
-rw-r--r--gfx/src/nsThebesFontEnumerator.cpp246
-rw-r--r--gfx/src/nsThebesFontEnumerator.h25
-rw-r--r--gfx/src/nsTransform2D.cpp22
-rw-r--r--gfx/src/nsTransform2D.h103
50 files changed, 13541 insertions, 0 deletions
diff --git a/gfx/src/AppUnits.h b/gfx/src/AppUnits.h
new file mode 100644
index 0000000000..2b00ec7d56
--- /dev/null
+++ b/gfx/src/AppUnits.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_AppUnits_h
+#define mozilla_AppUnits_h
+
+#include <stdint.h>
+
+namespace mozilla {
+constexpr inline int32_t AppUnitsPerCSSPixel() { return 60; }
+constexpr inline int32_t AppUnitsPerCSSInch() {
+ return 96 * AppUnitsPerCSSPixel();
+}
+} // namespace mozilla
+#endif /* _NS_APPUNITS_H_ */
diff --git a/gfx/src/ArrayView.h b/gfx/src/ArrayView.h
new file mode 100644
index 0000000000..e5d5ace976
--- /dev/null
+++ b/gfx/src/ArrayView.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_ARRAY_VIEW_H_
+#define MOZILLA_GFX_ARRAY_VIEW_H_
+
+#include "nsTArray.h"
+
+/* This is similar to mfbt/Range.h but has implicit conversion
+ * from nsTArray and less bounds checking.
+ * For now, prefer Range over ArrayView */
+
+namespace mozilla {
+namespace gfx {
+
+template <typename T>
+class ArrayView {
+ public:
+ MOZ_IMPLICIT ArrayView(const nsTArray<T>& aData)
+ : mData(aData.Elements()), mLength(aData.Length()) {}
+ ArrayView(const T* aData, const size_t aLength)
+ : mData(aData), mLength(aLength) {}
+ const T& operator[](const size_t aIdx) const { return mData[aIdx]; }
+ size_t Length() const { return mLength; }
+ const T* Data() const { return mData; }
+
+ private:
+ const T* mData;
+ const size_t mLength;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_ARRAY_VIEW_H_ */
diff --git a/gfx/src/CompositorHitTestInfo.h b/gfx/src/CompositorHitTestInfo.h
new file mode 100644
index 0000000000..f43fa7d3a0
--- /dev/null
+++ b/gfx/src/CompositorHitTestInfo.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_COMPOSITORHITTESTINFO_H_
+#define MOZILLA_GFX_COMPOSITORHITTESTINFO_H_
+
+#include "mozilla/EnumSet.h"
+#include "mozilla/EnumTypeTraits.h"
+
+namespace mozilla {
+namespace gfx {
+
+// This set of flags is used to figure out what information a frame has
+// that is relevant to hit-testing in the compositor. The flags are
+// intentionally set up so that if all of them are 0 the item is effectively
+// invisible to hit-testing, and no information for this frame needs to be
+// sent to the compositor.
+// Each enumerator is annotated with the value it contributes to an
+// EnumSet (2 ^ <value of enumerator>), in hexadecimal.
+enum class CompositorHitTestFlags : uint8_t {
+ // The frame participates in hit-testing
+ eVisibleToHitTest = 0, // 0x0001
+
+ // The frame may have odd shapes that requires the main thread to do accurate
+ // hit-testing.
+ eIrregularArea, // 0x0002
+ // The frame has APZ-aware listeners and so inputs targeted at this area
+ // need to be handled by the main thread before APZ can use them, as they
+ // might be prevent-defaulted.
+ eApzAwareListeners, // 0x0004
+ // This is an inactive scrollframe or unlayerized scrollthumb. In this state
+ // it cannot be used by APZ for async scrolling, so APZ will defer to the main
+ // thread.
+ eInactiveScrollframe, // 0x0008
+
+ // The touch action flags are set up so that the default of
+ // touch-action:auto on an element leaves all the flags as 0.
+ eTouchActionPanXDisabled, // 0x0010
+ eTouchActionPanYDisabled, // 0x0020
+ eTouchActionPinchZoomDisabled, // 0x0040
+ eTouchActionDoubleTapZoomDisabled, // 0x0080
+
+ // The frame is a scrollbar or a subframe inside a scrollbar (including
+ // scroll thumbs)
+ eScrollbar, // 0x0100
+ // The frame is a scrollthumb. If this is set then eScrollbar will also be
+ // set, unless gecko somehow generates a scroll thumb without a containing
+ // scrollbar.
+ eScrollbarThumb, // 0x0200
+ // If eScrollbar is set, this flag indicates if the scrollbar is a vertical
+ // one (if set) or a horizontal one (if not set)
+ eScrollbarVertical, // 0x0400
+
+ // Events targeting this frame should only be processed if a target
+ // confirmation is received from the main thread. If no such confirmation
+ // is received within a timeout period, the event may be dropped.
+ // Only meaningful in combination with eDispatchToContent.
+ eRequiresTargetConfirmation, // 0x0800
+
+ // Bits 0x1000, 0x2000, 0x4000, and 0x8000 are used by SideBitsPacked in
+ // WebRenderAPI.cpp when we pack SideBits and CompositorHitTestInfo into a
+ // uint16_t and pass in into webrender to store in the item tag.
+
+};
+
+using CompositorHitTestInfo = EnumSet<CompositorHitTestFlags, uint32_t>;
+
+// A CompositorHitTestInfo with none of the flags set
+constexpr CompositorHitTestInfo CompositorHitTestInvisibleToHit;
+
+// Mask to check for all the touch-action flags at once
+constexpr CompositorHitTestInfo CompositorHitTestTouchActionMask(
+ CompositorHitTestFlags::eTouchActionPanXDisabled,
+ CompositorHitTestFlags::eTouchActionPanYDisabled,
+ CompositorHitTestFlags::eTouchActionPinchZoomDisabled,
+ CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled);
+
+// Mask to check all the flags that involve APZ waiting for results from the
+// main thread
+constexpr CompositorHitTestInfo CompositorHitTestDispatchToContent(
+ CompositorHitTestFlags::eIrregularArea,
+ CompositorHitTestFlags::eApzAwareListeners,
+ CompositorHitTestFlags::eInactiveScrollframe);
+
+} // namespace gfx
+
+// Used for IPDL serialization. The 'value' have to be the biggest enum from
+// CompositorHitTestFlags.
+template <>
+struct MaxEnumValue<::mozilla::gfx::CompositorHitTestFlags> {
+ static constexpr unsigned int value = static_cast<unsigned int>(
+ gfx::CompositorHitTestFlags::eRequiresTargetConfirmation);
+};
+
+namespace gfx {
+
+// Checks if the CompositorHitTestFlags max enum value is less than N.
+template <int N>
+static constexpr bool DoesCompositorHitTestInfoFitIntoBits() {
+ if (MaxEnumValue<CompositorHitTestInfo::valueType>::value < N) {
+ return true;
+ }
+
+ return false;
+}
+} // namespace gfx
+
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_COMPOSITORHITTESTINFO_H_ */
diff --git a/gfx/src/DriverCrashGuard.cpp b/gfx/src/DriverCrashGuard.cpp
new file mode 100644
index 0000000000..c8ae375f71
--- /dev/null
+++ b/gfx/src/DriverCrashGuard.cpp
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "DriverCrashGuard.h"
+#include "gfxEnv.h"
+#include "gfxConfig.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Services.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace gfx {
+
+static const size_t NUM_CRASH_GUARD_TYPES = size_t(CrashGuardType::NUM_TYPES);
+static const char* sCrashGuardNames[] = {
+ "d3d11layers",
+ "glcontext",
+ "wmfvpxvideo",
+};
+static_assert(MOZ_ARRAY_LENGTH(sCrashGuardNames) == NUM_CRASH_GUARD_TYPES,
+ "CrashGuardType updated without a name string");
+
+static inline void BuildCrashGuardPrefName(CrashGuardType aType,
+ nsCString& aOutPrefName) {
+ MOZ_ASSERT(aType < CrashGuardType::NUM_TYPES);
+ MOZ_ASSERT(sCrashGuardNames[size_t(aType)]);
+
+ aOutPrefName.AssignLiteral("gfx.crash-guard.status.");
+ aOutPrefName.Append(sCrashGuardNames[size_t(aType)]);
+}
+
+DriverCrashGuard::DriverCrashGuard(CrashGuardType aType,
+ dom::ContentParent* aContentParent)
+ : mType(aType),
+ mMode(aContentParent ? Mode::Proxy : Mode::Normal),
+ mInitialized(false),
+ mGuardActivated(false),
+ mCrashDetected(false) {
+ BuildCrashGuardPrefName(aType, mStatusPref);
+}
+
+void DriverCrashGuard::InitializeIfNeeded() {
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
+ Initialize();
+}
+
+static inline bool AreCrashGuardsEnabled(CrashGuardType aType) {
+ // Crash guard isn't supported in the GPU or RDD process since the entire
+ // process is basically a crash guard.
+ if (XRE_IsGPUProcess() || XRE_IsRDDProcess()) {
+ return false;
+ }
+#ifdef NIGHTLY_BUILD
+ // We only use the crash guard on non-nightly channels, since the nightly
+ // channel is for development and having graphics features perma-disabled
+ // is rather annoying. Unless the user forces is with an environment
+ // variable, which comes in handy for testing.
+ // We handle the WMFVPXVideo crash guard differently to the other and always
+ // enable it as it completely breaks playback and there's no way around it.
+ if (aType != CrashGuardType::WMFVPXVideo) {
+ return gfxEnv::ForceCrashGuardNightly();
+ }
+#endif
+ // Check to see if all guards have been disabled through the environment.
+ return !gfxEnv::DisableCrashGuard();
+}
+
+void DriverCrashGuard::Initialize() {
+ if (!AreCrashGuardsEnabled(mType)) {
+ return;
+ }
+
+ // Using DriverCrashGuard off the main thread currently does not work. Under
+ // e10s it could conceivably work by dispatching the IPC calls via the main
+ // thread. In the parent process this would be harder. For now, we simply
+ // exit early instead.
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ mGfxInfo = services::GetGfxInfo();
+
+ if (XRE_IsContentProcess()) {
+ // Ask the parent whether or not activating the guard is okay. The parent
+ // won't bother if it detected a crash.
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ cc->SendBeginDriverCrashGuard(uint32_t(mType), &mCrashDetected);
+ if (mCrashDetected) {
+ LogFeatureDisabled();
+ return;
+ }
+
+ ActivateGuard();
+ return;
+ }
+
+ // Always check whether or not the lock file exists. For example, we could
+ // have crashed creating a D3D9 device in the parent process, and on restart
+ // are now requesting one in the child process. We catch everything here.
+ if (RecoverFromCrash()) {
+ mCrashDetected = true;
+ return;
+ }
+
+ // If the environment has changed, we always activate the guard. In the
+ // parent process this performs main-thread disk I/O. Child process guards
+ // only incur an IPC cost, so if we're proxying for a child process, we
+ // play it safe and activate the guard as long as we don't expect it to
+ // crash.
+ if (CheckOrRefreshEnvironment() ||
+ (mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed)) {
+ ActivateGuard();
+ return;
+ }
+
+ // If we got here and our status is "crashed", then the environment has not
+ // updated and we do not want to attempt to use the driver again.
+ if (GetStatus() == DriverInitStatus::Crashed) {
+ mCrashDetected = true;
+ LogFeatureDisabled();
+ }
+}
+
+DriverCrashGuard::~DriverCrashGuard() {
+ if (!mGuardActivated) {
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (mGuardFile) {
+ mGuardFile->Remove(false);
+ }
+
+ // If during our initialization, no other process encountered a crash, we
+ // proceed to mark the status as okay.
+ if (GetStatus() != DriverInitStatus::Crashed) {
+ SetStatus(DriverInitStatus::Okay);
+ }
+ } else {
+ dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType));
+ }
+
+ CrashReporter::RemoveCrashReportAnnotation(
+ CrashReporter::Annotation::GraphicsStartupTest);
+}
+
+bool DriverCrashGuard::Crashed() {
+ InitializeIfNeeded();
+
+ // Note, we read mCrashDetected instead of GetStatus(), since in child
+ // processes we're not guaranteed that the prefs have been synced in
+ // time.
+ return mCrashDetected;
+}
+
+nsCOMPtr<nsIFile> DriverCrashGuard::GetGuardFile() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCString filename;
+ filename.Assign(sCrashGuardNames[size_t(mType)]);
+ filename.AppendLiteral(".guard");
+
+ nsCOMPtr<nsIFile> file;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(file));
+ if (!file) {
+ return nullptr;
+ }
+ if (!NS_SUCCEEDED(file->AppendNative(filename))) {
+ return nullptr;
+ }
+ return file;
+}
+
+void DriverCrashGuard::ActivateGuard() {
+ mGuardActivated = true;
+
+ // Anotate crash reports only if we're a real guard. Otherwise, we could
+ // attribute a random parent process crash to a graphics problem in a child
+ // process.
+ if (mMode != Mode::Proxy) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::GraphicsStartupTest, true);
+ }
+
+ // If we're in the content process, the rest of the guarding is handled
+ // in the parent.
+ if (XRE_IsContentProcess()) {
+ return;
+ }
+
+ SetStatus(DriverInitStatus::Attempting);
+
+ if (mMode != Mode::Proxy) {
+ // In parent process guards, we use two tombstones to detect crashes: a
+ // preferences and a zero-byte file on the filesystem.
+ FlushPreferences();
+
+ // Create a temporary tombstone/lockfile.
+ FILE* fp = nullptr;
+ mGuardFile = GetGuardFile();
+ if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) {
+ return;
+ }
+ fclose(fp);
+ }
+}
+
+void DriverCrashGuard::NotifyCrashed() {
+ SetStatus(DriverInitStatus::Crashed);
+ FlushPreferences();
+ LogCrashRecovery();
+}
+
+bool DriverCrashGuard::RecoverFromCrash() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIFile> file = GetGuardFile();
+ bool exists;
+ if ((file && NS_SUCCEEDED(file->Exists(&exists)) && exists) ||
+ (GetStatus() == DriverInitStatus::Attempting)) {
+ // If we get here, we've just recovered from a crash. Disable acceleration
+ // until the environment changes.
+ if (file) {
+ file->Remove(false);
+ }
+ NotifyCrashed();
+ return true;
+ }
+ return false;
+}
+
+// Return true if the caller should proceed to guard for crashes. False if
+// the environment has not changed. We persist the "changed" status across
+// calls, so that after an environment changes, all guards for the new
+// session are activated rather than just the first.
+bool DriverCrashGuard::CheckOrRefreshEnvironment() {
+ // Our result can be cached statically since we don't check live prefs.
+ // We need to cache once per crash guard type.
+ // The first call to CheckOrRefrechEnvironment will always return true should
+ // the configuration had changed, following calls will return false.
+ static bool sBaseInfoChanged[NUM_CRASH_GUARD_TYPES];
+ static bool sBaseInfoChecked[NUM_CRASH_GUARD_TYPES];
+
+ const uint32_t type = uint32_t(mType);
+ if (!sBaseInfoChecked[type]) {
+ // None of the prefs we care about, so we cache the result statically.
+ sBaseInfoChecked[type] = true;
+ sBaseInfoChanged[type] = UpdateBaseEnvironment();
+ }
+
+ // Always update the full environment, even if the base info didn't change.
+ bool result = UpdateEnvironment() || sBaseInfoChanged[type] ||
+ GetStatus() == DriverInitStatus::Unknown;
+ sBaseInfoChanged[type] = false;
+ return result;
+}
+
+bool DriverCrashGuard::UpdateBaseEnvironment() {
+ bool changed = false;
+ if (mGfxInfo) {
+ nsString value;
+
+ // Driver properties.
+ mGfxInfo->GetAdapterDriverVersion(value);
+ changed |= CheckAndUpdatePref("driverVersion", value);
+ mGfxInfo->GetAdapterDeviceID(value);
+ changed |= CheckAndUpdatePref("deviceID", value);
+ }
+
+ // Firefox properties.
+ changed |= CheckAndUpdatePref(
+ "appVersion", NS_LITERAL_STRING_FROM_CSTRING(MOZ_APP_VERSION));
+
+ return changed;
+}
+
+bool DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault) {
+ if (!mGfxInfo) {
+ return aDefault;
+ }
+ int32_t status;
+ nsCString discardFailureId;
+ if (!NS_SUCCEEDED(
+ mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) {
+ return false;
+ }
+ return status == nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+bool DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName,
+ bool aCurrentValue) {
+ std::string pref = GetFullPrefName(aPrefName);
+
+ bool oldValue;
+ if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) &&
+ oldValue == aCurrentValue) {
+ return false;
+ }
+ Preferences::SetBool(pref.c_str(), aCurrentValue);
+ return true;
+}
+
+bool DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName,
+ const nsAString& aCurrentValue) {
+ std::string pref = GetFullPrefName(aPrefName);
+
+ nsAutoString oldValue;
+ Preferences::GetString(pref.c_str(), oldValue);
+ if (oldValue == aCurrentValue) {
+ return false;
+ }
+ Preferences::SetString(pref.c_str(), aCurrentValue);
+ return true;
+}
+
+std::string DriverCrashGuard::GetFullPrefName(const char* aPref) {
+ return std::string("gfx.crash-guard.") +
+ std::string(sCrashGuardNames[uint32_t(mType)]) + std::string(".") +
+ std::string(aPref);
+}
+
+DriverInitStatus DriverCrashGuard::GetStatus() const {
+ return (DriverInitStatus)Preferences::GetInt(mStatusPref.get(), 0);
+}
+
+void DriverCrashGuard::SetStatus(DriverInitStatus aStatus) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ Preferences::SetInt(mStatusPref.get(), int32_t(aStatus));
+}
+
+void DriverCrashGuard::FlushPreferences() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (nsIPrefService* prefService = Preferences::GetService()) {
+ static_cast<Preferences*>(prefService)->SavePrefFileBlocking();
+ }
+}
+
+void DriverCrashGuard::ForEachActiveCrashGuard(
+ const CrashGuardCallback& aCallback) {
+ for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) {
+ CrashGuardType type = static_cast<CrashGuardType>(i);
+
+ if (!AreCrashGuardsEnabled(type)) {
+ // Even if guards look active (via prefs), they can be ignored if globally
+ // disabled.
+ continue;
+ }
+
+ nsCString prefName;
+ BuildCrashGuardPrefName(type, prefName);
+
+ auto status =
+ static_cast<DriverInitStatus>(Preferences::GetInt(prefName.get(), 0));
+ if (status != DriverInitStatus::Crashed) {
+ continue;
+ }
+
+ aCallback(sCrashGuardNames[i], prefName.get());
+ }
+}
+
+D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent) {}
+
+void D3D11LayersCrashGuard::Initialize() {
+ if (!XRE_IsParentProcess()) {
+ // We assume the parent process already performed crash detection for
+ // graphics devices.
+ return;
+ }
+
+ DriverCrashGuard::Initialize();
+
+ // If no telemetry states have been recorded, this will set the state to okay.
+ // Otherwise, it will have no effect.
+ RecordTelemetry(TelemetryState::Okay);
+}
+
+bool D3D11LayersCrashGuard::UpdateEnvironment() {
+ // Our result can be cached statically since we don't check live prefs.
+ static bool checked = false;
+
+ if (checked) {
+ // We no longer need to bypass the crash guard.
+ return false;
+ }
+
+ checked = true;
+
+ bool changed = false;
+ // Feature status.
+#if defined(XP_WIN)
+ bool d2dEnabled = StaticPrefs::gfx_direct2d_force_enabled_AtStartup() ||
+ (!StaticPrefs::gfx_direct2d_disabled_AtStartup() &&
+ FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
+ changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled);
+
+ bool d3d11Enabled = gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING);
+ changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled);
+ if (changed) {
+ RecordTelemetry(TelemetryState::EnvironmentChanged);
+ }
+#endif
+
+ return changed;
+}
+
+void D3D11LayersCrashGuard::LogCrashRecovery() {
+ RecordTelemetry(TelemetryState::RecoveredFromCrash);
+ gfxCriticalNote << "D3D11 layers just crashed; D3D11 will be disabled.";
+}
+
+void D3D11LayersCrashGuard::LogFeatureDisabled() {
+ RecordTelemetry(TelemetryState::FeatureDisabled);
+ gfxCriticalNote << "D3D11 layers disabled due to a prior crash.";
+}
+
+void D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState) {
+ // D3D11LayersCrashGuard is a no-op in the child process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ // Since we instantiate this class more than once, make sure we only record
+ // the first state (since that is really all we care about).
+ static bool sTelemetryStateRecorded = false;
+ if (sTelemetryStateRecorded) {
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::GRAPHICS_DRIVER_STARTUP_TEST,
+ int32_t(aState));
+ sTelemetryStateRecorded = true;
+}
+
+GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::GLContext, aContentParent) {}
+
+void GLContextCrashGuard::Initialize() {
+ if (XRE_IsContentProcess()) {
+ // Disable the GL crash guard in content processes, since we're not going
+ // to lose the entire browser and we don't want to hinder WebGL
+ // availability.
+ return;
+ }
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // Disable the WebGL crash guard on Android - it doesn't use E10S, and
+ // its drivers will essentially never change, so the crash guard could
+ // permanently disable WebGL.
+ return;
+#endif
+
+ DriverCrashGuard::Initialize();
+}
+
+bool GLContextCrashGuard::UpdateEnvironment() {
+ static bool checked = false;
+
+ if (checked) {
+ // We no longer need to bypass the crash guard.
+ return false;
+ }
+
+ checked = true;
+
+ bool changed = false;
+
+#if defined(XP_WIN)
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11",
+ StaticPrefs::webgl_angle_force_d3d11());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11",
+ StaticPrefs::webgl_angle_try_d3d11());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp",
+ StaticPrefs::webgl_angle_force_warp());
+ changed |= CheckAndUpdateBoolPref(
+ "gfx.driver-init.webgl-angle",
+ FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE, false));
+ changed |= CheckAndUpdateBoolPref(
+ "gfx.driver-init.direct3d11-angle",
+ FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, false));
+#endif
+
+ return changed;
+}
+
+void GLContextCrashGuard::LogCrashRecovery() {
+ gfxCriticalNote << "GLContext just crashed.";
+}
+
+void GLContextCrashGuard::LogFeatureDisabled() {
+ gfxCriticalNote << "GLContext remains enabled despite a previous crash.";
+}
+
+WMFVPXVideoCrashGuard::WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::WMFVPXVideo, aContentParent) {}
+
+void WMFVPXVideoCrashGuard::LogCrashRecovery() {
+ gfxCriticalNote
+ << "WMF VPX decoder just crashed; hardware video will be disabled.";
+}
+
+void WMFVPXVideoCrashGuard::LogFeatureDisabled() {
+ gfxCriticalNote
+ << "WMF VPX video decoding is disabled due to a previous crash.";
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/src/DriverCrashGuard.h b/gfx/src/DriverCrashGuard.h
new file mode 100644
index 0000000000..1cb8d78d56
--- /dev/null
+++ b/gfx/src/DriverCrashGuard.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#ifndef gfx_src_DriverCrashGuard_h__
+#define gfx_src_DriverCrashGuard_h__
+
+#include "nsCOMPtr.h"
+#include "nsIGfxInfo.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include <functional>
+#include <string>
+
+namespace mozilla {
+
+namespace dom {
+class ContentParent;
+} // namespace dom
+
+namespace gfx {
+
+enum class DriverInitStatus {
+ // Drivers have not been initialized yet.
+ Unknown,
+
+ // We're attempting to initialize drivers.
+ Attempting,
+
+ // Drivers were successfully initialized last run.
+ Okay,
+
+ // We crashed during driver initialization, and have restarted.
+ Crashed
+};
+
+enum class CrashGuardType : uint32_t {
+ D3D11Layers,
+ GLContext,
+ WMFVPXVideo,
+ // Add new entries above this line, update the name array in
+ // DriverCrashGuard.cpp, make sure to add an entry in ContentParent.cpp,
+ // and add a fluent identifier in aboutSupport.ftl.
+
+ NUM_TYPES
+};
+
+// DriverCrashGuard is used to detect crashes at graphics driver callsites.
+//
+// If the graphics environment is unrecognized or has changed since the last
+// session, the crash guard will activate and will detect any crashes within
+// the scope of the guard object.
+//
+// If a callsite has a previously encountered crash, and the environment has
+// not changed since the last session, then the guard will set a status flag
+// indicating that the driver should not be used.
+class DriverCrashGuard {
+ public:
+ DriverCrashGuard(CrashGuardType aType, dom::ContentParent* aContentParent);
+ virtual ~DriverCrashGuard();
+
+ bool Crashed();
+ void NotifyCrashed();
+
+ // These are the values reported to Telemetry (GRAPHICS_DRIVER_STARTUP_TEST).
+ // Values should not change; add new values to the end.
+ enum class TelemetryState {
+ Okay = 0,
+ EnvironmentChanged = 1,
+ RecoveredFromCrash = 2,
+ FeatureDisabled = 3
+ };
+
+ enum class Mode {
+ // Normal operation.
+ Normal,
+
+ // Acting as a proxy between the parent and child process.
+ Proxy
+ };
+
+ typedef std::function<void(const char* aName, const char* aPrefName)>
+ CrashGuardCallback;
+ static void ForEachActiveCrashGuard(const CrashGuardCallback& aCallback);
+
+ protected:
+ virtual void Initialize();
+ // UpdateEnvironment needs to return true should we need to attempt the
+ // operation once again.
+ // It should return true once only so that in case of a crash, we won't
+ // needlessly attempt the operation over and over again leading to continual
+ // crashes. several times
+ virtual bool UpdateEnvironment() {
+ // We don't care about any extra preferences here.
+ return false;
+ }
+ virtual void LogCrashRecovery() = 0;
+ virtual void LogFeatureDisabled() = 0;
+
+ // Helper functions.
+ bool FeatureEnabled(int aFeature, bool aDefault = true);
+ bool CheckAndUpdatePref(const char* aPrefName,
+ const nsAString& aCurrentValue);
+ bool CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue);
+ std::string GetFullPrefName(const char* aPref);
+
+ private:
+ // Either process.
+ void InitializeIfNeeded();
+ bool CheckOrRefreshEnvironment();
+ bool UpdateBaseEnvironment();
+ DriverInitStatus GetStatus() const;
+
+ // Parent process only.
+ nsCOMPtr<nsIFile> GetGuardFile();
+ bool RecoverFromCrash();
+ void ActivateGuard();
+ void FlushPreferences();
+ void SetStatus(DriverInitStatus aStatus);
+
+ private:
+ CrashGuardType mType;
+ Mode mMode;
+ bool mInitialized;
+ bool mGuardActivated;
+ bool mCrashDetected;
+ nsCOMPtr<nsIFile> mGuardFile;
+
+ protected:
+ nsCString mStatusPref;
+ nsCOMPtr<nsIGfxInfo> mGfxInfo;
+};
+
+class D3D11LayersCrashGuard final : public DriverCrashGuard {
+ public:
+ explicit D3D11LayersCrashGuard(dom::ContentParent* aContentParent = nullptr);
+
+ protected:
+ void Initialize() override;
+ bool UpdateEnvironment() override;
+ void LogCrashRecovery() override;
+ void LogFeatureDisabled() override;
+
+ private:
+ void RecordTelemetry(TelemetryState aState);
+};
+
+class GLContextCrashGuard final : public DriverCrashGuard {
+ public:
+ explicit GLContextCrashGuard(dom::ContentParent* aContentParent = nullptr);
+ void Initialize() override;
+
+ protected:
+ bool UpdateEnvironment() override;
+ void LogCrashRecovery() override;
+ void LogFeatureDisabled() override;
+};
+
+class WMFVPXVideoCrashGuard final : public DriverCrashGuard {
+ public:
+ explicit WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent = nullptr);
+
+ protected:
+ void LogCrashRecovery() override;
+ void LogFeatureDisabled() override;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // gfx_src_DriverCrashGuard_h__
diff --git a/gfx/src/FilterDescription.h b/gfx/src/FilterDescription.h
new file mode 100644
index 0000000000..8c2ae12c98
--- /dev/null
+++ b/gfx/src/FilterDescription.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef __FilterDescription_h
+#define __FilterDescription_h
+
+#include "FilterSupport.h"
+#include "mozilla/Variant.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsTArray.h"
+
+namespace mozilla::gfx {
+class FilterPrimitiveDescription;
+}
+
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ mozilla::gfx::FilterPrimitiveDescription)
+
+namespace mozilla::gfx {
+typedef Variant<
+ EmptyAttributes, BlendAttributes, MorphologyAttributes,
+ ColorMatrixAttributes, FloodAttributes, TileAttributes,
+ ComponentTransferAttributes, OpacityAttributes, ConvolveMatrixAttributes,
+ OffsetAttributes, DisplacementMapAttributes, TurbulenceAttributes,
+ CompositeAttributes, MergeAttributes, ImageAttributes,
+ GaussianBlurAttributes, DropShadowAttributes, DiffuseLightingAttributes,
+ SpecularLightingAttributes, ToAlphaAttributes>
+ PrimitiveAttributes;
+
+/**
+ * A data structure to carry attributes for a given primitive that's part of a
+ * filter. Will be serializable via IPDL, so it must not contain complex
+ * functionality.
+ * Used as part of a FilterDescription.
+ */
+class FilterPrimitiveDescription final {
+ public:
+ enum {
+ kPrimitiveIndexSourceGraphic = -1,
+ kPrimitiveIndexSourceAlpha = -2,
+ kPrimitiveIndexFillPaint = -3,
+ kPrimitiveIndexStrokePaint = -4
+ };
+
+ FilterPrimitiveDescription();
+ explicit FilterPrimitiveDescription(PrimitiveAttributes&& aAttributes);
+ FilterPrimitiveDescription(FilterPrimitiveDescription&& aOther) = default;
+ FilterPrimitiveDescription& operator=(FilterPrimitiveDescription&& aOther) =
+ default;
+ FilterPrimitiveDescription(const FilterPrimitiveDescription& aOther)
+ : mAttributes(aOther.mAttributes),
+ mInputPrimitives(aOther.mInputPrimitives.Clone()),
+ mFilterPrimitiveSubregion(aOther.mFilterPrimitiveSubregion),
+ mFilterSpaceBounds(aOther.mFilterSpaceBounds),
+ mInputColorSpaces(aOther.mInputColorSpaces.Clone()),
+ mOutputColorSpace(aOther.mOutputColorSpace),
+ mIsTainted(aOther.mIsTainted) {}
+
+ const PrimitiveAttributes& Attributes() const { return mAttributes; }
+ PrimitiveAttributes& Attributes() { return mAttributes; }
+
+ IntRect PrimitiveSubregion() const { return mFilterPrimitiveSubregion; }
+ IntRect FilterSpaceBounds() const { return mFilterSpaceBounds; }
+ bool IsTainted() const { return mIsTainted; }
+
+ size_t NumberOfInputs() const { return mInputPrimitives.Length(); }
+ int32_t InputPrimitiveIndex(size_t aInputIndex) const {
+ return aInputIndex < mInputPrimitives.Length()
+ ? mInputPrimitives[aInputIndex]
+ : 0;
+ }
+
+ ColorSpace InputColorSpace(size_t aInputIndex) const {
+ return aInputIndex < mInputColorSpaces.Length()
+ ? mInputColorSpaces[aInputIndex]
+ : ColorSpace();
+ }
+
+ ColorSpace OutputColorSpace() const { return mOutputColorSpace; }
+
+ void SetPrimitiveSubregion(const IntRect& aRect) {
+ mFilterPrimitiveSubregion = aRect;
+ }
+
+ void SetFilterSpaceBounds(const IntRect& aRect) {
+ mFilterSpaceBounds = aRect;
+ }
+
+ void SetIsTainted(bool aIsTainted) { mIsTainted = aIsTainted; }
+
+ void SetInputPrimitive(size_t aInputIndex, int32_t aInputPrimitiveIndex) {
+ mInputPrimitives.EnsureLengthAtLeast(aInputIndex + 1);
+ mInputPrimitives[aInputIndex] = aInputPrimitiveIndex;
+ }
+
+ void SetInputColorSpace(size_t aInputIndex, ColorSpace aColorSpace) {
+ mInputColorSpaces.EnsureLengthAtLeast(aInputIndex + 1);
+ mInputColorSpaces[aInputIndex] = aColorSpace;
+ }
+
+ void SetOutputColorSpace(const ColorSpace& aColorSpace) {
+ mOutputColorSpace = aColorSpace;
+ }
+
+ bool operator==(const FilterPrimitiveDescription& aOther) const;
+ bool operator!=(const FilterPrimitiveDescription& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ PrimitiveAttributes mAttributes;
+ AutoTArray<int32_t, 2> mInputPrimitives;
+ IntRect mFilterPrimitiveSubregion;
+ IntRect mFilterSpaceBounds;
+ AutoTArray<ColorSpace, 2> mInputColorSpaces;
+ ColorSpace mOutputColorSpace;
+ bool mIsTainted;
+};
+
+/**
+ * A data structure that contains one or more FilterPrimitiveDescriptions.
+ * Designed to be serializable via IPDL, so it must not contain complex
+ * functionality.
+ */
+struct FilterDescription final {
+ FilterDescription() = default;
+ explicit FilterDescription(nsTArray<FilterPrimitiveDescription>&& aPrimitives)
+ : mPrimitives(std::move(aPrimitives)) {}
+
+ bool operator==(const FilterDescription& aOther) const;
+ bool operator!=(const FilterDescription& aOther) const {
+ return !(*this == aOther);
+ }
+
+ CopyableTArray<FilterPrimitiveDescription> mPrimitives;
+};
+} // namespace mozilla::gfx
+
+#endif // __FilterSupport_h
diff --git a/gfx/src/FilterSupport.cpp b/gfx/src/FilterSupport.cpp
new file mode 100644
index 0000000000..36facacd73
--- /dev/null
+++ b/gfx/src/FilterSupport.cpp
@@ -0,0 +1,1953 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FilterSupport.h"
+#include "FilterDescription.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Filters.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/PodOperations.h"
+
+#include "gfxContext.h"
+#include "gfxPattern.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "gfx2DGlue.h"
+
+#include "nsMargin.h"
+
+// c = n / 255
+// c <= 0.0031308f ? c * 12.92f : 1.055f * powf(c, 1 / 2.4f) - 0.055f
+static const float glinearRGBTosRGBMap[256] = {
+ 0.000f, 0.050f, 0.085f, 0.111f, 0.132f, 0.150f, 0.166f, 0.181f, 0.194f,
+ 0.207f, 0.219f, 0.230f, 0.240f, 0.250f, 0.260f, 0.269f, 0.278f, 0.286f,
+ 0.295f, 0.303f, 0.310f, 0.318f, 0.325f, 0.332f, 0.339f, 0.346f, 0.352f,
+ 0.359f, 0.365f, 0.371f, 0.378f, 0.383f, 0.389f, 0.395f, 0.401f, 0.406f,
+ 0.412f, 0.417f, 0.422f, 0.427f, 0.433f, 0.438f, 0.443f, 0.448f, 0.452f,
+ 0.457f, 0.462f, 0.466f, 0.471f, 0.476f, 0.480f, 0.485f, 0.489f, 0.493f,
+ 0.498f, 0.502f, 0.506f, 0.510f, 0.514f, 0.518f, 0.522f, 0.526f, 0.530f,
+ 0.534f, 0.538f, 0.542f, 0.546f, 0.549f, 0.553f, 0.557f, 0.561f, 0.564f,
+ 0.568f, 0.571f, 0.575f, 0.579f, 0.582f, 0.586f, 0.589f, 0.592f, 0.596f,
+ 0.599f, 0.603f, 0.606f, 0.609f, 0.613f, 0.616f, 0.619f, 0.622f, 0.625f,
+ 0.629f, 0.632f, 0.635f, 0.638f, 0.641f, 0.644f, 0.647f, 0.650f, 0.653f,
+ 0.656f, 0.659f, 0.662f, 0.665f, 0.668f, 0.671f, 0.674f, 0.677f, 0.680f,
+ 0.683f, 0.685f, 0.688f, 0.691f, 0.694f, 0.697f, 0.699f, 0.702f, 0.705f,
+ 0.708f, 0.710f, 0.713f, 0.716f, 0.718f, 0.721f, 0.724f, 0.726f, 0.729f,
+ 0.731f, 0.734f, 0.737f, 0.739f, 0.742f, 0.744f, 0.747f, 0.749f, 0.752f,
+ 0.754f, 0.757f, 0.759f, 0.762f, 0.764f, 0.767f, 0.769f, 0.772f, 0.774f,
+ 0.776f, 0.779f, 0.781f, 0.784f, 0.786f, 0.788f, 0.791f, 0.793f, 0.795f,
+ 0.798f, 0.800f, 0.802f, 0.805f, 0.807f, 0.809f, 0.812f, 0.814f, 0.816f,
+ 0.818f, 0.821f, 0.823f, 0.825f, 0.827f, 0.829f, 0.832f, 0.834f, 0.836f,
+ 0.838f, 0.840f, 0.843f, 0.845f, 0.847f, 0.849f, 0.851f, 0.853f, 0.855f,
+ 0.857f, 0.860f, 0.862f, 0.864f, 0.866f, 0.868f, 0.870f, 0.872f, 0.874f,
+ 0.876f, 0.878f, 0.880f, 0.882f, 0.884f, 0.886f, 0.888f, 0.890f, 0.892f,
+ 0.894f, 0.896f, 0.898f, 0.900f, 0.902f, 0.904f, 0.906f, 0.908f, 0.910f,
+ 0.912f, 0.914f, 0.916f, 0.918f, 0.920f, 0.922f, 0.924f, 0.926f, 0.928f,
+ 0.930f, 0.931f, 0.933f, 0.935f, 0.937f, 0.939f, 0.941f, 0.943f, 0.945f,
+ 0.946f, 0.948f, 0.950f, 0.952f, 0.954f, 0.956f, 0.957f, 0.959f, 0.961f,
+ 0.963f, 0.965f, 0.967f, 0.968f, 0.970f, 0.972f, 0.974f, 0.975f, 0.977f,
+ 0.979f, 0.981f, 0.983f, 0.984f, 0.986f, 0.988f, 0.990f, 0.991f, 0.993f,
+ 0.995f, 0.997f, 0.998f, 1.000f};
+
+// c = n / 255
+// c <= 0.04045f ? c / 12.92f : powf((c + 0.055f) / 1.055f, 2.4f)
+extern const float gsRGBToLinearRGBMap[256] = {
+ 0.000f, 0.000f, 0.001f, 0.001f, 0.001f, 0.002f, 0.002f, 0.002f, 0.002f,
+ 0.003f, 0.003f, 0.003f, 0.004f, 0.004f, 0.004f, 0.005f, 0.005f, 0.006f,
+ 0.006f, 0.007f, 0.007f, 0.007f, 0.008f, 0.009f, 0.009f, 0.010f, 0.010f,
+ 0.011f, 0.012f, 0.012f, 0.013f, 0.014f, 0.014f, 0.015f, 0.016f, 0.017f,
+ 0.018f, 0.019f, 0.019f, 0.020f, 0.021f, 0.022f, 0.023f, 0.024f, 0.025f,
+ 0.026f, 0.027f, 0.028f, 0.030f, 0.031f, 0.032f, 0.033f, 0.034f, 0.036f,
+ 0.037f, 0.038f, 0.040f, 0.041f, 0.042f, 0.044f, 0.045f, 0.047f, 0.048f,
+ 0.050f, 0.051f, 0.053f, 0.054f, 0.056f, 0.058f, 0.060f, 0.061f, 0.063f,
+ 0.065f, 0.067f, 0.068f, 0.070f, 0.072f, 0.074f, 0.076f, 0.078f, 0.080f,
+ 0.082f, 0.084f, 0.087f, 0.089f, 0.091f, 0.093f, 0.095f, 0.098f, 0.100f,
+ 0.102f, 0.105f, 0.107f, 0.109f, 0.112f, 0.114f, 0.117f, 0.120f, 0.122f,
+ 0.125f, 0.127f, 0.130f, 0.133f, 0.136f, 0.138f, 0.141f, 0.144f, 0.147f,
+ 0.150f, 0.153f, 0.156f, 0.159f, 0.162f, 0.165f, 0.168f, 0.171f, 0.175f,
+ 0.178f, 0.181f, 0.184f, 0.188f, 0.191f, 0.195f, 0.198f, 0.202f, 0.205f,
+ 0.209f, 0.212f, 0.216f, 0.220f, 0.223f, 0.227f, 0.231f, 0.235f, 0.238f,
+ 0.242f, 0.246f, 0.250f, 0.254f, 0.258f, 0.262f, 0.266f, 0.270f, 0.275f,
+ 0.279f, 0.283f, 0.287f, 0.292f, 0.296f, 0.301f, 0.305f, 0.309f, 0.314f,
+ 0.319f, 0.323f, 0.328f, 0.332f, 0.337f, 0.342f, 0.347f, 0.352f, 0.356f,
+ 0.361f, 0.366f, 0.371f, 0.376f, 0.381f, 0.386f, 0.392f, 0.397f, 0.402f,
+ 0.407f, 0.413f, 0.418f, 0.423f, 0.429f, 0.434f, 0.440f, 0.445f, 0.451f,
+ 0.456f, 0.462f, 0.468f, 0.474f, 0.479f, 0.485f, 0.491f, 0.497f, 0.503f,
+ 0.509f, 0.515f, 0.521f, 0.527f, 0.533f, 0.539f, 0.546f, 0.552f, 0.558f,
+ 0.565f, 0.571f, 0.578f, 0.584f, 0.591f, 0.597f, 0.604f, 0.610f, 0.617f,
+ 0.624f, 0.631f, 0.638f, 0.644f, 0.651f, 0.658f, 0.665f, 0.672f, 0.680f,
+ 0.687f, 0.694f, 0.701f, 0.708f, 0.716f, 0.723f, 0.730f, 0.738f, 0.745f,
+ 0.753f, 0.761f, 0.768f, 0.776f, 0.784f, 0.791f, 0.799f, 0.807f, 0.815f,
+ 0.823f, 0.831f, 0.839f, 0.847f, 0.855f, 0.863f, 0.871f, 0.880f, 0.888f,
+ 0.896f, 0.905f, 0.913f, 0.922f, 0.930f, 0.939f, 0.947f, 0.956f, 0.965f,
+ 0.973f, 0.982f, 0.991f, 1.000f};
+
+namespace mozilla {
+namespace gfx {
+
+// Some convenience FilterNode creation functions.
+
+namespace FilterWrappers {
+
+static already_AddRefed<FilterNode> Unpremultiply(DrawTarget* aDT,
+ FilterNode* aInput) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::UNPREMULTIPLY);
+ if (filter) {
+ filter->SetInput(IN_UNPREMULTIPLY_IN, aInput);
+ return filter.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> Premultiply(DrawTarget* aDT,
+ FilterNode* aInput) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::PREMULTIPLY);
+ if (filter) {
+ filter->SetInput(IN_PREMULTIPLY_IN, aInput);
+ return filter.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> LinearRGBToSRGB(DrawTarget* aDT,
+ FilterNode* aInput) {
+ RefPtr<FilterNode> transfer =
+ aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
+ if (transfer) {
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, glinearRGBTosRGBMap,
+ 256);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, glinearRGBTosRGBMap,
+ 256);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, glinearRGBTosRGBMap,
+ 256);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true);
+ transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput);
+ return transfer.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> SRGBToLinearRGB(DrawTarget* aDT,
+ FilterNode* aInput) {
+ RefPtr<FilterNode> transfer =
+ aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
+ if (transfer) {
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, gsRGBToLinearRGBMap,
+ 256);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, gsRGBToLinearRGBMap,
+ 256);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, gsRGBToLinearRGBMap,
+ 256);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true);
+ transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput);
+ return transfer.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> Crop(DrawTarget* aDT,
+ FilterNode* aInputFilter,
+ const IntRect& aRect) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::CROP);
+ if (filter) {
+ filter->SetAttribute(ATT_CROP_RECT, Rect(aRect));
+ filter->SetInput(IN_CROP_IN, aInputFilter);
+ return filter.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> Offset(DrawTarget* aDT,
+ FilterNode* aInputFilter,
+ const IntPoint& aOffset) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TRANSFORM);
+ if (filter) {
+ filter->SetAttribute(ATT_TRANSFORM_MATRIX,
+ Matrix::Translation(aOffset.x, aOffset.y));
+ filter->SetInput(IN_TRANSFORM_IN, aInputFilter);
+ return filter.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> GaussianBlur(DrawTarget* aDT,
+ FilterNode* aInputFilter,
+ const Size& aStdDeviation) {
+ float stdX = float(std::min(aStdDeviation.width, kMaxStdDeviation));
+ float stdY = float(std::min(aStdDeviation.height, kMaxStdDeviation));
+ if (stdX == stdY) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::GAUSSIAN_BLUR);
+ if (filter) {
+ filter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdX);
+ filter->SetInput(IN_GAUSSIAN_BLUR_IN, aInputFilter);
+ return filter.forget();
+ }
+ return nullptr;
+ }
+ RefPtr<FilterNode> filterH = aDT->CreateFilter(FilterType::DIRECTIONAL_BLUR);
+ RefPtr<FilterNode> filterV = aDT->CreateFilter(FilterType::DIRECTIONAL_BLUR);
+ if (filterH && filterV) {
+ filterH->SetAttribute(ATT_DIRECTIONAL_BLUR_DIRECTION,
+ (uint32_t)BLUR_DIRECTION_X);
+ filterH->SetAttribute(ATT_DIRECTIONAL_BLUR_STD_DEVIATION, stdX);
+ filterV->SetAttribute(ATT_DIRECTIONAL_BLUR_DIRECTION,
+ (uint32_t)BLUR_DIRECTION_Y);
+ filterV->SetAttribute(ATT_DIRECTIONAL_BLUR_STD_DEVIATION, stdY);
+ filterH->SetInput(IN_DIRECTIONAL_BLUR_IN, aInputFilter);
+ filterV->SetInput(IN_DIRECTIONAL_BLUR_IN, filterH);
+ return filterV.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<FilterNode> Clear(DrawTarget* aDT) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::FLOOD);
+ if (filter) {
+ filter->SetAttribute(ATT_FLOOD_COLOR, DeviceColor());
+ return filter.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<FilterNode> ForSurface(DrawTarget* aDT,
+ SourceSurface* aSurface,
+ const IntPoint& aSurfacePosition) {
+ RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TRANSFORM);
+ if (filter) {
+ filter->SetAttribute(
+ ATT_TRANSFORM_MATRIX,
+ Matrix::Translation(aSurfacePosition.x, aSurfacePosition.y));
+ filter->SetInput(IN_TRANSFORM_IN, aSurface);
+ return filter.forget();
+ }
+ return nullptr;
+}
+
+static already_AddRefed<FilterNode> ToAlpha(DrawTarget* aDT,
+ FilterNode* aInput) {
+ float zero = 0.0f;
+ RefPtr<FilterNode> transfer =
+ aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
+ if (transfer) {
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, &zero, 1);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, &zero, 1);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, &zero, 1);
+ transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true);
+ transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput);
+ return transfer.forget();
+ }
+ return nullptr;
+}
+
+} // namespace FilterWrappers
+
+// A class that wraps a FilterNode and handles conversion between different
+// color models. Create FilterCachedColorModels with your original filter and
+// the color model that this filter outputs in natively, and then call
+// ->ForColorModel(colorModel) in order to get a FilterNode which outputs to
+// the specified colorModel.
+// Internally, this is achieved by wrapping the original FilterNode with
+// conversion FilterNodes. These filter nodes are cached in such a way that no
+// repeated or back-and-forth conversions happen.
+class FilterCachedColorModels {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(FilterCachedColorModels)
+ // aFilter can be null. In that case, ForColorModel will return a non-null
+ // completely transparent filter for all color models.
+ FilterCachedColorModels(DrawTarget* aDT, FilterNode* aFilter,
+ ColorModel aOriginalColorModel);
+
+ // Get a FilterNode for the specified color model, guaranteed to be non-null.
+ already_AddRefed<FilterNode> ForColorModel(ColorModel aColorModel);
+
+ AlphaModel OriginalAlphaModel() const {
+ return mOriginalColorModel.mAlphaModel;
+ }
+
+ private:
+ // Create the required FilterNode that will be cached by ForColorModel.
+ already_AddRefed<FilterNode> WrapForColorModel(ColorModel aColorModel);
+
+ RefPtr<DrawTarget> mDT;
+ ColorModel mOriginalColorModel;
+
+ // This array is indexed by ColorModel::ToIndex.
+ RefPtr<FilterNode> mFilterForColorModel[4];
+
+ ~FilterCachedColorModels() = default;
+};
+
+FilterCachedColorModels::FilterCachedColorModels(DrawTarget* aDT,
+ FilterNode* aFilter,
+ ColorModel aOriginalColorModel)
+ : mDT(aDT), mOriginalColorModel(aOriginalColorModel) {
+ if (aFilter) {
+ mFilterForColorModel[aOriginalColorModel.ToIndex()] = aFilter;
+ } else {
+ RefPtr<FilterNode> clear = FilterWrappers::Clear(aDT);
+ mFilterForColorModel[0] = clear;
+ mFilterForColorModel[1] = clear;
+ mFilterForColorModel[2] = clear;
+ mFilterForColorModel[3] = clear;
+ }
+}
+
+already_AddRefed<FilterNode> FilterCachedColorModels::ForColorModel(
+ ColorModel aColorModel) {
+ if (aColorModel == mOriginalColorModel) {
+ // Make sure to not call WrapForColorModel if our original filter node was
+ // null, because then we'd get an infinite recursion.
+ RefPtr<FilterNode> filter =
+ mFilterForColorModel[mOriginalColorModel.ToIndex()];
+ return filter.forget();
+ }
+
+ if (!mFilterForColorModel[aColorModel.ToIndex()]) {
+ mFilterForColorModel[aColorModel.ToIndex()] =
+ WrapForColorModel(aColorModel);
+ }
+ RefPtr<FilterNode> filter(mFilterForColorModel[aColorModel.ToIndex()]);
+ return filter.forget();
+}
+
+already_AddRefed<FilterNode> FilterCachedColorModels::WrapForColorModel(
+ ColorModel aColorModel) {
+ // Convert one aspect at a time and recurse.
+ // Conversions between premultiplied / unpremultiplied color channels for the
+ // same color space can happen directly.
+ // Conversions between different color spaces can only happen on
+ // unpremultiplied color channels.
+
+ if (aColorModel.mAlphaModel == AlphaModel::Premultiplied) {
+ RefPtr<FilterNode> unpre = ForColorModel(
+ ColorModel(aColorModel.mColorSpace, AlphaModel::Unpremultiplied));
+ return FilterWrappers::Premultiply(mDT, unpre);
+ }
+
+ MOZ_ASSERT(aColorModel.mAlphaModel == AlphaModel::Unpremultiplied);
+ if (aColorModel.mColorSpace == mOriginalColorModel.mColorSpace) {
+ RefPtr<FilterNode> premultiplied = ForColorModel(
+ ColorModel(aColorModel.mColorSpace, AlphaModel::Premultiplied));
+ return FilterWrappers::Unpremultiply(mDT, premultiplied);
+ }
+
+ RefPtr<FilterNode> unpremultipliedOriginal = ForColorModel(
+ ColorModel(mOriginalColorModel.mColorSpace, AlphaModel::Unpremultiplied));
+ if (aColorModel.mColorSpace == ColorSpace::LinearRGB) {
+ return FilterWrappers::SRGBToLinearRGB(mDT, unpremultipliedOriginal);
+ }
+ return FilterWrappers::LinearRGBToSRGB(mDT, unpremultipliedOriginal);
+}
+
+static const float identityMatrix[] = {1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0};
+
+// When aAmount == 0, the identity matrix is returned.
+// When aAmount == 1, aToMatrix is returned.
+// When aAmount > 1, an exaggerated version of aToMatrix is returned. This can
+// be useful in certain cases, such as producing a color matrix to oversaturate
+// an image.
+//
+// This function is a shortcut of a full matrix addition and a scalar multiply,
+// and it assumes that the following elements in aToMatrix are 0 and 1:
+// x x x 0 0
+// x x x 0 0
+// x x x 0 0
+// 0 0 0 1 0
+static void InterpolateFromIdentityMatrix(const float aToMatrix[20],
+ float aAmount, float aOutMatrix[20]) {
+ PodCopy(aOutMatrix, identityMatrix, 20);
+
+ float oneMinusAmount = 1 - aAmount;
+
+ aOutMatrix[0] = aAmount * aToMatrix[0] + oneMinusAmount;
+ aOutMatrix[1] = aAmount * aToMatrix[1];
+ aOutMatrix[2] = aAmount * aToMatrix[2];
+
+ aOutMatrix[5] = aAmount * aToMatrix[5];
+ aOutMatrix[6] = aAmount * aToMatrix[6] + oneMinusAmount;
+ aOutMatrix[7] = aAmount * aToMatrix[7];
+
+ aOutMatrix[10] = aAmount * aToMatrix[10];
+ aOutMatrix[11] = aAmount * aToMatrix[11];
+ aOutMatrix[12] = aAmount * aToMatrix[12] + oneMinusAmount;
+}
+
+// Create a 4x5 color matrix for the different ways to specify color matrices
+// in SVG.
+bool ComputeColorMatrix(const ColorMatrixAttributes& aMatrixAttributes,
+ float aOutMatrix[20]) {
+ // Luminance coefficients.
+ static const float lumR = 0.2126f;
+ static const float lumG = 0.7152f;
+ static const float lumB = 0.0722f;
+
+ static const float oneMinusLumR = 1 - lumR;
+ static const float oneMinusLumG = 1 - lumG;
+ static const float oneMinusLumB = 1 - lumB;
+
+ static const float luminanceToAlphaMatrix[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, lumR, lumG, lumB, 0, 0};
+
+ static const float saturateMatrix[] = {
+ lumR, lumG, lumB, 0, 0, lumR, lumG, lumB, 0, 0,
+ lumR, lumG, lumB, 0, 0, 0, 0, 0, 1, 0};
+
+ static const float sepiaMatrix[] = {
+ 0.393f, 0.769f, 0.189f, 0, 0, 0.349f, 0.686f, 0.168f, 0, 0,
+ 0.272f, 0.534f, 0.131f, 0, 0, 0, 0, 0, 1, 0};
+
+ // Hue rotate specific coefficients.
+ static const float hueRotateR = 0.143f;
+ static const float hueRotateG = 0.140f;
+ static const float hueRotateB = 0.283f;
+
+ switch (aMatrixAttributes.mType) {
+ case SVG_FECOLORMATRIX_TYPE_MATRIX: {
+ if (aMatrixAttributes.mValues.Length() != 20) {
+ return false;
+ }
+
+ PodCopy(aOutMatrix, aMatrixAttributes.mValues.Elements(), 20);
+ break;
+ }
+
+ case SVG_FECOLORMATRIX_TYPE_SATURATE: {
+ if (aMatrixAttributes.mValues.Length() != 1) {
+ return false;
+ }
+
+ float s = aMatrixAttributes.mValues[0];
+
+ if (s < 0) {
+ return false;
+ }
+
+ InterpolateFromIdentityMatrix(saturateMatrix, 1 - s, aOutMatrix);
+ break;
+ }
+
+ case SVG_FECOLORMATRIX_TYPE_HUE_ROTATE: {
+ if (aMatrixAttributes.mValues.Length() != 1) {
+ return false;
+ }
+
+ PodCopy(aOutMatrix, identityMatrix, 20);
+
+ float hueRotateValue = aMatrixAttributes.mValues[0];
+
+ float c = static_cast<float>(cos(hueRotateValue * M_PI / 180));
+ float s = static_cast<float>(sin(hueRotateValue * M_PI / 180));
+
+ aOutMatrix[0] = lumR + oneMinusLumR * c - lumR * s;
+ aOutMatrix[1] = lumG - lumG * c - lumG * s;
+ aOutMatrix[2] = lumB - lumB * c + oneMinusLumB * s;
+
+ aOutMatrix[5] = lumR - lumR * c + hueRotateR * s;
+ aOutMatrix[6] = lumG + oneMinusLumG * c + hueRotateG * s;
+ aOutMatrix[7] = lumB - lumB * c - hueRotateB * s;
+
+ aOutMatrix[10] = lumR - lumR * c - oneMinusLumR * s;
+ aOutMatrix[11] = lumG - lumG * c + lumG * s;
+ aOutMatrix[12] = lumB + oneMinusLumB * c + lumB * s;
+
+ break;
+ }
+
+ case SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA: {
+ PodCopy(aOutMatrix, luminanceToAlphaMatrix, 20);
+ break;
+ }
+
+ case SVG_FECOLORMATRIX_TYPE_SEPIA: {
+ if (aMatrixAttributes.mValues.Length() != 1) {
+ return false;
+ }
+
+ float amount = aMatrixAttributes.mValues[0];
+
+ if (amount < 0 || amount > 1) {
+ return false;
+ }
+
+ InterpolateFromIdentityMatrix(sepiaMatrix, amount, aOutMatrix);
+ break;
+ }
+
+ default: {
+ return false;
+ }
+ }
+
+ return !ArrayEqual(aOutMatrix, identityMatrix, 20);
+}
+
+static void DisableAllTransfers(FilterNode* aTransferFilterNode) {
+ aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_R, true);
+ aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_G, true);
+ aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_B, true);
+ aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_A, true);
+}
+
+// Called for one channel at a time.
+// This function creates the required FilterNodes on demand and tries to
+// merge conversions of different channels into the same FilterNode if
+// possible.
+// There's a mismatch between the way SVG and the Moz2D API handle transfer
+// functions: In SVG, it's possible to specify a different transfer function
+// type for each color channel, but in Moz2D, a given transfer function type
+// applies to all color channels.
+//
+// @param aFunctionAttributes The attributes of the transfer function for this
+// channel.
+// @param aChannel The color channel that this function applies to, where
+// 0 = red, 1 = green, 2 = blue, 3 = alpha
+// @param aDT The DrawTarget that the FilterNodes should be created for.
+// @param aTableTransfer Existing FilterNode holders (which may still be
+// null) that the resulting FilterNodes from this
+// function will be stored in.
+//
+static void ConvertComponentTransferFunctionToFilter(
+ const ComponentTransferAttributes& aFunctionAttributes, int32_t aInChannel,
+ int32_t aOutChannel, DrawTarget* aDT, RefPtr<FilterNode>& aTableTransfer,
+ RefPtr<FilterNode>& aDiscreteTransfer, RefPtr<FilterNode>& aLinearTransfer,
+ RefPtr<FilterNode>& aGammaTransfer) {
+ static const TransferAtts disableAtt[4] = {
+ ATT_TRANSFER_DISABLE_R, ATT_TRANSFER_DISABLE_G, ATT_TRANSFER_DISABLE_B,
+ ATT_TRANSFER_DISABLE_A};
+
+ RefPtr<FilterNode> filter;
+
+ uint32_t type = aFunctionAttributes.mTypes[aInChannel];
+
+ switch (type) {
+ case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: {
+ const nsTArray<float>& tableValues =
+ aFunctionAttributes.mValues[aInChannel];
+ if (tableValues.Length() < 2) return;
+
+ if (!aTableTransfer) {
+ aTableTransfer = aDT->CreateFilter(FilterType::TABLE_TRANSFER);
+ if (!aTableTransfer) {
+ return;
+ }
+ DisableAllTransfers(aTableTransfer);
+ }
+ filter = aTableTransfer;
+ static const TableTransferAtts tableAtt[4] = {
+ ATT_TABLE_TRANSFER_TABLE_R, ATT_TABLE_TRANSFER_TABLE_G,
+ ATT_TABLE_TRANSFER_TABLE_B, ATT_TABLE_TRANSFER_TABLE_A};
+ filter->SetAttribute(disableAtt[aOutChannel], false);
+ filter->SetAttribute(tableAtt[aOutChannel], &tableValues[0],
+ tableValues.Length());
+ break;
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: {
+ const nsTArray<float>& tableValues =
+ aFunctionAttributes.mValues[aInChannel];
+ if (tableValues.Length() < 1) return;
+
+ if (!aDiscreteTransfer) {
+ aDiscreteTransfer = aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
+ if (!aDiscreteTransfer) {
+ return;
+ }
+ DisableAllTransfers(aDiscreteTransfer);
+ }
+ filter = aDiscreteTransfer;
+ static const DiscreteTransferAtts tableAtt[4] = {
+ ATT_DISCRETE_TRANSFER_TABLE_R, ATT_DISCRETE_TRANSFER_TABLE_G,
+ ATT_DISCRETE_TRANSFER_TABLE_B, ATT_DISCRETE_TRANSFER_TABLE_A};
+ filter->SetAttribute(disableAtt[aOutChannel], false);
+ filter->SetAttribute(tableAtt[aOutChannel], &tableValues[0],
+ tableValues.Length());
+
+ break;
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: {
+ static const LinearTransferAtts slopeAtt[4] = {
+ ATT_LINEAR_TRANSFER_SLOPE_R, ATT_LINEAR_TRANSFER_SLOPE_G,
+ ATT_LINEAR_TRANSFER_SLOPE_B, ATT_LINEAR_TRANSFER_SLOPE_A};
+ static const LinearTransferAtts interceptAtt[4] = {
+ ATT_LINEAR_TRANSFER_INTERCEPT_R, ATT_LINEAR_TRANSFER_INTERCEPT_G,
+ ATT_LINEAR_TRANSFER_INTERCEPT_B, ATT_LINEAR_TRANSFER_INTERCEPT_A};
+ if (!aLinearTransfer) {
+ aLinearTransfer = aDT->CreateFilter(FilterType::LINEAR_TRANSFER);
+ if (!aLinearTransfer) {
+ return;
+ }
+ DisableAllTransfers(aLinearTransfer);
+ }
+ filter = aLinearTransfer;
+ filter->SetAttribute(disableAtt[aOutChannel], false);
+ const nsTArray<float>& slopeIntercept =
+ aFunctionAttributes.mValues[aInChannel];
+ float slope = slopeIntercept[kComponentTransferSlopeIndex];
+ float intercept = slopeIntercept[kComponentTransferInterceptIndex];
+ filter->SetAttribute(slopeAtt[aOutChannel], slope);
+ filter->SetAttribute(interceptAtt[aOutChannel], intercept);
+ break;
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: {
+ static const GammaTransferAtts amplitudeAtt[4] = {
+ ATT_GAMMA_TRANSFER_AMPLITUDE_R, ATT_GAMMA_TRANSFER_AMPLITUDE_G,
+ ATT_GAMMA_TRANSFER_AMPLITUDE_B, ATT_GAMMA_TRANSFER_AMPLITUDE_A};
+ static const GammaTransferAtts exponentAtt[4] = {
+ ATT_GAMMA_TRANSFER_EXPONENT_R, ATT_GAMMA_TRANSFER_EXPONENT_G,
+ ATT_GAMMA_TRANSFER_EXPONENT_B, ATT_GAMMA_TRANSFER_EXPONENT_A};
+ static const GammaTransferAtts offsetAtt[4] = {
+ ATT_GAMMA_TRANSFER_OFFSET_R, ATT_GAMMA_TRANSFER_OFFSET_G,
+ ATT_GAMMA_TRANSFER_OFFSET_B, ATT_GAMMA_TRANSFER_OFFSET_A};
+ if (!aGammaTransfer) {
+ aGammaTransfer = aDT->CreateFilter(FilterType::GAMMA_TRANSFER);
+ if (!aGammaTransfer) {
+ return;
+ }
+ DisableAllTransfers(aGammaTransfer);
+ }
+ filter = aGammaTransfer;
+ filter->SetAttribute(disableAtt[aOutChannel], false);
+ const nsTArray<float>& gammaValues =
+ aFunctionAttributes.mValues[aInChannel];
+ float amplitude = gammaValues[kComponentTransferAmplitudeIndex];
+ float exponent = gammaValues[kComponentTransferExponentIndex];
+ float offset = gammaValues[kComponentTransferOffsetIndex];
+ filter->SetAttribute(amplitudeAtt[aOutChannel], amplitude);
+ filter->SetAttribute(exponentAtt[aOutChannel], exponent);
+ filter->SetAttribute(offsetAtt[aOutChannel], offset);
+ break;
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
+ default:
+ break;
+ }
+}
+
+const int32_t kMorphologyMaxRadius = 100000;
+
+// Handle the different primitive description types and create the necessary
+// FilterNode(s) for each.
+// Returns nullptr for invalid filter primitives. This should be interpreted as
+// transparent black by the caller.
+// aSourceRegions contains the filter primitive subregions of the source
+// primitives; only needed for eTile primitives.
+// aInputImages carries additional surfaces that are used by eImage primitives.
+static already_AddRefed<FilterNode> FilterNodeFromPrimitiveDescription(
+ const FilterPrimitiveDescription& aDescription, DrawTarget* aDT,
+ nsTArray<RefPtr<FilterNode>>& aSources, nsTArray<IntRect>& aSourceRegions,
+ nsTArray<RefPtr<SourceSurface>>& aInputImages) {
+ struct PrimitiveAttributesMatcher {
+ PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription,
+ DrawTarget* aDT,
+ nsTArray<RefPtr<FilterNode>>& aSources,
+ nsTArray<IntRect>& aSourceRegions,
+ nsTArray<RefPtr<SourceSurface>>& aInputImages)
+ : mDescription(aDescription),
+ mDT(aDT),
+ mSources(aSources),
+ mSourceRegions(aSourceRegions),
+ mInputImages(aInputImages) {}
+
+ const FilterPrimitiveDescription& mDescription;
+ DrawTarget* mDT;
+ nsTArray<RefPtr<FilterNode>>& mSources;
+ nsTArray<IntRect>& mSourceRegions;
+ nsTArray<RefPtr<SourceSurface>>& mInputImages;
+
+ already_AddRefed<FilterNode> operator()(
+ const EmptyAttributes& aEmptyAttributes) {
+ return nullptr;
+ }
+
+ already_AddRefed<FilterNode> operator()(const BlendAttributes& aBlend) {
+ uint32_t mode = aBlend.mBlendMode;
+ RefPtr<FilterNode> filter;
+ if (mode == SVG_FEBLEND_MODE_UNKNOWN) {
+ return nullptr;
+ }
+ if (mode == SVG_FEBLEND_MODE_NORMAL) {
+ filter = mDT->CreateFilter(FilterType::COMPOSITE);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetInput(IN_COMPOSITE_IN_START, mSources[1]);
+ filter->SetInput(IN_COMPOSITE_IN_START + 1, mSources[0]);
+ } else {
+ filter = mDT->CreateFilter(FilterType::BLEND);
+ if (!filter) {
+ return nullptr;
+ }
+ static const uint8_t blendModes[SVG_FEBLEND_MODE_LUMINOSITY + 1] = {
+ 0,
+ 0,
+ BLEND_MODE_MULTIPLY,
+ BLEND_MODE_SCREEN,
+ BLEND_MODE_DARKEN,
+ BLEND_MODE_LIGHTEN,
+ BLEND_MODE_OVERLAY,
+ BLEND_MODE_COLOR_DODGE,
+ BLEND_MODE_COLOR_BURN,
+ BLEND_MODE_HARD_LIGHT,
+ BLEND_MODE_SOFT_LIGHT,
+ BLEND_MODE_DIFFERENCE,
+ BLEND_MODE_EXCLUSION,
+ BLEND_MODE_HUE,
+ BLEND_MODE_SATURATION,
+ BLEND_MODE_COLOR,
+ BLEND_MODE_LUMINOSITY};
+ filter->SetAttribute(ATT_BLEND_BLENDMODE, (uint32_t)blendModes[mode]);
+ // The correct input order for both software and D2D filters is flipped
+ // from our source order, so flip here.
+ filter->SetInput(IN_BLEND_IN, mSources[1]);
+ filter->SetInput(IN_BLEND_IN2, mSources[0]);
+ }
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const ColorMatrixAttributes& aMatrixAttributes) {
+ float colorMatrix[20];
+ if (!ComputeColorMatrix(aMatrixAttributes, colorMatrix)) {
+ RefPtr<FilterNode> filter(mSources[0]);
+ return filter.forget();
+ }
+
+ Matrix5x4 matrix(
+ colorMatrix[0], colorMatrix[5], colorMatrix[10], colorMatrix[15],
+ colorMatrix[1], colorMatrix[6], colorMatrix[11], colorMatrix[16],
+ colorMatrix[2], colorMatrix[7], colorMatrix[12], colorMatrix[17],
+ colorMatrix[3], colorMatrix[8], colorMatrix[13], colorMatrix[18],
+ colorMatrix[4], colorMatrix[9], colorMatrix[14], colorMatrix[19]);
+
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::COLOR_MATRIX);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_COLOR_MATRIX_MATRIX, matrix);
+ filter->SetAttribute(ATT_COLOR_MATRIX_ALPHA_MODE,
+ (uint32_t)ALPHA_MODE_STRAIGHT);
+ filter->SetInput(IN_COLOR_MATRIX_IN, mSources[0]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const MorphologyAttributes& aMorphology) {
+ Size radii = aMorphology.mRadii;
+ int32_t rx = radii.width;
+ int32_t ry = radii.height;
+
+ // Is one of the radii zero or negative, return the input image
+ if (rx <= 0 || ry <= 0) {
+ RefPtr<FilterNode> filter(mSources[0]);
+ return filter.forget();
+ }
+
+ // Clamp radii to prevent completely insane values:
+ rx = std::min(rx, kMorphologyMaxRadius);
+ ry = std::min(ry, kMorphologyMaxRadius);
+
+ MorphologyOperator op = aMorphology.mOperator == SVG_OPERATOR_ERODE
+ ? MORPHOLOGY_OPERATOR_ERODE
+ : MORPHOLOGY_OPERATOR_DILATE;
+
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::MORPHOLOGY);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_MORPHOLOGY_RADII, IntSize(rx, ry));
+ filter->SetAttribute(ATT_MORPHOLOGY_OPERATOR, (uint32_t)op);
+ filter->SetInput(IN_MORPHOLOGY_IN, mSources[0]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const FloodAttributes& aFlood) {
+ DeviceColor color = ToDeviceColor(aFlood.mColor);
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::FLOOD);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_FLOOD_COLOR, color);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const TileAttributes& aTile) {
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::TILE);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_TILE_SOURCE_RECT, mSourceRegions[0]);
+ filter->SetInput(IN_TILE_IN, mSources[0]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const ComponentTransferAttributes& aComponentTransfer) {
+ MOZ_ASSERT(aComponentTransfer.mTypes[0] !=
+ SVG_FECOMPONENTTRANSFER_SAME_AS_R);
+ MOZ_ASSERT(aComponentTransfer.mTypes[3] !=
+ SVG_FECOMPONENTTRANSFER_SAME_AS_R);
+
+ RefPtr<FilterNode> filters[4]; // one for each FILTER_*_TRANSFER type
+ for (int32_t i = 0; i < 4; i++) {
+ int32_t inputIndex = (aComponentTransfer.mTypes[i] ==
+ SVG_FECOMPONENTTRANSFER_SAME_AS_R) &&
+ (i < 3)
+ ? 0
+ : i;
+ ConvertComponentTransferFunctionToFilter(aComponentTransfer, inputIndex,
+ i, mDT, filters[0], filters[1],
+ filters[2], filters[3]);
+ }
+
+ // Connect all used filters nodes.
+ RefPtr<FilterNode> lastFilter = mSources[0];
+ for (int32_t i = 0; i < 4; i++) {
+ if (filters[i]) {
+ filters[i]->SetInput(0, lastFilter);
+ lastFilter = filters[i];
+ }
+ }
+
+ return lastFilter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const OpacityAttributes& aOpacity) {
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::OPACITY);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_OPACITY_VALUE, aOpacity.mOpacity);
+ filter->SetInput(IN_OPACITY_IN, mSources[0]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const ConvolveMatrixAttributes& aConvolveMatrix) {
+ RefPtr<FilterNode> filter =
+ mDT->CreateFilter(FilterType::CONVOLVE_MATRIX);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_SIZE,
+ aConvolveMatrix.mKernelSize);
+ const nsTArray<float>& matrix = aConvolveMatrix.mKernelMatrix;
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_MATRIX, matrix.Elements(),
+ matrix.Length());
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_DIVISOR,
+ aConvolveMatrix.mDivisor);
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_BIAS, aConvolveMatrix.mBias);
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_TARGET, aConvolveMatrix.mTarget);
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_SOURCE_RECT, mSourceRegions[0]);
+ uint32_t edgeMode = aConvolveMatrix.mEdgeMode;
+ static const uint8_t edgeModes[SVG_EDGEMODE_NONE + 1] = {
+ EDGE_MODE_NONE, // SVG_EDGEMODE_UNKNOWN
+ EDGE_MODE_DUPLICATE, // SVG_EDGEMODE_DUPLICATE
+ EDGE_MODE_WRAP, // SVG_EDGEMODE_WRAP
+ EDGE_MODE_NONE // SVG_EDGEMODE_NONE
+ };
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_EDGE_MODE,
+ (uint32_t)edgeModes[edgeMode]);
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH,
+ aConvolveMatrix.mKernelUnitLength);
+ filter->SetAttribute(ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA,
+ aConvolveMatrix.mPreserveAlpha);
+ filter->SetInput(IN_CONVOLVE_MATRIX_IN, mSources[0]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const OffsetAttributes& aOffset) {
+ return FilterWrappers::Offset(mDT, mSources[0], aOffset.mValue);
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const DisplacementMapAttributes& aDisplacementMap) {
+ RefPtr<FilterNode> filter =
+ mDT->CreateFilter(FilterType::DISPLACEMENT_MAP);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_DISPLACEMENT_MAP_SCALE, aDisplacementMap.mScale);
+ static const uint8_t channel[SVG_CHANNEL_A + 1] = {
+ COLOR_CHANNEL_R, // SVG_CHANNEL_UNKNOWN
+ COLOR_CHANNEL_R, // SVG_CHANNEL_R
+ COLOR_CHANNEL_G, // SVG_CHANNEL_G
+ COLOR_CHANNEL_B, // SVG_CHANNEL_B
+ COLOR_CHANNEL_A // SVG_CHANNEL_A
+ };
+ filter->SetAttribute(ATT_DISPLACEMENT_MAP_X_CHANNEL,
+ (uint32_t)channel[aDisplacementMap.mXChannel]);
+ filter->SetAttribute(ATT_DISPLACEMENT_MAP_Y_CHANNEL,
+ (uint32_t)channel[aDisplacementMap.mYChannel]);
+ filter->SetInput(IN_DISPLACEMENT_MAP_IN, mSources[0]);
+ filter->SetInput(IN_DISPLACEMENT_MAP_IN2, mSources[1]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const TurbulenceAttributes& aTurbulence) {
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::TURBULENCE);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_TURBULENCE_BASE_FREQUENCY,
+ aTurbulence.mBaseFrequency);
+ filter->SetAttribute(ATT_TURBULENCE_NUM_OCTAVES, aTurbulence.mOctaves);
+ filter->SetAttribute(ATT_TURBULENCE_STITCHABLE, aTurbulence.mStitchable);
+ filter->SetAttribute(ATT_TURBULENCE_SEED, (uint32_t)aTurbulence.mSeed);
+ static const uint8_t type[SVG_TURBULENCE_TYPE_TURBULENCE + 1] = {
+ TURBULENCE_TYPE_FRACTAL_NOISE, // SVG_TURBULENCE_TYPE_UNKNOWN
+ TURBULENCE_TYPE_FRACTAL_NOISE, // SVG_TURBULENCE_TYPE_FRACTALNOISE
+ TURBULENCE_TYPE_TURBULENCE // SVG_TURBULENCE_TYPE_TURBULENCE
+ };
+ filter->SetAttribute(ATT_TURBULENCE_TYPE,
+ (uint32_t)type[aTurbulence.mType]);
+ filter->SetAttribute(
+ ATT_TURBULENCE_RECT,
+ mDescription.PrimitiveSubregion() - aTurbulence.mOffset);
+ return FilterWrappers::Offset(mDT, filter, aTurbulence.mOffset);
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const CompositeAttributes& aComposite) {
+ RefPtr<FilterNode> filter;
+ uint32_t op = aComposite.mOperator;
+ if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) {
+ const nsTArray<float>& coefficients = aComposite.mCoefficients;
+ static const float allZero[4] = {0, 0, 0, 0};
+ filter = mDT->CreateFilter(FilterType::ARITHMETIC_COMBINE);
+ // All-zero coefficients sometimes occur in junk filters.
+ if (!filter || (coefficients.Length() == ArrayLength(allZero) &&
+ ArrayEqual(coefficients.Elements(), allZero,
+ ArrayLength(allZero)))) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_ARITHMETIC_COMBINE_COEFFICIENTS,
+ coefficients.Elements(), coefficients.Length());
+ filter->SetInput(IN_ARITHMETIC_COMBINE_IN, mSources[0]);
+ filter->SetInput(IN_ARITHMETIC_COMBINE_IN2, mSources[1]);
+ } else {
+ filter = mDT->CreateFilter(FilterType::COMPOSITE);
+ if (!filter) {
+ return nullptr;
+ }
+ static const uint8_t operators[SVG_FECOMPOSITE_OPERATOR_LIGHTER + 1] = {
+ COMPOSITE_OPERATOR_OVER, // SVG_FECOMPOSITE_OPERATOR_UNKNOWN
+ COMPOSITE_OPERATOR_OVER, // SVG_FECOMPOSITE_OPERATOR_OVER
+ COMPOSITE_OPERATOR_IN, // SVG_FECOMPOSITE_OPERATOR_IN
+ COMPOSITE_OPERATOR_OUT, // SVG_FECOMPOSITE_OPERATOR_OUT
+ COMPOSITE_OPERATOR_ATOP, // SVG_FECOMPOSITE_OPERATOR_ATOP
+ COMPOSITE_OPERATOR_XOR, // SVG_FECOMPOSITE_OPERATOR_XOR
+ COMPOSITE_OPERATOR_OVER, // Unused, arithmetic is handled above
+ COMPOSITE_OPERATOR_LIGHTER // SVG_FECOMPOSITE_OPERATOR_LIGHTER
+ };
+ filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)operators[op]);
+ filter->SetInput(IN_COMPOSITE_IN_START, mSources[1]);
+ filter->SetInput(IN_COMPOSITE_IN_START + 1, mSources[0]);
+ }
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const MergeAttributes& aMerge) {
+ if (mSources.Length() == 0) {
+ return nullptr;
+ }
+ if (mSources.Length() == 1) {
+ RefPtr<FilterNode> filter(mSources[0]);
+ return filter.forget();
+ }
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::COMPOSITE);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_COMPOSITE_OPERATOR,
+ (uint32_t)COMPOSITE_OPERATOR_OVER);
+ for (size_t i = 0; i < mSources.Length(); i++) {
+ filter->SetInput(IN_COMPOSITE_IN_START + i, mSources[i]);
+ }
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const GaussianBlurAttributes& aGaussianBlur) {
+ return FilterWrappers::GaussianBlur(mDT, mSources[0],
+ aGaussianBlur.mStdDeviation);
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const DropShadowAttributes& aDropShadow) {
+ RefPtr<FilterNode> alpha = FilterWrappers::ToAlpha(mDT, mSources[0]);
+ RefPtr<FilterNode> blur =
+ FilterWrappers::GaussianBlur(mDT, alpha, aDropShadow.mStdDeviation);
+ RefPtr<FilterNode> offsetBlur = FilterWrappers::Offset(
+ mDT, blur, IntPoint::Truncate(aDropShadow.mOffset));
+ RefPtr<FilterNode> flood = mDT->CreateFilter(FilterType::FLOOD);
+ if (!flood) {
+ return nullptr;
+ }
+ sRGBColor color = aDropShadow.mColor;
+ if (mDescription.InputColorSpace(0) == ColorSpace::LinearRGB) {
+ color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
+ gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
+ gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
+ }
+ flood->SetAttribute(ATT_FLOOD_COLOR, ToDeviceColor(color));
+
+ RefPtr<FilterNode> composite = mDT->CreateFilter(FilterType::COMPOSITE);
+ if (!composite) {
+ return nullptr;
+ }
+ composite->SetAttribute(ATT_COMPOSITE_OPERATOR,
+ (uint32_t)COMPOSITE_OPERATOR_IN);
+ composite->SetInput(IN_COMPOSITE_IN_START, offsetBlur);
+ composite->SetInput(IN_COMPOSITE_IN_START + 1, flood);
+
+ RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::COMPOSITE);
+ if (!filter) {
+ return nullptr;
+ }
+ filter->SetAttribute(ATT_COMPOSITE_OPERATOR,
+ (uint32_t)COMPOSITE_OPERATOR_OVER);
+ filter->SetInput(IN_COMPOSITE_IN_START, composite);
+ filter->SetInput(IN_COMPOSITE_IN_START + 1, mSources[0]);
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const SpecularLightingAttributes& aLighting) {
+ return operator()(
+ *(static_cast<const DiffuseLightingAttributes*>(&aLighting)));
+ }
+
+ already_AddRefed<FilterNode> operator()(
+ const DiffuseLightingAttributes& aLighting) {
+ bool isSpecular =
+ mDescription.Attributes().is<SpecularLightingAttributes>();
+
+ if (aLighting.mLightType == LightType::None) {
+ return nullptr;
+ }
+
+ enum { POINT = 0, SPOT, DISTANT } lightType = POINT;
+
+ switch (aLighting.mLightType) {
+ case LightType::Point:
+ lightType = POINT;
+ break;
+ case LightType::Spot:
+ lightType = SPOT;
+ break;
+ case LightType::Distant:
+ lightType = DISTANT;
+ break;
+ default:
+ break;
+ }
+
+ static const FilterType filterType[2][DISTANT + 1] = {
+ {FilterType::POINT_DIFFUSE, FilterType::SPOT_DIFFUSE,
+ FilterType::DISTANT_DIFFUSE},
+ {FilterType::POINT_SPECULAR, FilterType::SPOT_SPECULAR,
+ FilterType::DISTANT_SPECULAR}};
+ RefPtr<FilterNode> filter =
+ mDT->CreateFilter(filterType[isSpecular][lightType]);
+ if (!filter) {
+ return nullptr;
+ }
+
+ filter->SetAttribute(ATT_LIGHTING_COLOR, ToDeviceColor(aLighting.mColor));
+ filter->SetAttribute(ATT_LIGHTING_SURFACE_SCALE, aLighting.mSurfaceScale);
+ filter->SetAttribute(ATT_LIGHTING_KERNEL_UNIT_LENGTH,
+ aLighting.mKernelUnitLength);
+
+ if (isSpecular) {
+ filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT,
+ aLighting.mLightingConstant);
+ filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT,
+ aLighting.mSpecularExponent);
+ } else {
+ filter->SetAttribute(ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT,
+ aLighting.mLightingConstant);
+ }
+
+ switch (lightType) {
+ case POINT: {
+ Point3D position(aLighting.mLightValues[kPointLightPositionXIndex],
+ aLighting.mLightValues[kPointLightPositionYIndex],
+ aLighting.mLightValues[kPointLightPositionZIndex]);
+ filter->SetAttribute(ATT_POINT_LIGHT_POSITION, position);
+ break;
+ }
+ case SPOT: {
+ Point3D position(aLighting.mLightValues[kSpotLightPositionXIndex],
+ aLighting.mLightValues[kSpotLightPositionYIndex],
+ aLighting.mLightValues[kSpotLightPositionZIndex]);
+ filter->SetAttribute(ATT_SPOT_LIGHT_POSITION, position);
+ Point3D pointsAt(aLighting.mLightValues[kSpotLightPointsAtXIndex],
+ aLighting.mLightValues[kSpotLightPointsAtYIndex],
+ aLighting.mLightValues[kSpotLightPointsAtZIndex]);
+ filter->SetAttribute(ATT_SPOT_LIGHT_POINTS_AT, pointsAt);
+ filter->SetAttribute(ATT_SPOT_LIGHT_FOCUS,
+ aLighting.mLightValues[kSpotLightFocusIndex]);
+ filter->SetAttribute(
+ ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE,
+ aLighting.mLightValues[kSpotLightLimitingConeAngleIndex]);
+ break;
+ }
+ case DISTANT: {
+ filter->SetAttribute(
+ ATT_DISTANT_LIGHT_AZIMUTH,
+ aLighting.mLightValues[kDistantLightAzimuthIndex]);
+ filter->SetAttribute(
+ ATT_DISTANT_LIGHT_ELEVATION,
+ aLighting.mLightValues[kDistantLightElevationIndex]);
+ break;
+ }
+ }
+
+ filter->SetInput(IN_LIGHTING_IN, mSources[0]);
+
+ return filter.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const ImageAttributes& aImage) {
+ const Matrix& TM = aImage.mTransform;
+ if (!TM.Determinant()) {
+ return nullptr;
+ }
+
+ // Pull the image from the additional image list using the index that's
+ // stored in the primitive description.
+ RefPtr<SourceSurface> inputImage = mInputImages[aImage.mInputIndex];
+
+ RefPtr<FilterNode> transform = mDT->CreateFilter(FilterType::TRANSFORM);
+ if (!transform) {
+ return nullptr;
+ }
+ transform->SetInput(IN_TRANSFORM_IN, inputImage);
+ transform->SetAttribute(ATT_TRANSFORM_MATRIX, TM);
+ transform->SetAttribute(ATT_TRANSFORM_FILTER, aImage.mFilter);
+ return transform.forget();
+ }
+
+ already_AddRefed<FilterNode> operator()(const ToAlphaAttributes& aToAlpha) {
+ return FilterWrappers::ToAlpha(mDT, mSources[0]);
+ }
+ };
+
+ return aDescription.Attributes().match(PrimitiveAttributesMatcher(
+ aDescription, aDT, aSources, aSourceRegions, aInputImages));
+}
+
+template <typename T>
+static const T& ElementForIndex(int32_t aIndex,
+ const nsTArray<T>& aPrimitiveElements,
+ const T& aSourceGraphicElement,
+ const T& aFillPaintElement,
+ const T& aStrokePaintElement) {
+ switch (aIndex) {
+ case FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic:
+ case FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha:
+ return aSourceGraphicElement;
+ case FilterPrimitiveDescription::kPrimitiveIndexFillPaint:
+ return aFillPaintElement;
+ case FilterPrimitiveDescription::kPrimitiveIndexStrokePaint:
+ return aStrokePaintElement;
+ default:
+ MOZ_ASSERT(aIndex >= 0, "bad index");
+ return aPrimitiveElements[aIndex];
+ }
+}
+
+static AlphaModel InputAlphaModelForPrimitive(
+ const FilterPrimitiveDescription& aDescr, int32_t aInputIndex,
+ AlphaModel aOriginalAlphaModel) {
+ const PrimitiveAttributes& atts = aDescr.Attributes();
+ if (atts.is<TileAttributes>() || atts.is<OffsetAttributes>() ||
+ atts.is<ToAlphaAttributes>()) {
+ return aOriginalAlphaModel;
+ }
+ if (atts.is<ColorMatrixAttributes>() ||
+ atts.is<ComponentTransferAttributes>()) {
+ return AlphaModel::Unpremultiplied;
+ }
+ if (atts.is<DisplacementMapAttributes>()) {
+ return aInputIndex == 0 ? AlphaModel::Premultiplied
+ : AlphaModel::Unpremultiplied;
+ }
+ if (atts.is<ConvolveMatrixAttributes>()) {
+ return atts.as<ConvolveMatrixAttributes>().mPreserveAlpha
+ ? AlphaModel::Unpremultiplied
+ : AlphaModel::Premultiplied;
+ }
+ return AlphaModel::Premultiplied;
+}
+
+static AlphaModel OutputAlphaModelForPrimitive(
+ const FilterPrimitiveDescription& aDescr,
+ const nsTArray<AlphaModel>& aInputAlphaModels) {
+ if (aInputAlphaModels.Length()) {
+ // For filters with inputs, the output is premultiplied if and only if the
+ // first input is premultiplied.
+ return InputAlphaModelForPrimitive(aDescr, 0, aInputAlphaModels[0]);
+ }
+
+ // All filters without inputs produce premultiplied alpha.
+ return AlphaModel::Premultiplied;
+}
+
+// Returns the output FilterNode, in premultiplied sRGB space.
+already_AddRefed<FilterNode> FilterNodeGraphFromDescription(
+ DrawTarget* aDT, const FilterDescription& aFilter,
+ const Rect& aResultNeededRect, FilterNode* aSourceGraphic,
+ const IntRect& aSourceGraphicRect, FilterNode* aFillPaint,
+ FilterNode* aStrokePaint,
+ nsTArray<RefPtr<SourceSurface>>& aAdditionalImages) {
+ const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
+ MOZ_RELEASE_ASSERT(!primitives.IsEmpty());
+
+ RefPtr<FilterCachedColorModels> sourceFilters[4];
+ nsTArray<RefPtr<FilterCachedColorModels>> primitiveFilters;
+
+ for (size_t i = 0; i < primitives.Length(); ++i) {
+ const FilterPrimitiveDescription& descr = primitives[i];
+
+ nsTArray<RefPtr<FilterNode>> inputFilterNodes;
+ nsTArray<IntRect> inputSourceRects;
+ nsTArray<AlphaModel> inputAlphaModels;
+
+ for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
+ int32_t inputIndex = descr.InputPrimitiveIndex(j);
+ if (inputIndex < 0) {
+ inputSourceRects.AppendElement(descr.FilterSpaceBounds());
+ } else {
+ inputSourceRects.AppendElement(
+ primitives[inputIndex].PrimitiveSubregion());
+ }
+
+ RefPtr<FilterCachedColorModels> inputFilter;
+ if (inputIndex >= 0) {
+ MOZ_ASSERT(inputIndex < (int64_t)primitiveFilters.Length(),
+ "out-of-bounds input index!");
+ inputFilter = primitiveFilters[inputIndex];
+ MOZ_ASSERT(
+ inputFilter,
+ "Referred to input filter that comes after the current one?");
+ } else {
+ int32_t sourceIndex = -inputIndex - 1;
+ MOZ_ASSERT(sourceIndex >= 0, "invalid source index");
+ MOZ_ASSERT(sourceIndex < 4, "invalid source index");
+ inputFilter = sourceFilters[sourceIndex];
+ if (!inputFilter) {
+ RefPtr<FilterNode> sourceFilterNode;
+
+ nsTArray<FilterNode*> primitiveFilters;
+ RefPtr<FilterNode> filt =
+ ElementForIndex(inputIndex, primitiveFilters, aSourceGraphic,
+ aFillPaint, aStrokePaint);
+ if (filt) {
+ sourceFilterNode = filt;
+
+ // Clip the original SourceGraphic to the first filter region if the
+ // surface isn't already sized appropriately.
+ if ((inputIndex ==
+ FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic ||
+ inputIndex ==
+ FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha) &&
+ !descr.FilterSpaceBounds().Contains(aSourceGraphicRect)) {
+ sourceFilterNode = FilterWrappers::Crop(
+ aDT, sourceFilterNode, descr.FilterSpaceBounds());
+ }
+
+ if (inputIndex ==
+ FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha) {
+ sourceFilterNode = FilterWrappers::ToAlpha(aDT, sourceFilterNode);
+ }
+ }
+
+ inputFilter = new FilterCachedColorModels(aDT, sourceFilterNode,
+ ColorModel::PremulSRGB());
+ sourceFilters[sourceIndex] = inputFilter;
+ }
+ }
+ MOZ_ASSERT(inputFilter);
+
+ AlphaModel inputAlphaModel = InputAlphaModelForPrimitive(
+ descr, j, inputFilter->OriginalAlphaModel());
+ inputAlphaModels.AppendElement(inputAlphaModel);
+ ColorModel inputColorModel(descr.InputColorSpace(j), inputAlphaModel);
+ inputFilterNodes.AppendElement(
+ inputFilter->ForColorModel(inputColorModel));
+ }
+
+ RefPtr<FilterNode> primitiveFilterNode = FilterNodeFromPrimitiveDescription(
+ descr, aDT, inputFilterNodes, inputSourceRects, aAdditionalImages);
+
+ if (primitiveFilterNode) {
+ primitiveFilterNode = FilterWrappers::Crop(aDT, primitiveFilterNode,
+ descr.PrimitiveSubregion());
+ }
+
+ ColorModel outputColorModel(
+ descr.OutputColorSpace(),
+ OutputAlphaModelForPrimitive(descr, inputAlphaModels));
+ RefPtr<FilterCachedColorModels> primitiveFilter =
+ new FilterCachedColorModels(aDT, primitiveFilterNode, outputColorModel);
+
+ primitiveFilters.AppendElement(primitiveFilter);
+ }
+
+ MOZ_RELEASE_ASSERT(!primitiveFilters.IsEmpty());
+ return primitiveFilters.LastElement()->ForColorModel(
+ ColorModel::PremulSRGB());
+}
+
+// FilterSupport
+
+void FilterSupport::RenderFilterDescription(
+ DrawTarget* aDT, const FilterDescription& aFilter, const Rect& aRenderRect,
+ SourceSurface* aSourceGraphic, const IntRect& aSourceGraphicRect,
+ SourceSurface* aFillPaint, const IntRect& aFillPaintRect,
+ SourceSurface* aStrokePaint, const IntRect& aStrokePaintRect,
+ nsTArray<RefPtr<SourceSurface>>& aAdditionalImages, const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
+ if (aSourceGraphic) {
+ sourceGraphic = FilterWrappers::ForSurface(aDT, aSourceGraphic,
+ aSourceGraphicRect.TopLeft());
+ }
+ if (aFillPaint) {
+ fillPaint =
+ FilterWrappers::ForSurface(aDT, aFillPaint, aFillPaintRect.TopLeft());
+ }
+ if (aStrokePaint) {
+ strokePaint = FilterWrappers::ForSurface(aDT, aStrokePaint,
+ aStrokePaintRect.TopLeft());
+ }
+ RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
+ aDT, aFilter, aRenderRect, sourceGraphic, aSourceGraphicRect, fillPaint,
+ strokePaint, aAdditionalImages);
+ if (!resultFilter) {
+ gfxWarning() << "Filter is NULL.";
+ return;
+ }
+ aDT->DrawFilter(resultFilter, aRenderRect, aDestPoint, aOptions);
+}
+
+static nsIntRegion UnionOfRegions(const nsTArray<nsIntRegion>& aRegions) {
+ nsIntRegion result;
+ for (size_t i = 0; i < aRegions.Length(); i++) {
+ result.Or(result, aRegions[i]);
+ }
+ return result;
+}
+
+static int32_t InflateSizeForBlurStdDev(float aStdDev) {
+ double size =
+ std::min(aStdDev, kMaxStdDeviation) * (3 * sqrt(2 * M_PI) / 4) * 1.5;
+ return uint32_t(floor(size + 0.5));
+}
+
+static nsIntRegion ResultChangeRegionForPrimitive(
+ const FilterPrimitiveDescription& aDescription,
+ const nsTArray<nsIntRegion>& aInputChangeRegions) {
+ struct PrimitiveAttributesMatcher {
+ PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription,
+ const nsTArray<nsIntRegion>& aInputChangeRegions)
+ : mDescription(aDescription),
+ mInputChangeRegions(aInputChangeRegions) {}
+
+ const FilterPrimitiveDescription& mDescription;
+ const nsTArray<nsIntRegion>& mInputChangeRegions;
+
+ nsIntRegion operator()(const EmptyAttributes& aEmptyAttributes) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const BlendAttributes& aBlend) {
+ return UnionOfRegions(mInputChangeRegions);
+ }
+
+ nsIntRegion operator()(const ColorMatrixAttributes& aColorMatrix) {
+ return mInputChangeRegions[0];
+ }
+
+ nsIntRegion operator()(const MorphologyAttributes& aMorphology) {
+ Size radii = aMorphology.mRadii;
+ int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius);
+ int32_t ry =
+ clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius);
+ return mInputChangeRegions[0].Inflated(nsIntMargin(ry, rx, ry, rx));
+ }
+
+ nsIntRegion operator()(const FloodAttributes& aFlood) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const TileAttributes& aTile) {
+ return mDescription.PrimitiveSubregion();
+ }
+
+ nsIntRegion operator()(
+ const ComponentTransferAttributes& aComponentTransfer) {
+ return mInputChangeRegions[0];
+ }
+
+ nsIntRegion operator()(const OpacityAttributes& aOpacity) {
+ return UnionOfRegions(mInputChangeRegions);
+ }
+
+ nsIntRegion operator()(const ConvolveMatrixAttributes& aConvolveMatrix) {
+ if (aConvolveMatrix.mEdgeMode != EDGE_MODE_NONE) {
+ return mDescription.PrimitiveSubregion();
+ }
+ Size kernelUnitLength = aConvolveMatrix.mKernelUnitLength;
+ IntSize kernelSize = aConvolveMatrix.mKernelSize;
+ IntPoint target = aConvolveMatrix.mTarget;
+ nsIntMargin m(
+ ceil(kernelUnitLength.width * (target.x)),
+ ceil(kernelUnitLength.height * (target.y)),
+ ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1)),
+ ceil(kernelUnitLength.height * (kernelSize.height - target.y - 1)));
+ return mInputChangeRegions[0].Inflated(m);
+ }
+
+ nsIntRegion operator()(const OffsetAttributes& aOffset) {
+ IntPoint offset = aOffset.mValue;
+ return mInputChangeRegions[0].MovedBy(offset.x, offset.y);
+ }
+
+ nsIntRegion operator()(const DisplacementMapAttributes& aDisplacementMap) {
+ int32_t scale = ceil(std::abs(aDisplacementMap.mScale));
+ return mInputChangeRegions[0].Inflated(
+ nsIntMargin(scale, scale, scale, scale));
+ }
+
+ nsIntRegion operator()(const TurbulenceAttributes& aTurbulence) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const CompositeAttributes& aComposite) {
+ return UnionOfRegions(mInputChangeRegions);
+ }
+
+ nsIntRegion operator()(const MergeAttributes& aMerge) {
+ return UnionOfRegions(mInputChangeRegions);
+ }
+
+ nsIntRegion operator()(const GaussianBlurAttributes& aGaussianBlur) {
+ const Size& stdDeviation = aGaussianBlur.mStdDeviation;
+ int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
+ int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
+ return mInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx));
+ }
+
+ nsIntRegion operator()(const DropShadowAttributes& aDropShadow) {
+ IntPoint offset = IntPoint::Truncate(aDropShadow.mOffset);
+ nsIntRegion offsetRegion =
+ mInputChangeRegions[0].MovedBy(offset.x, offset.y);
+ Size stdDeviation = aDropShadow.mStdDeviation;
+ int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
+ int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
+ nsIntRegion blurRegion =
+ offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
+ blurRegion.Or(blurRegion, mInputChangeRegions[0]);
+ return blurRegion;
+ }
+
+ nsIntRegion operator()(const SpecularLightingAttributes& aLighting) {
+ return operator()(
+ *(static_cast<const DiffuseLightingAttributes*>(&aLighting)));
+ }
+
+ nsIntRegion operator()(const DiffuseLightingAttributes& aLighting) {
+ Size kernelUnitLength = aLighting.mKernelUnitLength;
+ int32_t dx = ceil(kernelUnitLength.width);
+ int32_t dy = ceil(kernelUnitLength.height);
+ return mInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx));
+ }
+
+ nsIntRegion operator()(const ImageAttributes& aImage) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const ToAlphaAttributes& aToAlpha) {
+ return mInputChangeRegions[0];
+ }
+ };
+
+ return aDescription.Attributes().match(
+ PrimitiveAttributesMatcher(aDescription, aInputChangeRegions));
+}
+
+/* static */
+nsIntRegion FilterSupport::ComputeResultChangeRegion(
+ const FilterDescription& aFilter, const nsIntRegion& aSourceGraphicChange,
+ const nsIntRegion& aFillPaintChange,
+ const nsIntRegion& aStrokePaintChange) {
+ const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
+ MOZ_RELEASE_ASSERT(!primitives.IsEmpty());
+
+ nsTArray<nsIntRegion> resultChangeRegions;
+
+ for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) {
+ const FilterPrimitiveDescription& descr = primitives[i];
+
+ nsTArray<nsIntRegion> inputChangeRegions;
+ for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
+ int32_t inputIndex = descr.InputPrimitiveIndex(j);
+ MOZ_ASSERT(inputIndex < i, "bad input index");
+ nsIntRegion inputChangeRegion =
+ ElementForIndex(inputIndex, resultChangeRegions, aSourceGraphicChange,
+ aFillPaintChange, aStrokePaintChange);
+ inputChangeRegions.AppendElement(inputChangeRegion);
+ }
+ nsIntRegion changeRegion =
+ ResultChangeRegionForPrimitive(descr, inputChangeRegions);
+ changeRegion.And(changeRegion, descr.PrimitiveSubregion());
+ resultChangeRegions.AppendElement(changeRegion);
+ }
+
+ MOZ_RELEASE_ASSERT(!resultChangeRegions.IsEmpty());
+ return resultChangeRegions[resultChangeRegions.Length() - 1];
+}
+
+static float ResultOfZeroUnderTransferFunction(
+ const ComponentTransferAttributes& aFunctionAttributes, int32_t channel) {
+ switch (aFunctionAttributes.mTypes[channel]) {
+ case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: {
+ const nsTArray<float>& tableValues = aFunctionAttributes.mValues[channel];
+ if (tableValues.Length() < 2) {
+ return 0.0f;
+ }
+ return tableValues[0];
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: {
+ const nsTArray<float>& tableValues = aFunctionAttributes.mValues[channel];
+ if (tableValues.Length() < 1) {
+ return 0.0f;
+ }
+ return tableValues[0];
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: {
+ const nsTArray<float>& values = aFunctionAttributes.mValues[channel];
+ return values[kComponentTransferInterceptIndex];
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: {
+ const nsTArray<float>& values = aFunctionAttributes.mValues[channel];
+ return values[kComponentTransferOffsetIndex];
+ }
+
+ case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
+ default:
+ return 0.0f;
+ }
+}
+
+nsIntRegion FilterSupport::PostFilterExtentsForPrimitive(
+ const FilterPrimitiveDescription& aDescription,
+ const nsTArray<nsIntRegion>& aInputExtents) {
+ struct PrimitiveAttributesMatcher {
+ PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription,
+ const nsTArray<nsIntRegion>& aInputExtents)
+ : mDescription(aDescription), mInputExtents(aInputExtents) {}
+
+ const FilterPrimitiveDescription& mDescription;
+ const nsTArray<nsIntRegion>& mInputExtents;
+
+ nsIntRegion operator()(const EmptyAttributes& aEmptyAttributes) {
+ return IntRect();
+ }
+
+ nsIntRegion operator()(const BlendAttributes& aBlend) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const ColorMatrixAttributes& aColorMatrix) {
+ if (aColorMatrix.mType == (uint32_t)SVG_FECOLORMATRIX_TYPE_MATRIX) {
+ const nsTArray<float>& values = aColorMatrix.mValues;
+ if (values.Length() == 20 && values[19] > 0.0f) {
+ return mDescription.PrimitiveSubregion();
+ }
+ }
+ return mInputExtents[0];
+ }
+
+ nsIntRegion operator()(const MorphologyAttributes& aMorphology) {
+ uint32_t op = aMorphology.mOperator;
+ if (op == SVG_OPERATOR_ERODE) {
+ return mInputExtents[0];
+ }
+ Size radii = aMorphology.mRadii;
+ int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius);
+ int32_t ry =
+ clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius);
+ return mInputExtents[0].Inflated(nsIntMargin(ry, rx, ry, rx));
+ }
+
+ nsIntRegion operator()(const FloodAttributes& aFlood) {
+ if (aFlood.mColor.a == 0.0f) {
+ return IntRect();
+ }
+ return mDescription.PrimitiveSubregion();
+ }
+
+ nsIntRegion operator()(const TileAttributes& aTile) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(
+ const ComponentTransferAttributes& aComponentTransfer) {
+ if (ResultOfZeroUnderTransferFunction(aComponentTransfer, kChannelA) >
+ 0.0f) {
+ return mDescription.PrimitiveSubregion();
+ }
+ return mInputExtents[0];
+ }
+
+ nsIntRegion operator()(const OpacityAttributes& aOpacity) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const ConvolveMatrixAttributes& aConvolveMatrix) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const OffsetAttributes& aOffset) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const DisplacementMapAttributes& aDisplacementMap) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const TurbulenceAttributes& aTurbulence) {
+ return mDescription.PrimitiveSubregion();
+ }
+
+ nsIntRegion operator()(const CompositeAttributes& aComposite) {
+ uint32_t op = aComposite.mOperator;
+ if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) {
+ // The arithmetic composite primitive can draw outside the bounding
+ // box of its source images.
+ const nsTArray<float>& coefficients = aComposite.mCoefficients;
+ MOZ_ASSERT(coefficients.Length() == 4);
+
+ // The calculation is:
+ // r = c[0] * in[0] * in[1] + c[1] * in[0] + c[2] * in[1] + c[3]
+ nsIntRegion region;
+ if (coefficients[0] > 0.0f) {
+ region = mInputExtents[0].Intersect(mInputExtents[1]);
+ }
+ if (coefficients[1] > 0.0f) {
+ region.Or(region, mInputExtents[0]);
+ }
+ if (coefficients[2] > 0.0f) {
+ region.Or(region, mInputExtents[1]);
+ }
+ if (coefficients[3] > 0.0f) {
+ region = mDescription.PrimitiveSubregion();
+ }
+ return region;
+ }
+ if (op == SVG_FECOMPOSITE_OPERATOR_IN) {
+ return mInputExtents[0].Intersect(mInputExtents[1]);
+ }
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const MergeAttributes& aMerge) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const GaussianBlurAttributes& aGaussianBlur) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const DropShadowAttributes& aDropShadow) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+
+ nsIntRegion operator()(const DiffuseLightingAttributes& aDiffuseLighting) {
+ return mDescription.PrimitiveSubregion();
+ }
+
+ nsIntRegion operator()(
+ const SpecularLightingAttributes& aSpecularLighting) {
+ return mDescription.PrimitiveSubregion();
+ }
+
+ nsIntRegion operator()(const ImageAttributes& aImage) {
+ return mDescription.PrimitiveSubregion();
+ }
+
+ nsIntRegion operator()(const ToAlphaAttributes& aToAlpha) {
+ return ResultChangeRegionForPrimitive(mDescription, mInputExtents);
+ }
+ };
+
+ return aDescription.Attributes().match(
+ PrimitiveAttributesMatcher(aDescription, aInputExtents));
+}
+
+/* static */
+nsIntRegion FilterSupport::ComputePostFilterExtents(
+ const FilterDescription& aFilter,
+ const nsIntRegion& aSourceGraphicExtents) {
+ const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
+ MOZ_RELEASE_ASSERT(!primitives.IsEmpty());
+ nsTArray<nsIntRegion> postFilterExtents;
+
+ for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) {
+ const FilterPrimitiveDescription& descr = primitives[i];
+ nsIntRegion filterSpace = descr.FilterSpaceBounds();
+
+ nsTArray<nsIntRegion> inputExtents;
+ for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
+ int32_t inputIndex = descr.InputPrimitiveIndex(j);
+ MOZ_ASSERT(inputIndex < i, "bad input index");
+ nsIntRegion inputExtent =
+ ElementForIndex(inputIndex, postFilterExtents, aSourceGraphicExtents,
+ filterSpace, filterSpace);
+ inputExtents.AppendElement(inputExtent);
+ }
+ nsIntRegion extent = PostFilterExtentsForPrimitive(descr, inputExtents);
+ extent.And(extent, descr.PrimitiveSubregion());
+ postFilterExtents.AppendElement(extent);
+ }
+
+ MOZ_RELEASE_ASSERT(!postFilterExtents.IsEmpty());
+ return postFilterExtents[postFilterExtents.Length() - 1];
+}
+
+static nsIntRegion SourceNeededRegionForPrimitive(
+ const FilterPrimitiveDescription& aDescription,
+ const nsIntRegion& aResultNeededRegion, int32_t aInputIndex) {
+ struct PrimitiveAttributesMatcher {
+ PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription,
+ const nsIntRegion& aResultNeededRegion,
+ int32_t aInputIndex)
+ : mDescription(aDescription),
+ mResultNeededRegion(aResultNeededRegion),
+ mInputIndex(aInputIndex) {}
+
+ const FilterPrimitiveDescription& mDescription;
+ const nsIntRegion& mResultNeededRegion;
+ const int32_t mInputIndex;
+
+ nsIntRegion operator()(const EmptyAttributes& aEmptyAttributes) {
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const BlendAttributes& aBlend) {
+ return mResultNeededRegion;
+ }
+
+ nsIntRegion operator()(const ColorMatrixAttributes& aColorMatrix) {
+ return mResultNeededRegion;
+ }
+
+ nsIntRegion operator()(const MorphologyAttributes& aMorphology) {
+ Size radii = aMorphology.mRadii;
+ int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius);
+ int32_t ry =
+ clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius);
+ return mResultNeededRegion.Inflated(nsIntMargin(ry, rx, ry, rx));
+ }
+
+ nsIntRegion operator()(const FloodAttributes& aFlood) {
+ MOZ_CRASH("GFX: this shouldn't be called for filters without inputs");
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const TileAttributes& aTile) {
+ return IntRect(INT32_MIN / 2, INT32_MIN / 2, INT32_MAX, INT32_MAX);
+ }
+
+ nsIntRegion operator()(
+ const ComponentTransferAttributes& aComponentTransfer) {
+ return mResultNeededRegion;
+ }
+
+ nsIntRegion operator()(const OpacityAttributes& aOpacity) {
+ return mResultNeededRegion;
+ }
+
+ nsIntRegion operator()(const ConvolveMatrixAttributes& aConvolveMatrix) {
+ Size kernelUnitLength = aConvolveMatrix.mKernelUnitLength;
+ IntSize kernelSize = aConvolveMatrix.mKernelSize;
+ IntPoint target = aConvolveMatrix.mTarget;
+ nsIntMargin m(
+ ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1)),
+ ceil(kernelUnitLength.height * (kernelSize.height - target.y - 1)),
+ ceil(kernelUnitLength.width * (target.x)),
+ ceil(kernelUnitLength.height * (target.y)));
+ return mResultNeededRegion.Inflated(m);
+ }
+
+ nsIntRegion operator()(const OffsetAttributes& aOffset) {
+ IntPoint offset = aOffset.mValue;
+ return mResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y));
+ }
+
+ nsIntRegion operator()(const DisplacementMapAttributes& aDisplacementMap) {
+ if (mInputIndex == 1) {
+ return mResultNeededRegion;
+ }
+ int32_t scale = ceil(std::abs(aDisplacementMap.mScale));
+ return mResultNeededRegion.Inflated(
+ nsIntMargin(scale, scale, scale, scale));
+ }
+
+ nsIntRegion operator()(const TurbulenceAttributes& aTurbulence) {
+ MOZ_CRASH("GFX: this shouldn't be called for filters without inputs");
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const CompositeAttributes& aComposite) {
+ return mResultNeededRegion;
+ }
+
+ nsIntRegion operator()(const MergeAttributes& aMerge) {
+ return mResultNeededRegion;
+ }
+
+ nsIntRegion operator()(const GaussianBlurAttributes& aGaussianBlur) {
+ const Size& stdDeviation = aGaussianBlur.mStdDeviation;
+ int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
+ int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
+ return mResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
+ }
+
+ nsIntRegion operator()(const DropShadowAttributes& aDropShadow) {
+ IntPoint offset = IntPoint::Truncate(aDropShadow.mOffset);
+ nsIntRegion offsetRegion =
+ mResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y));
+ Size stdDeviation = aDropShadow.mStdDeviation;
+ int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
+ int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
+ nsIntRegion blurRegion =
+ offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
+ blurRegion.Or(blurRegion, mResultNeededRegion);
+ return blurRegion;
+ }
+
+ nsIntRegion operator()(const SpecularLightingAttributes& aLighting) {
+ return operator()(
+ *(static_cast<const DiffuseLightingAttributes*>(&aLighting)));
+ }
+
+ nsIntRegion operator()(const DiffuseLightingAttributes& aLighting) {
+ Size kernelUnitLength = aLighting.mKernelUnitLength;
+ int32_t dx = ceil(kernelUnitLength.width);
+ int32_t dy = ceil(kernelUnitLength.height);
+ return mResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
+ }
+
+ nsIntRegion operator()(const ImageAttributes& aImage) {
+ MOZ_CRASH("GFX: this shouldn't be called for filters without inputs");
+ return nsIntRegion();
+ }
+
+ nsIntRegion operator()(const ToAlphaAttributes& aToAlpha) {
+ return mResultNeededRegion;
+ }
+ };
+
+ return aDescription.Attributes().match(PrimitiveAttributesMatcher(
+ aDescription, aResultNeededRegion, aInputIndex));
+}
+
+/* static */
+void FilterSupport::ComputeSourceNeededRegions(
+ const FilterDescription& aFilter, const nsIntRegion& aResultNeededRegion,
+ nsIntRegion& aSourceGraphicNeededRegion,
+ nsIntRegion& aFillPaintNeededRegion,
+ nsIntRegion& aStrokePaintNeededRegion) {
+ const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
+ MOZ_ASSERT(!primitives.IsEmpty());
+ if (primitives.IsEmpty()) {
+ return;
+ }
+
+ nsTArray<nsIntRegion> primitiveNeededRegions;
+ primitiveNeededRegions.AppendElements(primitives.Length());
+
+ primitiveNeededRegions[primitives.Length() - 1] = aResultNeededRegion;
+
+ for (int32_t i = primitives.Length() - 1; i >= 0; --i) {
+ const FilterPrimitiveDescription& descr = primitives[i];
+ nsIntRegion neededRegion = primitiveNeededRegions[i];
+ neededRegion.And(neededRegion, descr.PrimitiveSubregion());
+
+ for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
+ int32_t inputIndex = descr.InputPrimitiveIndex(j);
+ MOZ_ASSERT(inputIndex < i, "bad input index");
+ nsIntRegion* inputNeededRegion =
+ const_cast<nsIntRegion*>(&ElementForIndex(
+ inputIndex, primitiveNeededRegions, aSourceGraphicNeededRegion,
+ aFillPaintNeededRegion, aStrokePaintNeededRegion));
+ inputNeededRegion->Or(*inputNeededRegion, SourceNeededRegionForPrimitive(
+ descr, neededRegion, j));
+ }
+ }
+
+ // Clip original SourceGraphic to first filter region.
+ const FilterPrimitiveDescription& firstDescr = primitives[0];
+ aSourceGraphicNeededRegion.And(aSourceGraphicNeededRegion,
+ firstDescr.FilterSpaceBounds());
+}
+
+// FilterPrimitiveDescription
+
+FilterPrimitiveDescription::FilterPrimitiveDescription()
+ : mAttributes(EmptyAttributes()),
+ mOutputColorSpace(ColorSpace::SRGB),
+ mIsTainted(false) {}
+
+FilterPrimitiveDescription::FilterPrimitiveDescription(
+ PrimitiveAttributes&& aAttributes)
+ : mAttributes(std::move(aAttributes)),
+ mOutputColorSpace(ColorSpace::SRGB),
+ mIsTainted(false) {}
+
+bool FilterPrimitiveDescription::operator==(
+ const FilterPrimitiveDescription& aOther) const {
+ return mFilterPrimitiveSubregion.IsEqualInterior(
+ aOther.mFilterPrimitiveSubregion) &&
+ mFilterSpaceBounds.IsEqualInterior(aOther.mFilterSpaceBounds) &&
+ mOutputColorSpace == aOther.mOutputColorSpace &&
+ mIsTainted == aOther.mIsTainted &&
+ mInputPrimitives == aOther.mInputPrimitives &&
+ mInputColorSpaces == aOther.mInputColorSpaces &&
+ mAttributes == aOther.mAttributes;
+}
+
+// FilterDescription
+
+bool FilterDescription::operator==(const FilterDescription& aOther) const {
+ return mPrimitives == aOther.mPrimitives;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/src/FilterSupport.h b/gfx/src/FilterSupport.h
new file mode 100644
index 0000000000..94c535ab0f
--- /dev/null
+++ b/gfx/src/FilterSupport.h
@@ -0,0 +1,466 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef __FilterSupport_h
+#define __FilterSupport_h
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsRegion.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gfx {
+class FilterPrimitiveDescription;
+class FilterNode;
+struct FilterDescription;
+} // namespace gfx
+} // namespace mozilla
+
+extern const float gsRGBToLinearRGBMap[256];
+
+namespace mozilla {
+namespace gfx {
+namespace FilterWrappers {
+extern already_AddRefed<FilterNode> Clear(DrawTarget* aDT);
+extern already_AddRefed<FilterNode> ForSurface(
+ DrawTarget* aDT, SourceSurface* aSurface, const IntPoint& aSurfacePosition);
+} // namespace FilterWrappers
+
+// Morphology Operators
+const unsigned short SVG_OPERATOR_UNKNOWN = 0;
+const unsigned short SVG_OPERATOR_ERODE = 1;
+const unsigned short SVG_OPERATOR_DILATE = 2;
+
+// ColorMatrix types
+const unsigned short SVG_FECOLORMATRIX_TYPE_UNKNOWN = 0;
+const unsigned short SVG_FECOLORMATRIX_TYPE_MATRIX = 1;
+const unsigned short SVG_FECOLORMATRIX_TYPE_SATURATE = 2;
+const unsigned short SVG_FECOLORMATRIX_TYPE_HUE_ROTATE = 3;
+const unsigned short SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA = 4;
+// ColorMatrix types for CSS filters
+const unsigned short SVG_FECOLORMATRIX_TYPE_SEPIA = 5;
+
+// ComponentTransfer types
+const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN = 0;
+const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY = 1;
+const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_TABLE = 2;
+const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE = 3;
+const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_LINEAR = 4;
+const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_GAMMA = 5;
+const unsigned short SVG_FECOMPONENTTRANSFER_SAME_AS_R = 6;
+
+// Blend Mode Values
+const unsigned short SVG_FEBLEND_MODE_UNKNOWN = 0;
+const unsigned short SVG_FEBLEND_MODE_NORMAL = 1;
+const unsigned short SVG_FEBLEND_MODE_MULTIPLY = 2;
+const unsigned short SVG_FEBLEND_MODE_SCREEN = 3;
+const unsigned short SVG_FEBLEND_MODE_DARKEN = 4;
+const unsigned short SVG_FEBLEND_MODE_LIGHTEN = 5;
+const unsigned short SVG_FEBLEND_MODE_OVERLAY = 6;
+const unsigned short SVG_FEBLEND_MODE_COLOR_DODGE = 7;
+const unsigned short SVG_FEBLEND_MODE_COLOR_BURN = 8;
+const unsigned short SVG_FEBLEND_MODE_HARD_LIGHT = 9;
+const unsigned short SVG_FEBLEND_MODE_SOFT_LIGHT = 10;
+const unsigned short SVG_FEBLEND_MODE_DIFFERENCE = 11;
+const unsigned short SVG_FEBLEND_MODE_EXCLUSION = 12;
+const unsigned short SVG_FEBLEND_MODE_HUE = 13;
+const unsigned short SVG_FEBLEND_MODE_SATURATION = 14;
+const unsigned short SVG_FEBLEND_MODE_COLOR = 15;
+const unsigned short SVG_FEBLEND_MODE_LUMINOSITY = 16;
+
+// Edge Mode Values
+const unsigned short SVG_EDGEMODE_UNKNOWN = 0;
+const unsigned short SVG_EDGEMODE_DUPLICATE = 1;
+const unsigned short SVG_EDGEMODE_WRAP = 2;
+const unsigned short SVG_EDGEMODE_NONE = 3;
+
+// Channel Selectors
+const unsigned short SVG_CHANNEL_UNKNOWN = 0;
+const unsigned short SVG_CHANNEL_R = 1;
+const unsigned short SVG_CHANNEL_G = 2;
+const unsigned short SVG_CHANNEL_B = 3;
+const unsigned short SVG_CHANNEL_A = 4;
+
+// Turbulence Types
+const unsigned short SVG_TURBULENCE_TYPE_UNKNOWN = 0;
+const unsigned short SVG_TURBULENCE_TYPE_FRACTALNOISE = 1;
+const unsigned short SVG_TURBULENCE_TYPE_TURBULENCE = 2;
+
+// Composite Operators
+const unsigned short SVG_FECOMPOSITE_OPERATOR_UNKNOWN = 0;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_OVER = 1;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_IN = 2;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_OUT = 3;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_ATOP = 4;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_XOR = 5;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_ARITHMETIC = 6;
+const unsigned short SVG_FECOMPOSITE_OPERATOR_LIGHTER = 7;
+
+struct FilterAttribute;
+
+// Limits
+const float kMaxStdDeviation = 500;
+
+// Simple PrimitiveAttributes:
+
+struct EmptyAttributes {
+ bool operator==(const EmptyAttributes& aOther) const { return true; }
+};
+
+struct BlendAttributes {
+ uint32_t mBlendMode;
+
+ bool operator==(const BlendAttributes& aOther) const {
+ return mBlendMode == aOther.mBlendMode;
+ }
+};
+
+struct MorphologyAttributes {
+ uint32_t mOperator;
+ Size mRadii;
+
+ bool operator==(const MorphologyAttributes& aOther) const {
+ return mOperator == aOther.mOperator && mRadii == aOther.mRadii;
+ }
+};
+
+struct FloodAttributes {
+ sRGBColor mColor;
+
+ bool operator==(const FloodAttributes& aOther) const {
+ return mColor == aOther.mColor;
+ }
+};
+
+struct TileAttributes {
+ bool operator==(const TileAttributes& aOther) const { return true; }
+};
+
+struct OpacityAttributes {
+ float mOpacity;
+
+ bool operator==(const OpacityAttributes& aOther) const {
+ return mOpacity == aOther.mOpacity;
+ }
+};
+
+struct OffsetAttributes {
+ IntPoint mValue;
+
+ bool operator==(const OffsetAttributes& aOther) const {
+ return mValue == aOther.mValue;
+ }
+};
+
+struct DisplacementMapAttributes {
+ float mScale;
+ uint32_t mXChannel;
+ uint32_t mYChannel;
+
+ bool operator==(const DisplacementMapAttributes& aOther) const {
+ return mScale == aOther.mScale && mXChannel == aOther.mXChannel &&
+ mYChannel == aOther.mYChannel;
+ }
+};
+
+struct TurbulenceAttributes {
+ IntPoint mOffset;
+ Size mBaseFrequency;
+ float mSeed;
+ uint32_t mOctaves;
+ bool mStitchable;
+ uint32_t mType;
+
+ bool operator==(const TurbulenceAttributes& aOther) const {
+ return mOffset == aOther.mOffset &&
+ mBaseFrequency == aOther.mBaseFrequency && mSeed == aOther.mSeed &&
+ mOctaves == aOther.mOctaves && mStitchable == aOther.mStitchable &&
+ mType == aOther.mType;
+ }
+};
+
+struct MergeAttributes {
+ bool operator==(const MergeAttributes& aOther) const { return true; }
+};
+
+struct ImageAttributes {
+ uint32_t mFilter;
+ uint32_t mInputIndex;
+ Matrix mTransform;
+
+ bool operator==(const ImageAttributes& aOther) const {
+ return mFilter == aOther.mFilter && mInputIndex == aOther.mInputIndex &&
+ mTransform.ExactlyEquals(aOther.mTransform);
+ }
+};
+
+struct GaussianBlurAttributes {
+ Size mStdDeviation;
+
+ bool operator==(const GaussianBlurAttributes& aOther) const {
+ return mStdDeviation == aOther.mStdDeviation;
+ }
+};
+
+struct DropShadowAttributes {
+ Size mStdDeviation;
+ Point mOffset;
+ sRGBColor mColor;
+
+ bool operator==(const DropShadowAttributes& aOther) const {
+ return mStdDeviation == aOther.mStdDeviation && mOffset == aOther.mOffset &&
+ mColor == aOther.mColor;
+ }
+};
+
+struct ToAlphaAttributes {
+ bool operator==(const ToAlphaAttributes& aOther) const { return true; }
+};
+
+// Complex PrimitiveAttributes:
+
+class ImplicitlyCopyableFloatArray : public CopyableTArray<float> {
+ public:
+ ImplicitlyCopyableFloatArray() = default;
+
+ ImplicitlyCopyableFloatArray(ImplicitlyCopyableFloatArray&& aOther) = default;
+
+ ImplicitlyCopyableFloatArray& operator=(
+ ImplicitlyCopyableFloatArray&& aOther) = default;
+
+ ImplicitlyCopyableFloatArray(const ImplicitlyCopyableFloatArray& aOther) =
+ default;
+
+ ImplicitlyCopyableFloatArray& operator=(
+ const ImplicitlyCopyableFloatArray& aOther) = default;
+};
+
+struct ColorMatrixAttributes {
+ uint32_t mType;
+ ImplicitlyCopyableFloatArray mValues;
+
+ bool operator==(const ColorMatrixAttributes& aOther) const {
+ return mType == aOther.mType && mValues == aOther.mValues;
+ }
+};
+
+// If the types for G and B are SVG_FECOMPONENTTRANSFER_SAME_AS_R,
+// use the R channel values - this lets us avoid copies.
+const uint32_t kChannelROrRGB = 0;
+const uint32_t kChannelG = 1;
+const uint32_t kChannelB = 2;
+const uint32_t kChannelA = 3;
+
+const uint32_t kComponentTransferSlopeIndex = 0;
+const uint32_t kComponentTransferInterceptIndex = 1;
+
+const uint32_t kComponentTransferAmplitudeIndex = 0;
+const uint32_t kComponentTransferExponentIndex = 1;
+const uint32_t kComponentTransferOffsetIndex = 2;
+
+struct ComponentTransferAttributes {
+ uint8_t mTypes[4];
+ ImplicitlyCopyableFloatArray mValues[4];
+
+ bool operator==(const ComponentTransferAttributes& aOther) const {
+ return mTypes[0] == aOther.mTypes[0] && mTypes[1] == aOther.mTypes[1] &&
+ mTypes[2] == aOther.mTypes[2] && mTypes[3] == aOther.mTypes[3] &&
+ mValues[0] == aOther.mValues[0] && mValues[1] == aOther.mValues[1] &&
+ mValues[2] == aOther.mValues[2] && mValues[3] == aOther.mValues[3];
+ }
+};
+
+struct ConvolveMatrixAttributes {
+ IntSize mKernelSize;
+ ImplicitlyCopyableFloatArray mKernelMatrix;
+ float mDivisor;
+ float mBias;
+ IntPoint mTarget;
+ uint32_t mEdgeMode;
+ Size mKernelUnitLength;
+ bool mPreserveAlpha;
+
+ bool operator==(const ConvolveMatrixAttributes& aOther) const {
+ return mKernelSize == aOther.mKernelSize &&
+ mKernelMatrix == aOther.mKernelMatrix &&
+ mDivisor == aOther.mDivisor && mBias == aOther.mBias &&
+ mTarget == aOther.mTarget && mEdgeMode == aOther.mEdgeMode &&
+ mKernelUnitLength == aOther.mKernelUnitLength &&
+ mPreserveAlpha == aOther.mPreserveAlpha;
+ }
+};
+
+struct CompositeAttributes {
+ uint32_t mOperator;
+ ImplicitlyCopyableFloatArray mCoefficients;
+
+ bool operator==(const CompositeAttributes& aOther) const {
+ return mOperator == aOther.mOperator &&
+ mCoefficients == aOther.mCoefficients;
+ }
+};
+
+enum class LightType {
+ None = 0,
+ Point,
+ Spot,
+ Distant,
+ Max,
+};
+
+const uint32_t kDistantLightAzimuthIndex = 0;
+const uint32_t kDistantLightElevationIndex = 1;
+const uint32_t kDistantLightNumAttributes = 2;
+
+const uint32_t kPointLightPositionXIndex = 0;
+const uint32_t kPointLightPositionYIndex = 1;
+const uint32_t kPointLightPositionZIndex = 2;
+const uint32_t kPointLightNumAttributes = 3;
+
+const uint32_t kSpotLightPositionXIndex = 0;
+const uint32_t kSpotLightPositionYIndex = 1;
+const uint32_t kSpotLightPositionZIndex = 2;
+const uint32_t kSpotLightPointsAtXIndex = 3;
+const uint32_t kSpotLightPointsAtYIndex = 4;
+const uint32_t kSpotLightPointsAtZIndex = 5;
+const uint32_t kSpotLightFocusIndex = 6;
+const uint32_t kSpotLightLimitingConeAngleIndex = 7;
+const uint32_t kSpotLightNumAttributes = 8;
+
+struct DiffuseLightingAttributes {
+ LightType mLightType;
+ ImplicitlyCopyableFloatArray mLightValues;
+ float mSurfaceScale;
+ Size mKernelUnitLength;
+ sRGBColor mColor;
+ float mLightingConstant;
+ float mSpecularExponent;
+
+ bool operator==(const DiffuseLightingAttributes& aOther) const {
+ return mLightType == aOther.mLightType &&
+ mLightValues == aOther.mLightValues &&
+ mSurfaceScale == aOther.mSurfaceScale &&
+ mKernelUnitLength == aOther.mKernelUnitLength &&
+ mColor == aOther.mColor;
+ }
+};
+
+struct SpecularLightingAttributes : public DiffuseLightingAttributes {};
+
+enum class ColorSpace { SRGB, LinearRGB, Max };
+
+enum class AlphaModel { Unpremultiplied, Premultiplied };
+
+class ColorModel {
+ public:
+ static ColorModel PremulSRGB() {
+ return ColorModel(ColorSpace::SRGB, AlphaModel::Premultiplied);
+ }
+
+ ColorModel(ColorSpace aColorSpace, AlphaModel aAlphaModel)
+ : mColorSpace(aColorSpace), mAlphaModel(aAlphaModel) {}
+ ColorModel()
+ : mColorSpace(ColorSpace::SRGB), mAlphaModel(AlphaModel::Premultiplied) {}
+ bool operator==(const ColorModel& aOther) const {
+ return mColorSpace == aOther.mColorSpace &&
+ mAlphaModel == aOther.mAlphaModel;
+ }
+
+ // Used to index FilterCachedColorModels::mFilterForColorModel.
+ uint8_t ToIndex() const {
+ return static_cast<uint8_t>(static_cast<uint8_t>(mColorSpace) << 1) |
+ static_cast<uint8_t>(mAlphaModel);
+ }
+
+ ColorSpace mColorSpace;
+ AlphaModel mAlphaModel;
+};
+
+already_AddRefed<FilterNode> FilterNodeGraphFromDescription(
+ DrawTarget* aDT, const FilterDescription& aFilter,
+ const Rect& aResultNeededRect, FilterNode* aSourceGraphic,
+ const IntRect& aSourceGraphicRect, FilterNode* aFillPaint,
+ FilterNode* aStrokePaint,
+ nsTArray<RefPtr<SourceSurface>>& aAdditionalImages);
+
+/**
+ * The methods of this class are not on FilterDescription because
+ * FilterDescription is designed as a simple value holder that can be used
+ * on any thread.
+ */
+class FilterSupport {
+ public:
+ /**
+ * Draw the filter described by aFilter. All rect parameters are in filter
+ * space coordinates. aRenderRect specifies the part of the filter output
+ * that will be drawn at (0, 0) into the draw target aDT, subject to the
+ * current transform on aDT but with no additional scaling.
+ * The source surfaces must match their corresponding rect in size.
+ * aAdditionalImages carries the images that are referenced by the
+ * eImageInputIndex attribute on any image primitives in the filter.
+ */
+ static void RenderFilterDescription(
+ DrawTarget* aDT, const FilterDescription& aFilter,
+ const Rect& aRenderRect, SourceSurface* aSourceGraphic,
+ const IntRect& aSourceGraphicRect, SourceSurface* aFillPaint,
+ const IntRect& aFillPaintRect, SourceSurface* aStrokePaint,
+ const IntRect& aStrokePaintRect,
+ nsTArray<RefPtr<SourceSurface>>& aAdditionalImages,
+ const Point& aDestPoint, const DrawOptions& aOptions = DrawOptions());
+
+ /**
+ * Computes the region that changes in the filter output due to a change in
+ * input. This is primarily needed when an individual piece of content inside
+ * a filtered container element changes.
+ */
+ static nsIntRegion ComputeResultChangeRegion(
+ const FilterDescription& aFilter, const nsIntRegion& aSourceGraphicChange,
+ const nsIntRegion& aFillPaintChange,
+ const nsIntRegion& aStrokePaintChange);
+
+ /**
+ * Computes the regions that need to be supplied in the filter inputs when
+ * painting aResultNeededRegion of the filter output.
+ */
+ static void ComputeSourceNeededRegions(
+ const FilterDescription& aFilter, const nsIntRegion& aResultNeededRegion,
+ nsIntRegion& aSourceGraphicNeededRegion,
+ nsIntRegion& aFillPaintNeededRegion,
+ nsIntRegion& aStrokePaintNeededRegion);
+
+ /**
+ * Computes the size of the filter output.
+ */
+ static nsIntRegion ComputePostFilterExtents(
+ const FilterDescription& aFilter,
+ const nsIntRegion& aSourceGraphicExtents);
+
+ /**
+ * Computes the size of a single FilterPrimitiveDescription's output given a
+ * set of input extents.
+ */
+ static nsIntRegion PostFilterExtentsForPrimitive(
+ const FilterPrimitiveDescription& aDescription,
+ const nsTArray<nsIntRegion>& aInputExtents);
+};
+
+/**
+ * Create a 4x5 color matrix for the different ways to specify color matrices
+ * in SVG.
+ *
+ * Return false if the input is invalid or if the resulting matrix is the
+ * identity.
+ */
+bool ComputeColorMatrix(const ColorMatrixAttributes& aMatrixAttributes,
+ float aOutMatrix[20]);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // __FilterSupport_h
diff --git a/gfx/src/FontPropertyTypes.h b/gfx/src/FontPropertyTypes.h
new file mode 100644
index 0000000000..9da6ab9391
--- /dev/null
+++ b/gfx/src/FontPropertyTypes.h
@@ -0,0 +1,490 @@
+/* -*- 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/. */
+
+/* font specific types shared by both thebes and layout */
+
+#ifndef GFX_FONT_PROPERTY_TYPES_H
+#define GFX_FONT_PROPERTY_TYPES_H
+
+#include <algorithm>
+#include <cstdint>
+#include <cmath>
+#include <utility>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/TextUtils.h"
+#include "nsString.h"
+
+/*
+ * This file is separate from gfxFont.h so that layout can include it
+ * without bringing in gfxFont.h and everything it includes.
+ */
+
+namespace mozilla {
+
+/**
+ * Generic template for font property type classes that use a fixed-point
+ * internal representation.
+ * Template parameters:
+ * InternalType - the integer type to use as the internal representation (e.g.
+ * uint16_t)
+ * * NOTE that T must NOT be plain /int/, as that would result in
+ * ambiguity between constructors from /int/ and /T/, which mean
+ * different things.
+ * FractionBits - number of bits to use for the fractional part
+ * Min, Max - [inclusive] limits to the range of values that may be stored
+ * Values are constructed from and exposed as floating-point, but stored
+ * internally as fixed point, so there will be a quantization effect on
+ * fractional values, depending on the number of fractional bits used.
+ * Using (16-bit) fixed-point types rather than floats for these style
+ * attributes reduces the memory footprint of gfxFontEntry and gfxFontStyle;
+ * it will also tend to reduce the number of distinct font instances that
+ * get created, particularly when styles are animated or set to arbitrary
+ * values (e.g. by sliders in the UI), which should reduce pressure on
+ * graphics resources and improve cache hit rates.
+ */
+template <class InternalType, unsigned FractionBits, int Min, int Max>
+class FontPropertyValue {
+ public:
+ // Initialize to the minimum value by default.
+ constexpr FontPropertyValue() : FontPropertyValue(Min) {}
+
+ explicit FontPropertyValue(const FontPropertyValue& aOther) = default;
+ FontPropertyValue& operator=(const FontPropertyValue& aOther) = default;
+
+ bool operator==(const FontPropertyValue& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ bool operator!=(const FontPropertyValue& aOther) const {
+ return mValue != aOther.mValue;
+ }
+ bool operator<(const FontPropertyValue& aOther) const {
+ return mValue < aOther.mValue;
+ }
+ bool operator>(const FontPropertyValue& aOther) const {
+ return mValue > aOther.mValue;
+ }
+ bool operator<=(const FontPropertyValue& aOther) const {
+ return mValue <= aOther.mValue;
+ }
+ bool operator>=(const FontPropertyValue& aOther) const {
+ return mValue >= aOther.mValue;
+ }
+
+ // The difference between two values, returned as a raw floating-point number
+ // (which might not be a valid property value in its own right).
+ float operator-(const FontPropertyValue& aOther) const {
+ return (mValue - aOther.mValue) * kInverseScale;
+ }
+
+ /// Return the raw internal representation, for purposes of hashing.
+ /// (Do not try to interpret the numeric value of this.)
+ uint16_t ForHash() const { return uint16_t(mValue); }
+
+ static constexpr const float kMin = float(Min);
+ static constexpr const float kMax = float(Max);
+
+ protected:
+ // Construct from a floating-point or integer value, checking that it is
+ // within the allowed range and converting to fixed-point representation.
+ explicit constexpr FontPropertyValue(float aValue)
+ : mValue(std::round(aValue * kScale)) {
+ MOZ_ASSERT(aValue >= kMin && aValue <= kMax);
+ }
+ explicit constexpr FontPropertyValue(int aValue)
+ : mValue(static_cast<InternalType>(aValue * kScale)) {
+ MOZ_ASSERT(aValue >= Min && aValue <= Max);
+ }
+
+ // Construct directly from a fixed-point value of type T, with no check;
+ // note that there may be special "flag" values that are outside the normal
+ // min/max range (e.g. for font-style:italic, distinct from oblique angle).
+ explicit constexpr FontPropertyValue(InternalType aValue) : mValue(aValue) {}
+
+ // This is protected as it may not be the most appropriate accessor for a
+ // given instance to expose. It's up to each individual property to provide
+ // public accessors that forward to this as required.
+ float ToFloat() const { return mValue * kInverseScale; }
+ int ToIntRounded() const { return (mValue + kPointFive) >> FractionBits; }
+
+ static constexpr int kScale = 1 << FractionBits;
+ static constexpr float kInverseScale = 1.0f / kScale;
+ static const unsigned kFractionBits = FractionBits;
+
+ // Constant representing 0.5 in the internal representation (note this
+ // assumes that kFractionBits is greater than zero!)
+ static const InternalType kPointFive = 1u << (kFractionBits - 1);
+
+ InternalType mValue;
+};
+
+/**
+ * font-weight: range 1..1000, fractional values permitted; keywords
+ * 'normal', 'bold' aliased to 400, 700 respectively; relative keywords
+ * 'lighter', 'bolder' (not currently handled here).
+ *
+ * We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375)
+ */
+class FontWeight final : public FontPropertyValue<uint16_t, 6, 1, 1000> {
+ public:
+ constexpr FontWeight() = default;
+
+ explicit constexpr FontWeight(float aValue) : FontPropertyValue(aValue) {}
+
+ /**
+ * CSS font weights can have fractional values, but this constructor exists
+ * for convenience when writing constants such as FontWeight(700) in code.
+ */
+ explicit constexpr FontWeight(int aValue) : FontPropertyValue(aValue) {}
+
+ static constexpr FontWeight Normal() { return FontWeight(kNormal); }
+
+ static constexpr FontWeight Thin() { return FontWeight(kThin); }
+
+ static constexpr FontWeight Bold() { return FontWeight(kBold); }
+
+ bool IsNormal() const { return mValue == kNormal; }
+ bool IsBold() const { return mValue >= kBoldThreshold; }
+
+ float ToFloat() const { return FontPropertyValue::ToFloat(); }
+ int ToIntRounded() const { return FontPropertyValue::ToIntRounded(); }
+
+ typedef uint16_t InternalType;
+
+ private:
+ friend class WeightRange;
+
+ explicit constexpr FontWeight(InternalType aValue)
+ : FontPropertyValue(aValue) {}
+
+ static const InternalType kNormal = 400u << kFractionBits;
+ static const InternalType kBold = 700u << kFractionBits;
+ static const InternalType kBoldThreshold = 600u << kFractionBits;
+ static const InternalType kThin = 100u << kFractionBits;
+ static const InternalType kExtraBold = 900u << kFractionBits;
+};
+
+/**
+ * font-stretch is represented as a percentage relative to 'normal'.
+ *
+ * css-fonts says the value must be >= 0%, and normal is 100%. Keywords
+ * from ultra-condensed to ultra-expanded are aliased to percentages
+ * from 50% to 200%; values outside that range are unlikely to be common,
+ * but could occur.
+ *
+ * Like font-weight, we use an unsigned 10.6 fixed-point value (range
+ * 0.0 - 1023.984375).
+ *
+ * We arbitrarily limit here to 1000%. (If that becomes a problem, we
+ * could reduce the number of fractional bits and increase the limit.)
+ */
+class FontStretch final : public FontPropertyValue<uint16_t, 6, 0, 1000> {
+ public:
+ constexpr FontStretch() = default;
+
+ explicit constexpr FontStretch(float aPercent)
+ : FontPropertyValue(aPercent) {}
+
+ static constexpr FontStretch Normal() { return FontStretch(kNormal); }
+ static constexpr FontStretch UltraCondensed() {
+ return FontStretch(kUltraCondensed);
+ }
+ static constexpr FontStretch ExtraCondensed() {
+ return FontStretch(kExtraCondensed);
+ }
+ static constexpr FontStretch Condensed() { return FontStretch(kCondensed); }
+ static constexpr FontStretch SemiCondensed() {
+ return FontStretch(kSemiCondensed);
+ }
+ static constexpr FontStretch SemiExpanded() {
+ return FontStretch(kSemiExpanded);
+ }
+ static constexpr FontStretch Expanded() { return FontStretch(kExpanded); }
+ static constexpr FontStretch ExtraExpanded() {
+ return FontStretch(kExtraExpanded);
+ }
+ static constexpr FontStretch UltraExpanded() {
+ return FontStretch(kUltraExpanded);
+ }
+
+ // The style system represents percentages in the 0.0..1.0 range, and
+ // FontStretch does it in the 0.0..100.0 range.
+ //
+ // TODO(emilio): We should consider changing this class to deal with the same
+ // range as the style system.
+ static FontStretch FromStyle(float aStylePercentage) {
+ return FontStretch(std::min(aStylePercentage * 100.0f, float(kMax)));
+ }
+
+ bool IsNormal() const { return mValue == kNormal; }
+ float Percentage() const { return ToFloat(); }
+
+ typedef uint16_t InternalType;
+
+ private:
+ friend class StretchRange;
+
+ explicit constexpr FontStretch(InternalType aValue)
+ : FontPropertyValue(aValue) {}
+
+ static const InternalType kUltraCondensed = 50u << kFractionBits;
+ static const InternalType kExtraCondensed =
+ (62u << kFractionBits) + kPointFive;
+ static const InternalType kCondensed = 75u << kFractionBits;
+ static const InternalType kSemiCondensed =
+ (87u << kFractionBits) + kPointFive;
+ static const InternalType kNormal = 100u << kFractionBits;
+ static const InternalType kSemiExpanded =
+ (112u << kFractionBits) + kPointFive;
+ static const InternalType kExpanded = 125u << kFractionBits;
+ static const InternalType kExtraExpanded = 150u << kFractionBits;
+ static const InternalType kUltraExpanded = 200u << kFractionBits;
+};
+
+/**
+ * font-style: normal | italic | oblique <angle>?
+ * values of <angle> below -90 or above 90 not permitted
+ * - Use a signed 8.8 fixed-point value
+ * (representable range -128.0 - 127.99609375)
+ * - Define min value (-128.0) as meaning 'normal'
+ * - Define max value (127.99609375) as 'italic'
+ * - Other values represent 'oblique <angle>'
+ * - Note that 'oblique 0deg' is distinct from 'normal' (should it be?)
+ */
+class FontSlantStyle final : public FontPropertyValue<int16_t, 8, -90, 90> {
+ public:
+ const static constexpr float kDefaultAngle = 14.0;
+
+ constexpr FontSlantStyle() = default;
+
+ static constexpr FontSlantStyle Normal() { return FontSlantStyle(kNormal); }
+
+ static constexpr FontSlantStyle Italic() { return FontSlantStyle(kItalic); }
+
+ static constexpr FontSlantStyle Oblique(float aAngle = kDefaultAngle) {
+ return FontSlantStyle(aAngle);
+ }
+
+ // Create from a string as generated by ToString. This is for internal use
+ // when serializing/deserializing entries for the startupcache, and is not
+ // intended to parse arbitrary (untrusted) strings.
+ static FontSlantStyle FromString(const char* aString) {
+ if (strcmp(aString, "normal") == 0) {
+ return Normal();
+ }
+ if (strcmp(aString, "italic") == 0) {
+ return Italic();
+ }
+ if (mozilla::IsAsciiDigit(aString[0]) && strstr(aString, "deg")) {
+ float angle = strtof(aString, nullptr);
+ return Oblique(angle);
+ }
+ // Not recognized as an oblique angle; maybe it's from a startup-cache
+ // created by an older version. The style field there used a simple 0/1
+ // for normal/italic respectively.
+ return aString[0] == '0' ? Normal() : Italic();
+ }
+
+ bool IsNormal() const { return mValue == kNormal; }
+ bool IsItalic() const { return mValue == kItalic; }
+ bool IsOblique() const { return mValue != kItalic && mValue != kNormal; }
+
+ float ObliqueAngle() const {
+ // It's not meaningful to get the oblique angle from a style that is
+ // actually 'normal' or 'italic'.
+ MOZ_ASSERT(IsOblique());
+ return ToFloat();
+ }
+
+ /**
+ * Write a string representation of the value to aOutString.
+ *
+ * NOTE that this APPENDS to the output string, it does not replace
+ * any existing contents.
+ */
+ void ToString(nsACString& aOutString) const {
+ if (IsNormal()) {
+ aOutString.Append("normal");
+ } else if (IsItalic()) {
+ aOutString.Append("italic");
+ } else {
+ aOutString.AppendPrintf("%gdeg", ObliqueAngle());
+ }
+ }
+
+ typedef int16_t InternalType;
+
+ private:
+ friend class SlantStyleRange;
+
+ explicit constexpr FontSlantStyle(InternalType aConstant)
+ : FontPropertyValue(aConstant) {}
+
+ explicit constexpr FontSlantStyle(float aAngle) : FontPropertyValue(aAngle) {}
+
+ static const InternalType kNormal = INT16_MIN;
+ static const InternalType kItalic = INT16_MAX;
+};
+
+/**
+ * Convenience type to hold a <min, max> pair representing a range of values.
+ *
+ * The min and max are both inclusive, so when min == max the range represents
+ * a single value (not an empty range).
+ */
+template <class T>
+class FontPropertyRange {
+ // This implementation assumes the underlying property type is a 16-bit value
+ // (see FromScalar and AsScalar below).
+ static_assert(sizeof(T) == 2, "FontPropertyValue should be a 16-bit type!");
+
+ public:
+ /**
+ * Construct a range from given minimum and maximum values (inclusive).
+ */
+ FontPropertyRange(T aMin, T aMax) : mValues(aMin, aMax) {
+ MOZ_ASSERT(aMin <= aMax);
+ }
+
+ /**
+ * Construct a range representing a single value (min==max).
+ */
+ explicit FontPropertyRange(T aValue) : mValues(aValue, aValue) {}
+
+ explicit FontPropertyRange(const FontPropertyRange& aOther) = default;
+ FontPropertyRange& operator=(const FontPropertyRange& aOther) = default;
+
+ T Min() const { return mValues.first; }
+ T Max() const { return mValues.second; }
+
+ /**
+ * Clamp the given value to this range.
+ *
+ * (We can't use mozilla::Clamp here because it only accepts integral types.)
+ */
+ T Clamp(T aValue) const {
+ return aValue <= Min() ? Min() : (aValue >= Max() ? Max() : aValue);
+ }
+
+ /**
+ * Return whether the range consists of a single unique value.
+ */
+ bool IsSingle() const { return Min() == Max(); }
+
+ bool operator==(const FontPropertyRange& aOther) const {
+ return mValues == aOther.mValues;
+ }
+ bool operator!=(const FontPropertyRange& aOther) const {
+ return mValues != aOther.mValues;
+ }
+
+ /**
+ * Conversion of the property range to/from a single 32-bit scalar value,
+ * suitable for IPC serialization, hashing, caching.
+ *
+ * No assumptions should be made about the numeric value of the scalar.
+ *
+ * This depends on the underlying property type being a 16-bit value!
+ */
+ typedef uint32_t ScalarType;
+
+ ScalarType AsScalar() const {
+ return (mValues.first.ForHash() << 16) | mValues.second.ForHash();
+ }
+
+ /*
+ * FIXME:
+ * FromScalar is defined in each individual subclass, because I can't
+ * persuade the compiler to accept a definition here in the template. :\
+ *
+ static FontPropertyRange FromScalar(ScalarType aScalar)
+ {
+ return FontPropertyRange(T(typename T::InternalType(aScalar >> 16)),
+ T(typename T::InternalType(aScalar & 0xffff)));
+ }
+ */
+
+ protected:
+ std::pair<T, T> mValues;
+};
+
+class WeightRange : public FontPropertyRange<FontWeight> {
+ public:
+ WeightRange(FontWeight aMin, FontWeight aMax)
+ : FontPropertyRange(aMin, aMax) {}
+
+ explicit WeightRange(FontWeight aWeight) : FontPropertyRange(aWeight) {}
+
+ WeightRange(const WeightRange& aOther) = default;
+
+ void ToString(nsACString& aOutString, const char* aDelim = "..") const {
+ aOutString.AppendFloat(Min().ToFloat());
+ if (!IsSingle()) {
+ aOutString.Append(aDelim);
+ aOutString.AppendFloat(Max().ToFloat());
+ }
+ }
+
+ static WeightRange FromScalar(ScalarType aScalar) {
+ return WeightRange(FontWeight(FontWeight::InternalType(aScalar >> 16)),
+ FontWeight(FontWeight::InternalType(aScalar & 0xffff)));
+ }
+};
+
+class StretchRange : public FontPropertyRange<FontStretch> {
+ public:
+ StretchRange(FontStretch aMin, FontStretch aMax)
+ : FontPropertyRange(aMin, aMax) {}
+
+ explicit StretchRange(FontStretch aStretch) : FontPropertyRange(aStretch) {}
+
+ StretchRange(const StretchRange& aOther) = default;
+
+ void ToString(nsACString& aOutString, const char* aDelim = "..") const {
+ aOutString.AppendFloat(Min().Percentage());
+ if (!IsSingle()) {
+ aOutString.Append(aDelim);
+ aOutString.AppendFloat(Max().Percentage());
+ }
+ }
+
+ static StretchRange FromScalar(ScalarType aScalar) {
+ return StretchRange(
+ FontStretch(FontStretch::InternalType(aScalar >> 16)),
+ FontStretch(FontStretch::InternalType(aScalar & 0xffff)));
+ }
+};
+
+class SlantStyleRange : public FontPropertyRange<FontSlantStyle> {
+ public:
+ SlantStyleRange(FontSlantStyle aMin, FontSlantStyle aMax)
+ : FontPropertyRange(aMin, aMax) {}
+
+ explicit SlantStyleRange(FontSlantStyle aStyle) : FontPropertyRange(aStyle) {}
+
+ SlantStyleRange(const SlantStyleRange& aOther) = default;
+
+ void ToString(nsACString& aOutString, const char* aDelim = "..") const {
+ Min().ToString(aOutString);
+ if (!IsSingle()) {
+ aOutString.Append(aDelim);
+ Max().ToString(aOutString);
+ }
+ }
+
+ static SlantStyleRange FromScalar(ScalarType aScalar) {
+ return SlantStyleRange(
+ FontSlantStyle(FontSlantStyle::InternalType(aScalar >> 16)),
+ FontSlantStyle(FontSlantStyle::InternalType(aScalar & 0xffff)));
+ }
+};
+
+} // namespace mozilla
+
+#endif // GFX_FONT_PROPERTY_TYPES_H
diff --git a/gfx/src/RegionBuilder.h b/gfx/src/RegionBuilder.h
new file mode 100644
index 0000000000..c866f8fcf6
--- /dev/null
+++ b/gfx/src/RegionBuilder.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef RegionBuilder_h__
+#define RegionBuilder_h__
+
+#include <nsTArray.h>
+
+template <typename RegionType>
+class RegionBuilder {
+ public:
+ typedef typename RegionType::RectType RectType;
+
+ RegionBuilder() = default;
+
+ void OrWith(const RectType& aRect) {
+ pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()};
+ mRects.AppendElement(box);
+ }
+
+ RegionType ToRegion() const { return RegionType(mRects); }
+
+ private:
+ nsTArray<pixman_box32_t> mRects;
+};
+
+#endif // RegionBuilder_h__
diff --git a/gfx/src/RelativeLuminanceUtils.h b/gfx/src/RelativeLuminanceUtils.h
new file mode 100644
index 0000000000..64047a4e56
--- /dev/null
+++ b/gfx/src/RelativeLuminanceUtils.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_RelativeLuminanceUtils_h
+#define mozilla_RelativeLuminanceUtils_h
+
+#include "nsColor.h"
+
+namespace mozilla {
+
+// Utilities for calculating relative luminance based on the algorithm
+// defined in https://www.w3.org/TR/WCAG20/#relativeluminancedef
+class RelativeLuminanceUtils {
+ public:
+ // Compute the relative luminance.
+ static float Compute(nscolor aColor) {
+ float r = ComputeComponent(NS_GET_R(aColor));
+ float g = ComputeComponent(NS_GET_G(aColor));
+ float b = ComputeComponent(NS_GET_B(aColor));
+ return ComputeFromComponents(r, g, b);
+ }
+
+ // Adjust the relative luminance of the given color.
+ static nscolor Adjust(nscolor aColor, float aLuminance) {
+ float r = ComputeComponent(NS_GET_R(aColor));
+ float g = ComputeComponent(NS_GET_G(aColor));
+ float b = ComputeComponent(NS_GET_B(aColor));
+ float luminance = ComputeFromComponents(r, g, b);
+ float factor = aLuminance / luminance;
+ uint8_t r1 = DecomputeComponent(r * factor);
+ uint8_t g1 = DecomputeComponent(g * factor);
+ uint8_t b1 = DecomputeComponent(b * factor);
+ return NS_RGBA(r1, g1, b1, NS_GET_A(aColor));
+ }
+
+ private:
+ static float ComputeComponent(uint8_t aComponent) {
+ float v = float(aComponent) / 255.0f;
+ if (v <= 0.03928f) {
+ return v / 12.92f;
+ }
+ return std::pow((v + 0.055f) / 1.055f, 2.4f);
+ }
+
+ static constexpr float ComputeFromComponents(float aR, float aG, float aB) {
+ return 0.2126f * aR + 0.7152f * aG + 0.0722f * aB;
+ }
+
+ // Inverse function of ComputeComponent.
+ static uint8_t DecomputeComponent(float aComponent) {
+ if (aComponent <= 0.03928f / 12.92f) {
+ aComponent *= 12.92f;
+ } else {
+ aComponent = std::pow(aComponent, 1.0f / 2.4f) * 1.055f - 0.055f;
+ }
+ return ClampColor(aComponent * 255.0f);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RelativeLuminanceUtils_h
diff --git a/gfx/src/TiledRegion.cpp b/gfx/src/TiledRegion.cpp
new file mode 100644
index 0000000000..32c19f56b6
--- /dev/null
+++ b/gfx/src/TiledRegion.cpp
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TiledRegion.h"
+
+#include <algorithm>
+
+#include "mozilla/fallible.h"
+
+namespace mozilla {
+namespace gfx {
+
+static const int32_t kTileSize = 256;
+static const size_t kMaxTiles = 1000;
+
+/**
+ * TiledRegionImpl stores an array of non-empty rectangles (pixman_box32_ts) to
+ * represent the region. Each rectangle is contained in a single tile;
+ * rectangles never cross tile boundaries. The rectangles are sorted by their
+ * tile's origin in top-to-bottom, left-to-right order.
+ * (Note that this can mean that a rectangle r1 can come before another
+ * rectangle r2 even if r2.y1 < r1.y1, as long as the two rects are in the same
+ * row of tiles and r1.x1 < r2.x1.)
+ * Empty tiles take up no space in the array - there is no rectangle stored for
+ * them. As a result, any algorithm that needs to deal with empty tiles will
+ * iterate through the mRects array and compare the positions of two
+ * consecutive rects to figure out whether there are any empty tiles between
+ * them.
+ */
+
+static pixman_box32_t IntersectionOfNonEmptyBoxes(const pixman_box32_t& aBox1,
+ const pixman_box32_t& aBox2) {
+ return pixman_box32_t{
+ std::max(aBox1.x1, aBox2.x1), std::max(aBox1.y1, aBox2.y1),
+ std::min(aBox1.x2, aBox2.x2), std::min(aBox1.y2, aBox2.y2)};
+}
+
+// A TileIterator points to a specific tile inside a certain tile range, or to
+// the end of the tile range. Advancing a TileIterator will move to the next
+// tile inside the range (or to the range end). The next tile is either the
+// tile to the right of the current one, or the first tile of the next tile
+// row if the current tile is already the last tile in the row.
+class TileIterator {
+ public:
+ TileIterator(const pixman_box32_t& aTileBounds, const IntPoint& aPosition)
+ : mTileBounds(aTileBounds), mPos(aPosition) {}
+
+ bool operator!=(const TileIterator& aOther) { return mPos != aOther.mPos; }
+ bool operator==(const TileIterator& aOther) { return mPos == aOther.mPos; }
+
+ IntPoint operator*() const { return mPos; }
+
+ const TileIterator& operator++() {
+ mPos.x += kTileSize;
+ if (mPos.x >= mTileBounds.x2) {
+ mPos.x = mTileBounds.x1;
+ mPos.y += kTileSize;
+ }
+ return *this;
+ }
+
+ TileIterator& operator=(const IntPoint& aPosition) {
+ mPos = aPosition;
+ return *this;
+ }
+
+ bool IsBeforeTileContainingPoint(const IntPoint& aPoint) const {
+ return (mPos.y + kTileSize) <= aPoint.y ||
+ (mPos.y <= aPoint.y && (mPos.x + kTileSize) <= aPoint.x);
+ }
+
+ bool IsAtTileContainingPoint(const IntPoint& aPoint) const {
+ return mPos.y <= aPoint.y && aPoint.y < (mPos.y + kTileSize) &&
+ mPos.x <= aPoint.x && aPoint.x < (mPos.x + kTileSize);
+ }
+
+ pixman_box32_t IntersectionWith(const pixman_box32_t& aRect) const {
+ pixman_box32_t tile = {mPos.x, mPos.y, mPos.x + kTileSize,
+ mPos.y + kTileSize};
+ return IntersectionOfNonEmptyBoxes(tile, aRect);
+ }
+
+ private:
+ const pixman_box32_t& mTileBounds;
+ IntPoint mPos;
+};
+
+// A TileRange describes a range of tiles contained inside a certain tile
+// bounds (which is a rectangle that includes all tiles that you're
+// interested in). The tile range can start and end at any point inside a
+// tile row.
+// The tile range end is described by the tile that starts at the bottom
+// left corner of the tile bounds, i.e. the first tile under the tile
+// bounds.
+class TileRange {
+ public:
+ // aTileBounds, aStart and aEnd need to be aligned with the tile grid.
+ TileRange(const pixman_box32_t& aTileBounds, const IntPoint& aStart,
+ const IntPoint& aEnd)
+ : mTileBounds(aTileBounds), mStart(aStart), mEnd(aEnd) {}
+ // aTileBounds needs to be aligned with the tile grid.
+ explicit TileRange(const pixman_box32_t& aTileBounds)
+ : mTileBounds(aTileBounds),
+ mStart(mTileBounds.x1, mTileBounds.y1),
+ mEnd(mTileBounds.x1, mTileBounds.y2) {}
+
+ TileIterator Begin() const { return TileIterator(mTileBounds, mStart); }
+ TileIterator End() const { return TileIterator(mTileBounds, mEnd); }
+
+ // The number of tiles in this tile range.
+ size_t Length() const {
+ if (mEnd.y == mStart.y) {
+ return (mEnd.x - mStart.x) / kTileSize;
+ }
+ int64_t numberOfFullRows =
+ (((int64_t)mEnd.y - (int64_t)mStart.y) / kTileSize) - 1;
+ int64_t tilesInFirstRow =
+ ((int64_t)mTileBounds.x2 - (int64_t)mStart.x) / kTileSize;
+ int64_t tilesInLastRow =
+ ((int64_t)mEnd.x - (int64_t)mTileBounds.x1) / kTileSize;
+ int64_t tilesInFullRow =
+ ((int64_t)mTileBounds.x2 - (int64_t)mTileBounds.x1) / kTileSize;
+ int64_t total =
+ tilesInFirstRow + (tilesInFullRow * numberOfFullRows) + tilesInLastRow;
+ MOZ_ASSERT(total > 0);
+ // On 32bit systems the total may be larger than what fits in a size_t (4
+ // bytes), so clamp it to size_t's max value in that case.
+ return static_cast<uint64_t>(total) >=
+ static_cast<uint64_t>(std::numeric_limits<size_t>::max())
+ ? std::numeric_limits<size_t>::max()
+ : static_cast<size_t>(total);
+ }
+
+ // If aTileOrigin does not describe a tile inside our tile bounds, move it
+ // to the next tile that you'd encounter by "advancing" a tile iterator
+ // inside these tile bounds. If aTileOrigin is after the last tile inside
+ // our tile bounds, move it to the range end tile.
+ // The result of this method is a valid end tile for a tile range with our
+ // tile bounds.
+ IntPoint MoveIntoBounds(const IntPoint& aTileOrigin) const {
+ IntPoint p = aTileOrigin;
+ if (p.x < mTileBounds.x1) {
+ p.x = mTileBounds.x1;
+ } else if (p.x >= mTileBounds.x2) {
+ p.x = mTileBounds.x1;
+ p.y += kTileSize;
+ }
+ if (p.y < mTileBounds.y1) {
+ p.y = mTileBounds.y1;
+ p.x = mTileBounds.x1;
+ } else if (p.y >= mTileBounds.y2) {
+ // There's only one valid state after the end of the tile range, and
+ // that's the bottom left point of the tile bounds.
+ p.x = mTileBounds.x1;
+ p.y = mTileBounds.y2;
+ }
+ return p;
+ }
+
+ private:
+ const pixman_box32_t& mTileBounds;
+ const IntPoint mStart;
+ const IntPoint mEnd;
+};
+
+static IntPoint TileContainingPoint(const IntPoint& aPoint) {
+ return IntPoint(RoundDownToMultiple(aPoint.x, kTileSize),
+ RoundDownToMultiple(aPoint.y, kTileSize));
+}
+
+enum class IterationAction : uint8_t { CONTINUE, STOP };
+
+enum class IterationEndReason : uint8_t { NOT_STOPPED, STOPPED };
+
+template <typename HandleEmptyTilesFunction,
+ typename HandleNonEmptyTileFunction, typename RectArrayT>
+IterationEndReason ProcessIntersectedTiles(
+ const pixman_box32_t& aRect, RectArrayT& aRectArray,
+ HandleEmptyTilesFunction aHandleEmptyTiles,
+ HandleNonEmptyTileFunction aHandleNonEmptyTile) {
+ pixman_box32_t tileBounds = {RoundDownToMultiple(aRect.x1, kTileSize),
+ RoundDownToMultiple(aRect.y1, kTileSize),
+ RoundUpToMultiple(aRect.x2, kTileSize),
+ RoundUpToMultiple(aRect.y2, kTileSize)};
+ if (tileBounds.x2 < tileBounds.x1 || tileBounds.y2 < tileBounds.y1) {
+ // RoundUpToMultiple probably overflowed. Bail out.
+ return IterationEndReason::STOPPED;
+ }
+
+ TileRange tileRange(tileBounds);
+ TileIterator rangeEnd = tileRange.End();
+
+ // tileIterator points to the next tile in tileRange, or to rangeEnd if we're
+ // done.
+ TileIterator tileIterator = tileRange.Begin();
+
+ // We iterate over the rectangle array. Depending on the position of the
+ // rectangle we encounter, we may need to advance tileIterator by zero, one,
+ // or more tiles:
+ // - Zero if the rectangle we encountered is outside the tiles that
+ // intersect aRect.
+ // - One if the rectangle is in the exact tile that we're interested in next
+ // (i.e. the tile that tileIterator points at).
+ // - More than one if the encountered rectangle is in a tile that's further
+ // to the right or to the bottom than tileIterator. In that case there is
+ // at least one empty tile between the last rectangle we encountered and
+ // the current one.
+ for (size_t i = 0; i < aRectArray.Length() && tileIterator != rangeEnd; i++) {
+ MOZ_ASSERT(aRectArray[i].x1 < aRectArray[i].x2 &&
+ aRectArray[i].y1 < aRectArray[i].y2,
+ "empty rect");
+ IntPoint rectOrigin(aRectArray[i].x1, aRectArray[i].y1);
+ if (tileIterator.IsBeforeTileContainingPoint(rectOrigin)) {
+ IntPoint tileOrigin = TileContainingPoint(rectOrigin);
+ IntPoint afterEmptyTiles = tileRange.MoveIntoBounds(tileOrigin);
+ TileRange emptyTiles(tileBounds, *tileIterator, afterEmptyTiles);
+ if (aHandleEmptyTiles(aRectArray, i, emptyTiles) ==
+ IterationAction::STOP) {
+ return IterationEndReason::STOPPED;
+ }
+ tileIterator = afterEmptyTiles;
+ if (tileIterator == rangeEnd) {
+ return IterationEndReason::NOT_STOPPED;
+ }
+ }
+ if (tileIterator.IsAtTileContainingPoint(rectOrigin)) {
+ pixman_box32_t rectIntersection = tileIterator.IntersectionWith(aRect);
+ if (aHandleNonEmptyTile(aRectArray, i, rectIntersection) ==
+ IterationAction::STOP) {
+ return IterationEndReason::STOPPED;
+ }
+ ++tileIterator;
+ }
+ }
+
+ if (tileIterator != rangeEnd) {
+ // We've looked at all of our existing rectangles but haven't covered all
+ // of the tiles that we're interested in yet. So we need to deal with the
+ // remaining tiles now.
+ size_t endIndex = aRectArray.Length();
+ TileRange emptyTiles(tileBounds, *tileIterator, *rangeEnd);
+ if (aHandleEmptyTiles(aRectArray, endIndex, emptyTiles) ==
+ IterationAction::STOP) {
+ return IterationEndReason::STOPPED;
+ }
+ }
+ return IterationEndReason::NOT_STOPPED;
+}
+
+static pixman_box32_t UnionBoundsOfNonEmptyBoxes(const pixman_box32_t& aBox1,
+ const pixman_box32_t& aBox2) {
+ return {std::min(aBox1.x1, aBox2.x1), std::min(aBox1.y1, aBox2.y1),
+ std::max(aBox1.x2, aBox2.x2), std::max(aBox1.y2, aBox2.y2)};
+}
+
+// Returns true when adding the rectangle was successful, and false if
+// allocation failed.
+// When this returns false, our internal state might not be consistent and we
+// need to be cleared.
+bool TiledRegionImpl::AddRect(const pixman_box32_t& aRect) {
+ // We are adding a rectangle that can span multiple tiles.
+ // For each empty tile that aRect intersects, we need to add the intersection
+ // of aRect with that tile to mRects, respecting the order of mRects.
+ // For each tile that already has a rectangle, we need to enlarge that
+ // existing rectangle to include the intersection of aRect with the tile.
+ return ProcessIntersectedTiles(
+ aRect, mRects,
+ [&aRect](nsTArray<pixman_box32_t>& rects, size_t& rectIndex,
+ TileRange emptyTiles) {
+ CheckedInt<size_t> newLength(rects.Length());
+ newLength += emptyTiles.Length();
+ if (!newLength.isValid() || newLength.value() >= kMaxTiles ||
+ !rects.InsertElementsAt(rectIndex, emptyTiles.Length(),
+ fallible)) {
+ return IterationAction::STOP;
+ }
+ for (TileIterator tileIt = emptyTiles.Begin();
+ tileIt != emptyTiles.End(); ++tileIt, ++rectIndex) {
+ rects[rectIndex] = tileIt.IntersectionWith(aRect);
+ }
+ return IterationAction::CONTINUE;
+ },
+ [](nsTArray<pixman_box32_t>& rects, size_t rectIndex,
+ const pixman_box32_t& rectIntersectionWithTile) {
+ rects[rectIndex] = UnionBoundsOfNonEmptyBoxes(
+ rects[rectIndex], rectIntersectionWithTile);
+ return IterationAction::CONTINUE;
+ }) == IterationEndReason::NOT_STOPPED;
+}
+
+static bool NonEmptyBoxesIntersect(const pixman_box32_t& aBox1,
+ const pixman_box32_t& aBox2) {
+ return aBox1.x1 < aBox2.x2 && aBox2.x1 < aBox1.x2 && aBox1.y1 < aBox2.y2 &&
+ aBox2.y1 < aBox1.y2;
+}
+
+bool TiledRegionImpl::Intersects(const pixman_box32_t& aRect) const {
+ // aRect intersects this region if it intersects any of our rectangles.
+ return ProcessIntersectedTiles(
+ aRect, mRects,
+ [](const nsTArray<pixman_box32_t>& rects, size_t& rectIndex,
+ TileRange emptyTiles) {
+ // Ignore empty tiles and keep on iterating.
+ return IterationAction::CONTINUE;
+ },
+ [](const nsTArray<pixman_box32_t>& rects, size_t rectIndex,
+ const pixman_box32_t& rectIntersectionWithTile) {
+ if (NonEmptyBoxesIntersect(rects[rectIndex],
+ rectIntersectionWithTile)) {
+ // Found an intersecting rectangle, so aRect intersects this
+ // region.
+ return IterationAction::STOP;
+ }
+ return IterationAction::CONTINUE;
+ }) == IterationEndReason::STOPPED;
+}
+
+static bool NonEmptyBoxContainsNonEmptyBox(const pixman_box32_t& aBox1,
+ const pixman_box32_t& aBox2) {
+ return aBox1.x1 <= aBox2.x1 && aBox2.x2 <= aBox1.x2 && aBox1.y1 <= aBox2.y1 &&
+ aBox2.y2 <= aBox1.y2;
+}
+
+bool TiledRegionImpl::Contains(const pixman_box32_t& aRect) const {
+ // aRect is contained in this region if aRect does not intersect any empty
+ // tiles and, for each non-empty tile, if the intersection of aRect with that
+ // tile is contained in the existing rectangle we have in that tile.
+ return ProcessIntersectedTiles(
+ aRect, mRects,
+ [](const nsTArray<pixman_box32_t>& rects, size_t& rectIndex,
+ TileRange emptyTiles) {
+ // Found an empty tile that intersects aRect, so aRect is not
+ // contained in this region.
+ return IterationAction::STOP;
+ },
+ [](const nsTArray<pixman_box32_t>& rects, size_t rectIndex,
+ const pixman_box32_t& rectIntersectionWithTile) {
+ if (!NonEmptyBoxContainsNonEmptyBox(rects[rectIndex],
+ rectIntersectionWithTile)) {
+ // Our existing rectangle in this tile does not cover the part
+ // of aRect that intersects this tile, so aRect is not
+ // contained in this region.
+ return IterationAction::STOP;
+ }
+ return IterationAction::CONTINUE;
+ }) == IterationEndReason::NOT_STOPPED;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/src/TiledRegion.h b/gfx/src/TiledRegion.h
new file mode 100644
index 0000000000..5508c92d1b
--- /dev/null
+++ b/gfx/src/TiledRegion.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_TILEDREGION_H_
+#define MOZILLA_GFX_TILEDREGION_H_
+
+#include <utility>
+
+#include "mozilla/ArrayView.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsRegion.h"
+#include "pixman.h"
+
+namespace mozilla {
+namespace gfx {
+
+// See TiledRegion.cpp for documentation on TiledRegionImpl.
+class TiledRegionImpl {
+ public:
+ void Clear() { mRects.Clear(); }
+ bool AddRect(const pixman_box32_t& aRect);
+ bool Intersects(const pixman_box32_t& aRect) const;
+ bool Contains(const pixman_box32_t& aRect) const;
+ operator ArrayView<pixman_box32_t>() const {
+ return ArrayView<pixman_box32_t>(mRects);
+ }
+
+ private:
+ nsTArray<pixman_box32_t> mRects;
+};
+
+/**
+ * A auto-simplifying region type that supports one rectangle per tile.
+ * The virtual tile grid is anchored at (0, 0) and has quadratic tiles whose
+ * size is hard-coded as kTileSize in TiledRegion.cpp.
+ * A TiledRegion starts out empty. You can add rectangles or (regular) regions
+ * into it by calling Add(). Add() is a mutating union operation (similar to
+ * OrWith on nsRegion) that's *not* exact, because it will enlarge the region as
+ * necessary to satisfy the "one rectangle per tile" requirement.
+ * Tiled regions convert implicitly to the underlying regular region type.
+ * The only way to remove parts from a TiledRegion is by calling SetEmpty().
+ */
+template <typename RegionT>
+class TiledRegion {
+ public:
+ typedef typename RegionT::RectType RectT;
+
+ TiledRegion() : mCoversBounds(false) {}
+
+ TiledRegion(const TiledRegion& aOther)
+ : mBounds(aOther.mBounds), mImpl(aOther.mImpl), mCoversBounds(false) {}
+
+ TiledRegion(TiledRegion&& aOther)
+ : mBounds(aOther.mBounds),
+ mImpl(std::move(aOther.mImpl)),
+ mCoversBounds(false) {}
+
+ RegionT GetRegion() const {
+ if (mBounds.IsEmpty()) {
+ return RegionT();
+ }
+ if (mCoversBounds) {
+ // Rect limit hit or allocation failed, treat as 1 rect.
+ return RegionT(mBounds);
+ }
+ return RegionT(mImpl);
+ }
+
+ TiledRegion& operator=(const TiledRegion& aOther) {
+ if (&aOther != this) {
+ mBounds = aOther.mBounds;
+ mImpl = aOther.mImpl;
+ mCoversBounds = aOther.mCoversBounds;
+ }
+ return *this;
+ }
+
+ void Add(const RectT& aRect) {
+ if (aRect.IsEmpty()) {
+ return;
+ }
+
+ Maybe<RectT> newBounds = mBounds.SafeUnion(aRect);
+ if (!newBounds) {
+ return;
+ }
+ mBounds = newBounds.value();
+ MOZ_ASSERT(!mBounds.Overflows());
+
+ if (mCoversBounds) {
+ return;
+ }
+
+ if (!mImpl.AddRect(RectToBox(aRect))) {
+ FallBackToBounds();
+ }
+ }
+
+ void Add(const RegionT& aRegion) {
+ Maybe<RectT> newBounds = mBounds.SafeUnion(aRegion.GetBounds());
+ if (!newBounds) {
+ return;
+ }
+ mBounds = newBounds.value();
+ MOZ_ASSERT(!mBounds.Overflows());
+
+ if (mCoversBounds) {
+ return;
+ }
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ RectT r = iter.Get();
+ if (r.IsEmpty() || r.Overflows()) {
+ // This can happen if e.g. a negative-width rect was wrapped into a
+ // region. Treat it the same as we would if such a rect was passed to
+ // the Add(const RectT&) function.
+ continue;
+ }
+ if (!mImpl.AddRect(RectToBox(r))) {
+ FallBackToBounds();
+ return;
+ }
+ }
+ }
+
+ bool IsEmpty() const { return mBounds.IsEmpty(); }
+
+ void SetEmpty() {
+ mBounds.SetEmpty();
+ mImpl.Clear();
+ mCoversBounds = false;
+ }
+
+ RectT GetBounds() const { return mBounds; }
+ bool CoversBounds() const { return mCoversBounds; }
+
+ bool Intersects(const RectT& aRect) const {
+ if (aRect.IsEmpty()) {
+ return true;
+ }
+ if (aRect.Overflows() || !mBounds.Intersects(aRect)) {
+ return false;
+ }
+ if (mCoversBounds) {
+ return true;
+ }
+
+ return mImpl.Intersects(RectToBox(aRect));
+ }
+
+ bool Contains(const RectT& aRect) const {
+ if (aRect.IsEmpty()) {
+ return true;
+ }
+ if (aRect.Overflows() || !mBounds.Contains(aRect)) {
+ return false;
+ }
+ if (mCoversBounds) {
+ return true;
+ }
+ return mImpl.Contains(RectToBox(aRect));
+ }
+
+ private:
+ void FallBackToBounds() {
+ mCoversBounds = true;
+ mImpl.Clear();
+ }
+
+ static pixman_box32_t RectToBox(const RectT& aRect) {
+ MOZ_ASSERT(!aRect.IsEmpty());
+ MOZ_ASSERT(!aRect.Overflows());
+ return {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()};
+ }
+
+ RectT mBounds;
+ TiledRegionImpl mImpl;
+
+ // mCoversBounds is true if we bailed out due to a large number of tiles.
+ // mCoversBounds being true means that this TiledRegion is just a simple
+ // rectangle (our mBounds).
+ // Once set to true, the TiledRegion will stay in this state until SetEmpty
+ // is called.
+ bool mCoversBounds;
+};
+
+typedef TiledRegion<IntRegion> TiledIntRegion;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_TILEDREGION_H_ */
diff --git a/gfx/src/X11UndefineNone.h b/gfx/src/X11UndefineNone.h
new file mode 100644
index 0000000000..3f0c4aef31
--- /dev/null
+++ b/gfx/src/X11UndefineNone.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// The header <X11/X.h> defines "None" as a macro that expands to "0L".
+// This is terrible because many enumerations have an enumerator named "None".
+// To work around this, we undefine the macro "None", and define a replacement
+// macro named "X11None".
+// Include this header after including X11 headers, where necessary.
+#ifdef None
+# undef None
+# define X11None 0L
+// <X11/X.h> also defines "RevertToNone" as a macro that expands to "(int)None".
+// Since we are undefining "None", that stops working. To keep it working,
+// we undefine "RevertToNone" and redefine it in terms of "X11None".
+# ifdef RevertToNone
+# undef RevertToNone
+# define RevertToNone (int)X11None
+# endif
+#endif
+
+// X11 also defines Always, which conflicts with some style system enum variant
+// names, so get rid of that too, given we don't use it anywhere else.
+#ifdef Always
+# undef Always
+#endif
+
+// And Complex...
+#ifdef Complex
+# undef Complex
+#endif
+
+#ifdef CurrentTime
+# undef CurrentTime
+# define X11CurrentTime 0L
+#endif
+
+// X11/Xlib.h also defines True and False, get rid of those too for
+// the same reasons as above...
+#ifdef True
+# undef True
+# define X11True 1
+#endif
+#ifdef False
+# undef False
+# define X11False 0
+#endif
diff --git a/gfx/src/X11Util.cpp b/gfx/src/X11Util.cpp
new file mode 100644
index 0000000000..709d652bae
--- /dev/null
+++ b/gfx/src/X11Util.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "X11Util.h"
+#include "nsDebug.h" // for NS_ASSERTION, etc
+#include "MainThreadUtils.h" // for NS_IsMainThread
+
+namespace mozilla {
+
+void FindVisualAndDepth(Display* aDisplay, VisualID aVisualID, Visual** aVisual,
+ int* aDepth) {
+ const Screen* screen = DefaultScreenOfDisplay(aDisplay);
+
+ for (int d = 0; d < screen->ndepths; d++) {
+ Depth* d_info = &screen->depths[d];
+ for (int v = 0; v < d_info->nvisuals; v++) {
+ Visual* visual = &d_info->visuals[v];
+ if (visual->visualid == aVisualID) {
+ *aVisual = visual;
+ *aDepth = d_info->depth;
+ return;
+ }
+ }
+ }
+
+ NS_ASSERTION(aVisualID == X11None, "VisualID not on Screen.");
+ *aVisual = nullptr;
+ *aDepth = 0;
+}
+
+void FinishX(Display* aDisplay) {
+ unsigned long lastRequest = NextRequest(aDisplay) - 1;
+ if (lastRequest == LastKnownRequestProcessed(aDisplay)) return;
+
+ XSync(aDisplay, X11False);
+}
+
+ScopedXErrorHandler::ErrorEvent* ScopedXErrorHandler::sXErrorPtr;
+
+int ScopedXErrorHandler::ErrorHandler(Display*, XErrorEvent* ev) {
+ // only record the error if no error was previously recorded.
+ // this means that in case of multiple errors, it's the first error that we
+ // report.
+ if (!sXErrorPtr->mError.error_code) sXErrorPtr->mError = *ev;
+ return 0;
+}
+
+ScopedXErrorHandler::ScopedXErrorHandler(bool aAllowOffMainThread) {
+ if (!aAllowOffMainThread) {
+ // Off main thread usage is not safe in general, but OMTC GL layers uses
+ // this with the main thread blocked, which makes it safe.
+ NS_WARNING_ASSERTION(
+ NS_IsMainThread(),
+ "ScopedXErrorHandler being called off main thread, may cause issues");
+ }
+ // let sXErrorPtr point to this object's mXError object, but don't reset this
+ // mXError object! think of the case of nested ScopedXErrorHandler's.
+ mOldXErrorPtr = sXErrorPtr;
+ sXErrorPtr = &mXError;
+ mOldErrorHandler = XSetErrorHandler(ErrorHandler);
+}
+
+ScopedXErrorHandler::~ScopedXErrorHandler() {
+ sXErrorPtr = mOldXErrorPtr;
+ XSetErrorHandler(mOldErrorHandler);
+}
+
+bool ScopedXErrorHandler::SyncAndGetError(Display* dpy, XErrorEvent* ev) {
+ FinishX(dpy);
+
+ bool retval = mXError.mError.error_code != 0;
+ if (ev) *ev = mXError.mError;
+ mXError = ErrorEvent(); // reset
+ return retval;
+}
+
+} // namespace mozilla
diff --git a/gfx/src/X11Util.h b/gfx/src/X11Util.h
new file mode 100644
index 0000000000..fc583fb6d4
--- /dev/null
+++ b/gfx/src/X11Util.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_X11Util_h
+#define mozilla_X11Util_h
+
+// Utilities common to all X clients, regardless of UI toolkit.
+
+#if defined(MOZ_WIDGET_GTK)
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+#else
+# error Unknown toolkit
+#endif
+
+#include <string.h> // for memset
+#include "mozilla/Scoped.h" // for SCOPED_TEMPLATE
+
+namespace mozilla {
+
+/**
+ * Return the default X Display created and used by the UI toolkit.
+ */
+inline Display* DefaultXDisplay() {
+#if defined(MOZ_WIDGET_GTK)
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ return GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ }
+#endif
+ return nullptr;
+}
+
+/**
+ * Sets *aVisual to point to aDisplay's Visual struct corresponding to
+ * aVisualID, and *aDepth to its depth. When aVisualID is None, these are set
+ * to nullptr and 0 respectively. Both out-parameter pointers are assumed
+ * non-nullptr.
+ */
+void FindVisualAndDepth(Display* aDisplay, VisualID aVisualID, Visual** aVisual,
+ int* aDepth);
+
+/**
+ * Ensure that all X requests have been processed.
+ *
+ * This is similar to XSync, but doesn't need a round trip if the previous
+ * request was synchronous or if events have been received since the last
+ * request. Subsequent FinishX calls will be noops if there have been no
+ * intermediate requests.
+ */
+
+void FinishX(Display* aDisplay);
+
+/**
+ * Invoke XFree() on a pointer to memory allocated by Xlib (if the
+ * pointer is nonnull) when this class goes out of scope.
+ */
+template <typename T>
+struct ScopedXFreePtrTraits {
+ typedef T* type;
+ static T* empty() { return nullptr; }
+ static void release(T* ptr) {
+ if (ptr != nullptr) XFree(ptr);
+ }
+};
+SCOPED_TEMPLATE(ScopedXFree, ScopedXFreePtrTraits)
+
+/**
+ * On construction, set a graceful X error handler that doesn't crash the
+ * application and records X errors. On destruction, restore the X error handler
+ * to what it was before construction.
+ *
+ * The SyncAndGetError() method allows to know whether a X error occurred,
+ * optionally allows to get the full XErrorEvent, and resets the recorded X
+ * error state so that a single X error will be reported only once.
+ *
+ * Nesting is correctly handled: multiple nested ScopedXErrorHandler's don't
+ * interfere with each other's state. However, if SyncAndGetError is not called
+ * on the nested ScopedXErrorHandler, then any X errors caused by X calls made
+ * while the nested ScopedXErrorHandler was in place may then be caught by the
+ * other ScopedXErrorHandler. This is just a result of X being asynchronous and
+ * us not doing any implicit syncing: the only method in this class what causes
+ * syncing is SyncAndGetError().
+ *
+ * This class is not thread-safe at all. It is assumed that only one thread is
+ * using any ScopedXErrorHandler's. Given that it's not used on Mac, it should
+ * be easy to make it thread-safe by using thread-local storage with __thread.
+ */
+class ScopedXErrorHandler {
+ public:
+ // trivial wrapper around XErrorEvent, just adding ctor initializing by zero.
+ struct ErrorEvent {
+ XErrorEvent mError;
+
+ ErrorEvent() { memset(this, 0, sizeof(ErrorEvent)); }
+ };
+
+ private:
+ // this ScopedXErrorHandler's ErrorEvent object
+ ErrorEvent mXError;
+
+ // static pointer for use by the error handler
+ static ErrorEvent* sXErrorPtr;
+
+ // what to restore sXErrorPtr to on destruction
+ ErrorEvent* mOldXErrorPtr;
+
+ // what to restore the error handler to on destruction
+ int (*mOldErrorHandler)(Display*, XErrorEvent*);
+
+ public:
+ static int ErrorHandler(Display*, XErrorEvent* ev);
+
+ /**
+ * @param aAllowOffMainThread whether to warn if used off main thread
+ */
+ explicit ScopedXErrorHandler(bool aAllowOffMainThread = false);
+
+ ~ScopedXErrorHandler();
+
+ /** \returns true if a X error occurred since the last time this method was
+ * called on this ScopedXErrorHandler object, or since the creation of this
+ * ScopedXErrorHandler object if this method was never called on it.
+ *
+ * \param ev this optional parameter, if set, will be filled with the
+ * XErrorEvent object. If multiple errors occurred, the first one will be
+ * returned.
+ */
+ bool SyncAndGetError(Display* dpy, XErrorEvent* ev = nullptr);
+};
+
+class OffMainThreadScopedXErrorHandler : public ScopedXErrorHandler {
+ public:
+ OffMainThreadScopedXErrorHandler() : ScopedXErrorHandler(true) {}
+};
+
+} // namespace mozilla
+
+#endif // mozilla_X11Util_h
diff --git a/gfx/src/components.conf b/gfx/src/components.conf
new file mode 100644
index 0000000000..d1c5d69351
--- /dev/null
+++ b/gfx/src/components.conf
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{a6cf9115-15b3-11d2-932e-00805f8add32}',
+ 'contract_ids': ['@mozilla.org/gfx/fontenumerator;1'],
+ 'type': 'nsThebesFontEnumerator',
+ 'headers': ['/gfx/src/nsThebesFontEnumerator.h'],
+ },
+]
diff --git a/gfx/src/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp
new file mode 100644
index 0000000000..e89d3769a9
--- /dev/null
+++ b/gfx/src/gfxCrashReporterUtils.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxCrashReporterUtils.h"
+#include <string.h> // for strcmp
+#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2
+#include "mozilla/SchedulerGroup.h" // for SchedulerGroup
+#include "mozilla/Services.h" // for GetObserverService
+#include "mozilla/StaticMutex.h"
+#include "mozilla/mozalloc.h" // for operator new, etc
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "MainThreadUtils.h" // for NS_IsMainThread
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsError.h" // for NS_OK, NS_FAILED, nsresult
+#include "nsExceptionHandler.h" // for AppendAppNotesToCrashReport
+#include "nsID.h"
+#include "nsIObserver.h" // for nsIObserver, etc
+#include "nsIObserverService.h" // for nsIObserverService
+#include "nsIRunnable.h" // for nsIRunnable
+#include "nsISupports.h"
+#include "nsTArray.h" // for nsTArray
+#include "nscore.h" // for NS_IMETHOD, NS_IMETHODIMP, etc
+
+namespace mozilla {
+
+static nsTArray<nsCString>* gFeaturesAlreadyReported = nullptr;
+static StaticMutex gFeaturesAlreadyReportedMutex;
+
+class ObserverToDestroyFeaturesAlreadyReported final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ ObserverToDestroyFeaturesAlreadyReported() = default;
+
+ private:
+ virtual ~ObserverToDestroyFeaturesAlreadyReported() = default;
+};
+
+NS_IMPL_ISUPPORTS(ObserverToDestroyFeaturesAlreadyReported, nsIObserver)
+
+NS_IMETHODIMP
+ObserverToDestroyFeaturesAlreadyReported::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ StaticMutexAutoLock al(gFeaturesAlreadyReportedMutex);
+ if (gFeaturesAlreadyReported) {
+ delete gFeaturesAlreadyReported;
+ gFeaturesAlreadyReported = nullptr;
+ }
+ }
+ return NS_OK;
+}
+
+class RegisterObserverRunnable : public Runnable {
+ public:
+ RegisterObserverRunnable() : Runnable("RegisterObserverRunnable") {}
+ NS_IMETHOD Run() override {
+ // LeakLog made me do this. Basically, I just wanted
+ // gFeaturesAlreadyReported to be a static nsTArray<nsCString>, and LeakLog
+ // was complaining about leaks like this:
+ // leaked 1 instance of nsTArray_base with size 8 bytes
+ // leaked 7 instances of nsStringBuffer with size 8 bytes each (56 bytes
+ // total)
+ // So this is a work-around using a pointer, and using a nsIObserver to
+ // deallocate on xpcom shutdown. Yay for fighting bloat.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return NS_OK;
+ RefPtr<ObserverToDestroyFeaturesAlreadyReported> observer =
+ new ObserverToDestroyFeaturesAlreadyReported;
+ observerService->AddObserver(observer, "xpcom-shutdown", false);
+ return NS_OK;
+ }
+};
+
+class AppendAppNotesRunnable : public Runnable {
+ public:
+ explicit AppendAppNotesRunnable(const nsACString& aFeatureStr)
+ : Runnable("AppendAppNotesRunnable"), mFeatureString(aFeatureStr) {}
+
+ NS_IMETHOD Run() override {
+ CrashReporter::AppendAppNotesToCrashReport(mFeatureString);
+ return NS_OK;
+ }
+
+ private:
+ nsAutoCString mFeatureString;
+};
+
+void ScopedGfxFeatureReporter::WriteAppNote(char statusChar,
+ int32_t statusNumber) {
+ StaticMutexAutoLock al(gFeaturesAlreadyReportedMutex);
+
+ if (!gFeaturesAlreadyReported) {
+ gFeaturesAlreadyReported = new nsTArray<nsCString>;
+ nsCOMPtr<nsIRunnable> r = new RegisterObserverRunnable();
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+
+ nsAutoCString featureString;
+ if (statusNumber == 0) {
+ featureString.AppendPrintf("%s%c ", mFeature, statusChar);
+ } else {
+ featureString.AppendPrintf("%s%c%d ", mFeature, statusChar, statusNumber);
+ }
+
+ if (!gFeaturesAlreadyReported->Contains(featureString)) {
+ gFeaturesAlreadyReported->AppendElement(featureString);
+ AppNote(featureString);
+ }
+}
+
+void ScopedGfxFeatureReporter::AppNote(const nsACString& aMessage) {
+ if (NS_IsMainThread()) {
+ CrashReporter::AppendAppNotesToCrashReport(aMessage);
+ } else {
+ nsCOMPtr<nsIRunnable> r = new AppendAppNotesRunnable(aMessage);
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+}
+
+} // end namespace mozilla
diff --git a/gfx/src/gfxCrashReporterUtils.h b/gfx/src/gfxCrashReporterUtils.h
new file mode 100644
index 0000000000..30b670df61
--- /dev/null
+++ b/gfx/src/gfxCrashReporterUtils.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef gfxCrashReporterUtils_h__
+#define gfxCrashReporterUtils_h__
+
+#include "nsString.h"
+
+namespace mozilla {
+
+/** \class ScopedGfxFeatureReporter
+ *
+ * On creation, adds "FeatureName?" to AppNotes
+ * On destruction, adds "FeatureName-", or "FeatureName+" if you called
+ * SetSuccessful().
+ *
+ * Any such string is added at most once to AppNotes, and is subsequently
+ * skipped.
+ *
+ * This ScopedGfxFeatureReporter class is designed to be fool-proof to use in
+ * functions that have many exit points. We don't want to encourage having
+ * function with many exit points. It just happens that our graphics features
+ * initialization functions are like that.
+ */
+class ScopedGfxFeatureReporter {
+ public:
+ explicit ScopedGfxFeatureReporter(const char* aFeature, bool aForce = false)
+ : mFeature(aFeature), mStatusChar('-'), mStatusNumber(0) {
+ WriteAppNote(aForce ? '!' : '?', 0);
+ }
+ ~ScopedGfxFeatureReporter() { WriteAppNote(mStatusChar, mStatusNumber); }
+ void SetSuccessful() { mStatusChar = '+'; }
+ void SetSuccessful(int32_t aNumber) {
+ mStatusChar = '+';
+ mStatusNumber = aNumber;
+ }
+
+ static void AppNote(const nsACString& aMessage);
+
+ class AppNoteWritingRunnable;
+
+ protected:
+ const char* mFeature;
+ char mStatusChar;
+ int32_t mStatusNumber;
+
+ private:
+ void WriteAppNote(char statusChar, int32_t statusNumber);
+};
+
+} // end namespace mozilla
+
+#endif // gfxCrashReporterUtils_h__
diff --git a/gfx/src/gfxTelemetry.cpp b/gfx/src/gfxTelemetry.cpp
new file mode 100644
index 0000000000..f434164c40
--- /dev/null
+++ b/gfx/src/gfxTelemetry.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "gfxTelemetry.h"
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace gfx {
+
+const char* FeatureStatusToString(FeatureStatus aStatus) {
+ switch (aStatus) {
+ case FeatureStatus::Unused:
+ return "unused";
+ case FeatureStatus::Unavailable:
+ return "unavailable";
+ case FeatureStatus::UnavailableInSafeMode:
+ return "unavailable-in-safe-mode";
+ case FeatureStatus::UnavailableNoGpuProcess:
+ return "unavailable-no-gpu-process";
+ case FeatureStatus::UnavailableNoHwCompositing:
+ return "unavailable-no-hw-compositing";
+ case FeatureStatus::UnavailableNotBuilt:
+ return "unavailable-not-built";
+ case FeatureStatus::UnavailableNoAngle:
+ return "unavailable-no-angle";
+ case FeatureStatus::UnavailableNoWebRender:
+ return "unavailable-no-webrender";
+ case FeatureStatus::CrashedInHandler:
+ return "crashed";
+ case FeatureStatus::Blocked:
+ return "blocked";
+ case FeatureStatus::BlockedDeviceUnknown:
+ return "blocked-device-unknown";
+ case FeatureStatus::BlockedDeviceTooOld:
+ return "blocked-device-too-old";
+ case FeatureStatus::BlockedVendorUnsupported:
+ return "blocked-vendor-unsupported";
+ case FeatureStatus::BlockedHasBattery:
+ return "blocked-has-battery";
+ case FeatureStatus::BlockedScreenTooLarge:
+ return "blocked-screen-too-large";
+ case FeatureStatus::BlockedScreenUnknown:
+ return "blocked-screen-unknown";
+ case FeatureStatus::BlockedNoGfxInfo:
+ return "blocked-no-gfx-info";
+ case FeatureStatus::BlockedOverride:
+ return "blocked-override";
+ case FeatureStatus::BlockedReleaseChannelIntel:
+ return "blocked-release-channel-intel";
+ case FeatureStatus::BlockedReleaseChannelAMD:
+ return "blocked-release-channel-amd";
+ case FeatureStatus::BlockedReleaseChannelNvidia:
+ return "blocked-release-channel-nvidia";
+ case FeatureStatus::BlockedReleaseChannelBattery:
+ return "blocked-release-channel-battery";
+ case FeatureStatus::BlockedReleaseChannelAndroid:
+ return "blocked-release-channel-android";
+ case FeatureStatus::Denied:
+ return "denied";
+ case FeatureStatus::Blocklisted:
+ return "blocklisted";
+ case FeatureStatus::OptIn:
+ return "opt-in";
+ case FeatureStatus::Failed:
+ return "failed";
+ case FeatureStatus::Disabled:
+ return "disabled";
+ case FeatureStatus::Available:
+ return "available";
+ case FeatureStatus::ForceEnabled:
+ return "force_enabled";
+ case FeatureStatus::CrashedOnStartup:
+ return "crashed_on_startup";
+ case FeatureStatus::Broken:
+ return "broken";
+ default:
+ MOZ_ASSERT_UNREACHABLE("missing status case");
+ return "unknown";
+ }
+}
+
+bool IsFeatureStatusFailure(FeatureStatus aStatus) {
+ return !(aStatus == FeatureStatus::Unused ||
+ aStatus == FeatureStatus::Available ||
+ aStatus == FeatureStatus::ForceEnabled);
+}
+
+bool IsFeatureStatusSuccess(FeatureStatus aStatus) {
+ return aStatus == FeatureStatus::Available ||
+ aStatus == FeatureStatus::ForceEnabled;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/src/gfxTelemetry.h b/gfx/src/gfxTelemetry.h
new file mode 100644
index 0000000000..37d603674a
--- /dev/null
+++ b/gfx/src/gfxTelemetry.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#ifndef gfx_src_gfxTelemetry_h__
+#define gfx_src_gfxTelemetry_h__
+
+#include <cstdint>
+
+namespace mozilla {
+namespace gfx {
+
+// Describes the status of a graphics feature, in terms of whether or not we've
+// attempted to initialize the feature, and if so, whether or not it succeeded
+// (and if not, why).
+enum class FeatureStatus {
+ // This feature has not been requested.
+ Unused,
+
+ // This feature is unavailable due to Safe Mode, not being included with
+ // the operating system, or a dependent feature being disabled.
+ Unavailable,
+ UnavailableInSafeMode,
+ UnavailableNoGpuProcess,
+ UnavailableNoHwCompositing,
+ UnavailableNotBuilt,
+ UnavailableNoAngle,
+ UnavailableNoWebRender,
+
+ // This feature crashed immediately when we tried to initialize it, but we
+ // were able to recover via SEH (or something similar).
+ CrashedInHandler,
+
+ // This feature was blocked for reasons outside the blocklist, such as a
+ // runtime test failing.
+ Blocked,
+ BlockedDeviceUnknown,
+ BlockedDeviceTooOld,
+ BlockedVendorUnsupported,
+ BlockedHasBattery,
+ BlockedScreenTooLarge,
+ BlockedScreenUnknown,
+ BlockedNoGfxInfo,
+ BlockedOverride,
+ BlockedReleaseChannelIntel,
+ BlockedReleaseChannelAMD,
+ BlockedReleaseChannelNvidia,
+ BlockedReleaseChannelBattery,
+ BlockedReleaseChannelAndroid,
+
+ // This feature has been blocked by the allowlist.
+ Denied,
+
+ // This feature has been blocked by the graphics blocklist.
+ Blocklisted,
+
+ // This feature is disabled by default, and so activation isn't attempted
+ // unless something explicitly enables it.
+ OptIn,
+
+ // This feature was attempted but failed to activate.
+ Failed,
+
+ // This feature was explicitly disabled by the user.
+ Disabled,
+
+ // This feature is available for use.
+ Available,
+
+ // This feature was explicitly force-enabled by the user.
+ ForceEnabled,
+
+ // This feature was disabled due to the startup crash guard.
+ CrashedOnStartup,
+
+ // This feature was attempted but later determined to be broken.
+ Broken,
+
+ // Add new entries above here.
+ LAST
+};
+
+const char* FeatureStatusToString(FeatureStatus aStatus);
+bool IsFeatureStatusFailure(FeatureStatus aStatus);
+bool IsFeatureStatusSuccess(FeatureStatus aStatus);
+
+enum class TelemetryDeviceCode : uint32_t { Content = 0, Image = 1, D2D1 = 2 };
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // gfx_src_gfxTelemetry_h__
diff --git a/gfx/src/moz.build b/gfx/src/moz.build
new file mode 100644
index 0000000000..51058dd940
--- /dev/null
+++ b/gfx/src/moz.build
@@ -0,0 +1,97 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIFontEnumerator.idl",
+]
+
+XPIDL_MODULE = "gfx"
+
+DEFINES["MOZ_APP_VERSION"] = '"%s"' % CONFIG["MOZ_APP_VERSION"]
+
+EXPORTS += [
+ "DriverCrashGuard.h",
+ "FilterDescription.h",
+ "FilterSupport.h",
+ "gfxCrashReporterUtils.h",
+ "gfxTelemetry.h",
+ "nsBoundingMetrics.h",
+ "nsColor.h",
+ "nsColorNameList.h",
+ "nsColorNames.h",
+ "nsCoord.h",
+ "nsDeviceContext.h",
+ "nsFont.h",
+ "nsFontMetrics.h",
+ "nsGfxCIID.h",
+ "nsITheme.h",
+ "nsMargin.h",
+ "nsPoint.h",
+ "nsRect.h",
+ "nsRectAbsolute.h",
+ "nsRegion.h",
+ "nsRegionFwd.h",
+ "nsSize.h",
+ "nsTransform2D.h",
+ "RegionBuilder.h",
+ "X11UndefineNone.h",
+]
+
+EXPORTS.mozilla += [
+ "AppUnits.h",
+ "ArrayView.h",
+ "FontPropertyTypes.h",
+ "RelativeLuminanceUtils.h",
+]
+
+EXPORTS.mozilla.gfx += [
+ "CompositorHitTestInfo.h",
+ "TiledRegion.h",
+]
+
+if CONFIG["MOZ_X11"]:
+ EXPORTS.mozilla += ["X11Util.h"]
+ SOURCES += [
+ "X11Util.cpp",
+ ]
+
+UNIFIED_SOURCES += [
+ "DriverCrashGuard.cpp",
+ "FilterSupport.cpp",
+ "gfxCrashReporterUtils.cpp",
+ "gfxTelemetry.cpp",
+ "nsColor.cpp",
+ "nsFont.cpp",
+ "nsFontMetrics.cpp",
+ "nsRect.cpp",
+ "nsRegion.cpp",
+ "nsThebesFontEnumerator.cpp",
+ "nsTransform2D.cpp",
+ "TiledRegion.cpp",
+]
+
+# nsDeviceContext.cpp cannot be built in unified mode because it pulls in OS X system headers.
+SOURCES += [
+ "nsDeviceContext.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/ipc", # for ContentChild.h
+]
+
+FINAL_LIBRARY = "xul"
+
+CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"]
diff --git a/gfx/src/nsBoundingMetrics.h b/gfx/src/nsBoundingMetrics.h
new file mode 100644
index 0000000000..e937dc1870
--- /dev/null
+++ b/gfx/src/nsBoundingMetrics.h
@@ -0,0 +1,83 @@
+/* -*- 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/. */
+
+#ifndef __nsBoundingMetrics_h
+#define __nsBoundingMetrics_h
+
+#include "nsCoord.h"
+#include <algorithm>
+
+/* Struct used for accurate measurements of a string, in order to
+ * allow precise positioning when processing MathML. This is in its
+ * own header file because some very-widely-included headers need it
+ * but not the rest of nsFontMetrics, or vice versa.
+ */
+
+struct nsBoundingMetrics {
+ ///////////
+ // Metrics that _exactly_ enclose the text:
+
+ // The character coordinate system is the one used on X Windows:
+ // 1. The origin is located at the intersection of the baseline
+ // with the left of the character's cell.
+ // 2. All horizontal bearings are oriented from left to right.
+ // 2. All horizontal bearings are oriented from left to right.
+ // 3. The ascent is oriented from bottom to top (being 0 at the orgin).
+ // 4. The descent is oriented from top to bottom (being 0 at the origin).
+
+ // Note that Win32/Mac/PostScript use a different convention for
+ // the descent (all vertical measurements are oriented from bottom
+ // to top on these palatforms). Make sure to flip the sign of the
+ // descent on these platforms for cross-platform compatibility.
+
+ // Any of the following member variables listed here can have
+ // positive or negative value.
+
+ nscoord leftBearing;
+ /* The horizontal distance from the origin of the drawing
+ operation to the left-most part of the drawn string. */
+
+ nscoord rightBearing;
+ /* The horizontal distance from the origin of the drawing
+ operation to the right-most part of the drawn string.
+ The _exact_ width of the string is therefore:
+ rightBearing - leftBearing */
+
+ nscoord ascent;
+ /* The vertical distance from the origin of the drawing
+ operation to the top-most part of the drawn string. */
+
+ nscoord descent;
+ /* The vertical distance from the origin of the drawing
+ operation to the bottom-most part of the drawn string.
+ The _exact_ height of the string is therefore:
+ ascent + descent */
+
+ nscoord width;
+ /* The horizontal distance from the origin of the drawing
+ operation to the correct origin for drawing another string
+ to follow the current one. Depending on the font, this
+ could be greater than or less than the right bearing. */
+
+ nsBoundingMetrics()
+ : leftBearing(0), rightBearing(0), ascent(0), descent(0), width(0) {}
+
+ void operator+=(const nsBoundingMetrics& bm) {
+ if (ascent + descent == 0 && rightBearing - leftBearing == 0) {
+ ascent = bm.ascent;
+ descent = bm.descent;
+ leftBearing = width + bm.leftBearing;
+ rightBearing = width + bm.rightBearing;
+ } else {
+ if (ascent < bm.ascent) ascent = bm.ascent;
+ if (descent < bm.descent) descent = bm.descent;
+ leftBearing = std::min(leftBearing, width + bm.leftBearing);
+ rightBearing = std::max(rightBearing, width + bm.rightBearing);
+ }
+ width += bm.width;
+ }
+};
+
+#endif // __nsBoundingMetrics_h
diff --git a/gfx/src/nsColor.cpp b/gfx/src/nsColor.cpp
new file mode 100644
index 0000000000..841b187438
--- /dev/null
+++ b/gfx/src/nsColor.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h" // for ArrayLength
+#include "mozilla/mozalloc.h" // for operator delete, etc
+#include "mozilla/MathAlgorithms.h"
+
+#include "nsColor.h"
+#include <sys/types.h> // for int32_t
+#include "nsColorNames.h" // for nsColorNames
+#include "nsDebug.h" // for NS_ASSERTION, etc
+#include "nsStaticNameTable.h"
+#include "nsString.h" // for nsAutoCString, nsString, etc
+#include "nscore.h" // for nsAString, etc
+#include "prtypes.h" // for PR_BEGIN_MACRO, etc
+
+using namespace mozilla;
+
+// define an array of all color names
+#define GFX_COLOR(_name, _value) #_name,
+static const char* const kColorNames[] = {
+#include "nsColorNameList.h"
+};
+#undef GFX_COLOR
+
+// define an array of all color name values
+#define GFX_COLOR(_name, _value) _value,
+static const nscolor kColors[] = {
+#include "nsColorNameList.h"
+};
+#undef GFX_COLOR
+
+#define eColorName_COUNT (ArrayLength(kColorNames))
+#define eColorName_UNKNOWN (-1)
+
+static nsStaticCaseInsensitiveNameTable* gColorTable = nullptr;
+
+void nsColorNames::AddRefTable(void) {
+ NS_ASSERTION(!gColorTable, "pre existing array!");
+ if (!gColorTable) {
+ gColorTable =
+ new nsStaticCaseInsensitiveNameTable(kColorNames, eColorName_COUNT);
+ }
+}
+
+void nsColorNames::ReleaseTable(void) {
+ if (gColorTable) {
+ delete gColorTable;
+ gColorTable = nullptr;
+ }
+}
+
+static int ComponentValue(const char16_t* aColorSpec, int aLen, int color,
+ int dpc) {
+ int component = 0;
+ int index = (color * dpc);
+ if (2 < dpc) {
+ dpc = 2;
+ }
+ while (--dpc >= 0) {
+ char16_t ch = ((index < aLen) ? aColorSpec[index++] : '0');
+ if (('0' <= ch) && (ch <= '9')) {
+ component = (component * 16) + (ch - '0');
+ } else if ((('a' <= ch) && (ch <= 'f')) || (('A' <= ch) && (ch <= 'F'))) {
+ // "ch&7" handles lower and uppercase hex alphabetics
+ component = (component * 16) + (ch & 7) + 9;
+ } else { // not a hex digit, treat it like 0
+ component = (component * 16);
+ }
+ }
+ return component;
+}
+
+bool NS_HexToRGBA(const nsAString& aColorSpec, nsHexColorType aType,
+ nscolor* aResult) {
+ const char16_t* buffer = aColorSpec.BeginReading();
+
+ int nameLen = aColorSpec.Length();
+ bool hasAlpha = false;
+ if (nameLen != 3 && nameLen != 6) {
+ if ((nameLen != 4 && nameLen != 8) || aType == nsHexColorType::NoAlpha) {
+ // Improperly formatted color value
+ return false;
+ }
+ hasAlpha = true;
+ }
+
+ // Make sure the digits are legal
+ for (int i = 0; i < nameLen; i++) {
+ char16_t ch = buffer[i];
+ if (((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'f')) ||
+ ((ch >= 'A') && (ch <= 'F'))) {
+ // Legal character
+ continue;
+ }
+ // Whoops. Illegal character.
+ return false;
+ }
+
+ // Convert the ascii to binary
+ int dpc = ((nameLen <= 4) ? 1 : 2);
+ // Translate components from hex to binary
+ int r = ComponentValue(buffer, nameLen, 0, dpc);
+ int g = ComponentValue(buffer, nameLen, 1, dpc);
+ int b = ComponentValue(buffer, nameLen, 2, dpc);
+ int a;
+ if (hasAlpha) {
+ a = ComponentValue(buffer, nameLen, 3, dpc);
+ } else {
+ a = (dpc == 1) ? 0xf : 0xff;
+ }
+ if (dpc == 1) {
+ // Scale single digit component to an 8 bit value. Replicate the
+ // single digit to compute the new value.
+ r = (r << 4) | r;
+ g = (g << 4) | g;
+ b = (b << 4) | b;
+ a = (a << 4) | a;
+ }
+ NS_ASSERTION((r >= 0) && (r <= 255), "bad r");
+ NS_ASSERTION((g >= 0) && (g <= 255), "bad g");
+ NS_ASSERTION((b >= 0) && (b <= 255), "bad b");
+ NS_ASSERTION((a >= 0) && (a <= 255), "bad a");
+ *aResult = NS_RGBA(r, g, b, a);
+ return true;
+}
+
+// This implements part of the algorithm for legacy behavior described in
+// http://www.whatwg.org/specs/web-apps/current-work/complete/common-microsyntaxes.html#rules-for-parsing-a-legacy-color-value
+bool NS_LooseHexToRGB(const nsString& aColorSpec, nscolor* aResult) {
+ if (aColorSpec.EqualsLiteral("transparent")) {
+ return false;
+ }
+
+ int nameLen = aColorSpec.Length();
+ const char16_t* colorSpec = aColorSpec.get();
+ if (nameLen > 128) {
+ nameLen = 128;
+ }
+
+ if ('#' == colorSpec[0]) {
+ ++colorSpec;
+ --nameLen;
+ }
+
+ // digits per component
+ int dpc = (nameLen + 2) / 3;
+ int newdpc = dpc;
+
+ // Use only the rightmost 8 characters of each component.
+ if (newdpc > 8) {
+ nameLen -= newdpc - 8;
+ colorSpec += newdpc - 8;
+ newdpc = 8;
+ }
+
+ // And then keep trimming characters at the left until we'd trim one
+ // that would leave a nonzero value, but not past 2 characters per
+ // component.
+ while (newdpc > 2) {
+ bool haveNonzero = false;
+ for (int c = 0; c < 3; ++c) {
+ MOZ_ASSERT(c * dpc < nameLen,
+ "should not pass end of string while newdpc > 2");
+ char16_t ch = colorSpec[c * dpc];
+ if (('1' <= ch && ch <= '9') || ('A' <= ch && ch <= 'F') ||
+ ('a' <= ch && ch <= 'f')) {
+ haveNonzero = true;
+ break;
+ }
+ }
+ if (haveNonzero) {
+ break;
+ }
+ --newdpc;
+ --nameLen;
+ ++colorSpec;
+ }
+
+ // Translate components from hex to binary
+ int r = ComponentValue(colorSpec, nameLen, 0, dpc);
+ int g = ComponentValue(colorSpec, nameLen, 1, dpc);
+ int b = ComponentValue(colorSpec, nameLen, 2, dpc);
+ NS_ASSERTION((r >= 0) && (r <= 255), "bad r");
+ NS_ASSERTION((g >= 0) && (g <= 255), "bad g");
+ NS_ASSERTION((b >= 0) && (b <= 255), "bad b");
+
+ *aResult = NS_RGB(r, g, b);
+ return true;
+}
+
+bool NS_ColorNameToRGB(const nsAString& aColorName, nscolor* aResult) {
+ if (!gColorTable) return false;
+
+ int32_t id = gColorTable->Lookup(aColorName);
+ if (eColorName_UNKNOWN < id) {
+ NS_ASSERTION(uint32_t(id) < eColorName_COUNT,
+ "gColorTable->Lookup messed up");
+ if (aResult) {
+ *aResult = kColors[id];
+ }
+ return true;
+ }
+ return false;
+}
+
+// Fast approximate division by 255. It has the property that
+// for all 0 <= n <= 255*255, FAST_DIVIDE_BY_255(n) == n/255.
+// But it only uses two adds and two shifts instead of an
+// integer division (which is expensive on many processors).
+//
+// equivalent to target=v/255
+#define FAST_DIVIDE_BY_255(target, v) \
+ PR_BEGIN_MACRO \
+ unsigned tmp_ = v; \
+ target = ((tmp_ << 8) + tmp_ + 255) >> 16; \
+ PR_END_MACRO
+
+// Macro to blend two colors
+//
+// equivalent to target = (bg*(255-fgalpha) + fg*fgalpha)/255
+#define MOZ_BLEND(target, bg, fg, fgalpha) \
+ FAST_DIVIDE_BY_255(target, (bg) * (255 - fgalpha) + (fg) * (fgalpha))
+
+nscolor NS_ComposeColors(nscolor aBG, nscolor aFG) {
+ // This function uses colors that are non premultiplied alpha.
+ int r, g, b, a;
+
+ int bgAlpha = NS_GET_A(aBG);
+ int fgAlpha = NS_GET_A(aFG);
+
+ // Compute the final alpha of the blended color
+ // a = fgAlpha + bgAlpha*(255 - fgAlpha)/255;
+ FAST_DIVIDE_BY_255(a, bgAlpha * (255 - fgAlpha));
+ a = fgAlpha + a;
+ int blendAlpha;
+ if (a == 0) {
+ // In this case the blended color is totally trasparent,
+ // we preserve the color information of the foreground color.
+ blendAlpha = 255;
+ } else {
+ blendAlpha = (fgAlpha * 255) / a;
+ }
+ MOZ_BLEND(r, NS_GET_R(aBG), NS_GET_R(aFG), blendAlpha);
+ MOZ_BLEND(g, NS_GET_G(aBG), NS_GET_G(aFG), blendAlpha);
+ MOZ_BLEND(b, NS_GET_B(aBG), NS_GET_B(aFG), blendAlpha);
+
+ return NS_RGBA(r, g, b, a);
+}
+
+const char* NS_RGBToColorName(nscolor aColor) {
+ for (size_t idx = 0; idx < ArrayLength(kColors); ++idx) {
+ if (kColors[idx] == aColor) {
+ return kColorNames[idx];
+ }
+ }
+
+ return nullptr;
+}
diff --git a/gfx/src/nsColor.h b/gfx/src/nsColor.h
new file mode 100644
index 0000000000..e47f5a4575
--- /dev/null
+++ b/gfx/src/nsColor.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsColor_h___
+#define nsColor_h___
+
+#include <stdint.h> // for uint8_t, uint32_t
+#include "nsCoord.h" // for NSToIntRound
+#include "nsStringFwd.h"
+
+// A color is a 32 bit unsigned integer with four components: R, G, B
+// and A.
+typedef uint32_t nscolor;
+
+// Make a color out of r,g,b values. This assumes that the r,g,b values are
+// properly constrained to 0-255. This also assumes that a is 255.
+#define NS_RGB(_r, _g, _b) \
+ ((nscolor)((255 << 24) | ((_b) << 16) | ((_g) << 8) | (_r)))
+
+// Make a color out of r,g,b,a values. This assumes that the r,g,b,a
+// values are properly constrained to 0-255.
+#define NS_RGBA(_r, _g, _b, _a) \
+ ((nscolor)(((_a) << 24) | ((_b) << 16) | ((_g) << 8) | (_r)))
+
+// Extract color components from nscolor
+#define NS_GET_R(_rgba) ((uint8_t)((_rgba)&0xff))
+#define NS_GET_G(_rgba) ((uint8_t)(((_rgba) >> 8) & 0xff))
+#define NS_GET_B(_rgba) ((uint8_t)(((_rgba) >> 16) & 0xff))
+#define NS_GET_A(_rgba) ((uint8_t)(((_rgba) >> 24) & 0xff))
+
+namespace mozilla {
+
+template <typename T>
+inline uint8_t ClampColor(T aColor) {
+ if (aColor >= 255) {
+ return 255;
+ }
+ if (aColor <= 0) {
+ return 0;
+ }
+ return NSToIntRound(aColor);
+}
+
+} // namespace mozilla
+
+enum class nsHexColorType : uint8_t {
+ NoAlpha, // 3 or 6 digit hex colors only
+ AllowAlpha, // 3, 4, 6, or 8 digit hex colors
+};
+
+// Translate a hex string to a color. Return true if it parses ok,
+// otherwise return false.
+// This accepts the number of digits specified by aType.
+bool NS_HexToRGBA(const nsAString& aBuf, nsHexColorType aType,
+ nscolor* aResult);
+
+// Compose one NS_RGB color onto another. The result is what
+// you get if you draw aFG on top of aBG with operator OVER.
+nscolor NS_ComposeColors(nscolor aBG, nscolor aFG);
+
+namespace mozilla {
+
+inline uint32_t RoundingDivideBy255(uint32_t n) {
+ // There is an approximate alternative: ((n << 8) + n + 32896) >> 16
+ // But that is actually slower than this simple expression on a modern
+ // machine with a modern compiler.
+ return (n + 127) / 255;
+}
+
+} // namespace mozilla
+
+// Translate a hex string to a color. Return true if it parses ok,
+// otherwise return false.
+// This version accepts 1 to 9 digits (missing digits are 0)
+bool NS_LooseHexToRGB(const nsString& aBuf, nscolor* aResult);
+
+// There is no function to translate a color to a hex string, because
+// the hex-string syntax does not support transparency.
+
+// Translate a color name to a color. Return true if it parses ok,
+// otherwise return false.
+bool NS_ColorNameToRGB(const nsAString& aBuf, nscolor* aResult);
+
+// Return a color name for the given nscolor. If there is no color
+// name for it, returns null. If there are multiple possible color
+// names for the given color, the first one in nsColorNameList.h
+// (which is generally the first one in alphabetical order) will be
+// returned.
+const char* NS_RGBToColorName(nscolor aColor);
+
+#endif /* nsColor_h___ */
diff --git a/gfx/src/nsColorNameList.h b/gfx/src/nsColorNameList.h
new file mode 100644
index 0000000000..dee6dc79c7
--- /dev/null
+++ b/gfx/src/nsColorNameList.h
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/******
+
+ This file contains the list of all named colors
+ See nsCSSColorNames.h for access to the enum values for colors
+
+ It is designed to be used as inline input to nsCSSColorNames.cpp *only*
+ through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro GFX_COLOR which will have cruel
+ and unusual things done to it
+
+ It is recommended (but not strictly necessary) to keep all entries
+ in alphabetical order
+
+ The first argument to GFX_COLOR is both the enum identifier of the color
+ and the string value
+ The second argument is the sRGBA value for the named color
+
+ 'name' entries *must* use only lowercase characters.
+
+ ** Break these invarient and bad things will happen. **
+
+
+ ******/
+
+GFX_COLOR(aliceblue, NS_RGB(240, 248, 255))
+GFX_COLOR(antiquewhite, NS_RGB(250, 235, 215))
+GFX_COLOR(aqua, NS_RGB(0, 255, 255))
+GFX_COLOR(aquamarine, NS_RGB(127, 255, 212))
+GFX_COLOR(azure, NS_RGB(240, 255, 255))
+GFX_COLOR(beige, NS_RGB(245, 245, 220))
+GFX_COLOR(bisque, NS_RGB(255, 228, 196))
+GFX_COLOR(black, NS_RGB(0, 0, 0))
+GFX_COLOR(blanchedalmond, NS_RGB(255, 235, 205))
+GFX_COLOR(blue, NS_RGB(0, 0, 255))
+GFX_COLOR(blueviolet, NS_RGB(138, 43, 226))
+GFX_COLOR(brown, NS_RGB(165, 42, 42))
+GFX_COLOR(burlywood, NS_RGB(222, 184, 135))
+GFX_COLOR(cadetblue, NS_RGB(95, 158, 160))
+GFX_COLOR(chartreuse, NS_RGB(127, 255, 0))
+GFX_COLOR(chocolate, NS_RGB(210, 105, 30))
+GFX_COLOR(coral, NS_RGB(255, 127, 80))
+GFX_COLOR(cornflowerblue, NS_RGB(100, 149, 237))
+GFX_COLOR(cornsilk, NS_RGB(255, 248, 220))
+GFX_COLOR(crimson, NS_RGB(220, 20, 60))
+GFX_COLOR(cyan, NS_RGB(0, 255, 255))
+GFX_COLOR(darkblue, NS_RGB(0, 0, 139))
+GFX_COLOR(darkcyan, NS_RGB(0, 139, 139))
+GFX_COLOR(darkgoldenrod, NS_RGB(184, 134, 11))
+GFX_COLOR(darkgray, NS_RGB(169, 169, 169))
+GFX_COLOR(darkgreen, NS_RGB(0, 100, 0))
+GFX_COLOR(darkgrey, NS_RGB(169, 169, 169))
+GFX_COLOR(darkkhaki, NS_RGB(189, 183, 107))
+GFX_COLOR(darkmagenta, NS_RGB(139, 0, 139))
+GFX_COLOR(darkolivegreen, NS_RGB(85, 107, 47))
+GFX_COLOR(darkorange, NS_RGB(255, 140, 0))
+GFX_COLOR(darkorchid, NS_RGB(153, 50, 204))
+GFX_COLOR(darkred, NS_RGB(139, 0, 0))
+GFX_COLOR(darksalmon, NS_RGB(233, 150, 122))
+GFX_COLOR(darkseagreen, NS_RGB(143, 188, 143))
+GFX_COLOR(darkslateblue, NS_RGB(72, 61, 139))
+GFX_COLOR(darkslategray, NS_RGB(47, 79, 79))
+GFX_COLOR(darkslategrey, NS_RGB(47, 79, 79))
+GFX_COLOR(darkturquoise, NS_RGB(0, 206, 209))
+GFX_COLOR(darkviolet, NS_RGB(148, 0, 211))
+GFX_COLOR(deeppink, NS_RGB(255, 20, 147))
+GFX_COLOR(deepskyblue, NS_RGB(0, 191, 255))
+GFX_COLOR(dimgray, NS_RGB(105, 105, 105))
+GFX_COLOR(dimgrey, NS_RGB(105, 105, 105))
+GFX_COLOR(dodgerblue, NS_RGB(30, 144, 255))
+GFX_COLOR(firebrick, NS_RGB(178, 34, 34))
+GFX_COLOR(floralwhite, NS_RGB(255, 250, 240))
+GFX_COLOR(forestgreen, NS_RGB(34, 139, 34))
+GFX_COLOR(fuchsia, NS_RGB(255, 0, 255))
+GFX_COLOR(gainsboro, NS_RGB(220, 220, 220))
+GFX_COLOR(ghostwhite, NS_RGB(248, 248, 255))
+GFX_COLOR(gold, NS_RGB(255, 215, 0))
+GFX_COLOR(goldenrod, NS_RGB(218, 165, 32))
+GFX_COLOR(gray, NS_RGB(128, 128, 128))
+GFX_COLOR(grey, NS_RGB(128, 128, 128))
+GFX_COLOR(green, NS_RGB(0, 128, 0))
+GFX_COLOR(greenyellow, NS_RGB(173, 255, 47))
+GFX_COLOR(honeydew, NS_RGB(240, 255, 240))
+GFX_COLOR(hotpink, NS_RGB(255, 105, 180))
+GFX_COLOR(indianred, NS_RGB(205, 92, 92))
+GFX_COLOR(indigo, NS_RGB(75, 0, 130))
+GFX_COLOR(ivory, NS_RGB(255, 255, 240))
+GFX_COLOR(khaki, NS_RGB(240, 230, 140))
+GFX_COLOR(lavender, NS_RGB(230, 230, 250))
+GFX_COLOR(lavenderblush, NS_RGB(255, 240, 245))
+GFX_COLOR(lawngreen, NS_RGB(124, 252, 0))
+GFX_COLOR(lemonchiffon, NS_RGB(255, 250, 205))
+GFX_COLOR(lightblue, NS_RGB(173, 216, 230))
+GFX_COLOR(lightcoral, NS_RGB(240, 128, 128))
+GFX_COLOR(lightcyan, NS_RGB(224, 255, 255))
+GFX_COLOR(lightgoldenrodyellow, NS_RGB(250, 250, 210))
+GFX_COLOR(lightgray, NS_RGB(211, 211, 211))
+GFX_COLOR(lightgreen, NS_RGB(144, 238, 144))
+GFX_COLOR(lightgrey, NS_RGB(211, 211, 211))
+GFX_COLOR(lightpink, NS_RGB(255, 182, 193))
+GFX_COLOR(lightsalmon, NS_RGB(255, 160, 122))
+GFX_COLOR(lightseagreen, NS_RGB(32, 178, 170))
+GFX_COLOR(lightskyblue, NS_RGB(135, 206, 250))
+GFX_COLOR(lightslategray, NS_RGB(119, 136, 153))
+GFX_COLOR(lightslategrey, NS_RGB(119, 136, 153))
+GFX_COLOR(lightsteelblue, NS_RGB(176, 196, 222))
+GFX_COLOR(lightyellow, NS_RGB(255, 255, 224))
+GFX_COLOR(lime, NS_RGB(0, 255, 0))
+GFX_COLOR(limegreen, NS_RGB(50, 205, 50))
+GFX_COLOR(linen, NS_RGB(250, 240, 230))
+GFX_COLOR(magenta, NS_RGB(255, 0, 255))
+GFX_COLOR(maroon, NS_RGB(128, 0, 0))
+GFX_COLOR(mediumaquamarine, NS_RGB(102, 205, 170))
+GFX_COLOR(mediumblue, NS_RGB(0, 0, 205))
+GFX_COLOR(mediumorchid, NS_RGB(186, 85, 211))
+GFX_COLOR(mediumpurple, NS_RGB(147, 112, 219))
+GFX_COLOR(mediumseagreen, NS_RGB(60, 179, 113))
+GFX_COLOR(mediumslateblue, NS_RGB(123, 104, 238))
+GFX_COLOR(mediumspringgreen, NS_RGB(0, 250, 154))
+GFX_COLOR(mediumturquoise, NS_RGB(72, 209, 204))
+GFX_COLOR(mediumvioletred, NS_RGB(199, 21, 133))
+GFX_COLOR(midnightblue, NS_RGB(25, 25, 112))
+GFX_COLOR(mintcream, NS_RGB(245, 255, 250))
+GFX_COLOR(mistyrose, NS_RGB(255, 228, 225))
+GFX_COLOR(moccasin, NS_RGB(255, 228, 181))
+GFX_COLOR(navajowhite, NS_RGB(255, 222, 173))
+GFX_COLOR(navy, NS_RGB(0, 0, 128))
+GFX_COLOR(oldlace, NS_RGB(253, 245, 230))
+GFX_COLOR(olive, NS_RGB(128, 128, 0))
+GFX_COLOR(olivedrab, NS_RGB(107, 142, 35))
+GFX_COLOR(orange, NS_RGB(255, 165, 0))
+GFX_COLOR(orangered, NS_RGB(255, 69, 0))
+GFX_COLOR(orchid, NS_RGB(218, 112, 214))
+GFX_COLOR(palegoldenrod, NS_RGB(238, 232, 170))
+GFX_COLOR(palegreen, NS_RGB(152, 251, 152))
+GFX_COLOR(paleturquoise, NS_RGB(175, 238, 238))
+GFX_COLOR(palevioletred, NS_RGB(219, 112, 147))
+GFX_COLOR(papayawhip, NS_RGB(255, 239, 213))
+GFX_COLOR(peachpuff, NS_RGB(255, 218, 185))
+GFX_COLOR(peru, NS_RGB(205, 133, 63))
+GFX_COLOR(pink, NS_RGB(255, 192, 203))
+GFX_COLOR(plum, NS_RGB(221, 160, 221))
+GFX_COLOR(powderblue, NS_RGB(176, 224, 230))
+GFX_COLOR(purple, NS_RGB(128, 0, 128))
+GFX_COLOR(rebeccapurple, NS_RGB(102, 51, 153))
+GFX_COLOR(red, NS_RGB(255, 0, 0))
+GFX_COLOR(rosybrown, NS_RGB(188, 143, 143))
+GFX_COLOR(royalblue, NS_RGB(65, 105, 225))
+GFX_COLOR(saddlebrown, NS_RGB(139, 69, 19))
+GFX_COLOR(salmon, NS_RGB(250, 128, 114))
+GFX_COLOR(sandybrown, NS_RGB(244, 164, 96))
+GFX_COLOR(seagreen, NS_RGB(46, 139, 87))
+GFX_COLOR(seashell, NS_RGB(255, 245, 238))
+GFX_COLOR(sienna, NS_RGB(160, 82, 45))
+GFX_COLOR(silver, NS_RGB(192, 192, 192))
+GFX_COLOR(skyblue, NS_RGB(135, 206, 235))
+GFX_COLOR(slateblue, NS_RGB(106, 90, 205))
+GFX_COLOR(slategray, NS_RGB(112, 128, 144))
+GFX_COLOR(slategrey, NS_RGB(112, 128, 144))
+GFX_COLOR(snow, NS_RGB(255, 250, 250))
+GFX_COLOR(springgreen, NS_RGB(0, 255, 127))
+GFX_COLOR(steelblue, NS_RGB(70, 130, 180))
+GFX_COLOR(tan, NS_RGB(210, 180, 140))
+GFX_COLOR(teal, NS_RGB(0, 128, 128))
+GFX_COLOR(thistle, NS_RGB(216, 191, 216))
+GFX_COLOR(tomato, NS_RGB(255, 99, 71))
+GFX_COLOR(transparent, NS_RGBA(0, 0, 0, 0))
+GFX_COLOR(turquoise, NS_RGB(64, 224, 208))
+GFX_COLOR(violet, NS_RGB(238, 130, 238))
+GFX_COLOR(wheat, NS_RGB(245, 222, 179))
+GFX_COLOR(white, NS_RGB(255, 255, 255))
+GFX_COLOR(whitesmoke, NS_RGB(245, 245, 245))
+GFX_COLOR(yellow, NS_RGB(255, 255, 0))
+GFX_COLOR(yellowgreen, NS_RGB(154, 205, 50))
diff --git a/gfx/src/nsColorNames.h b/gfx/src/nsColorNames.h
new file mode 100644
index 0000000000..d30ec751aa
--- /dev/null
+++ b/gfx/src/nsColorNames.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsColorNames_h___
+#define nsColorNames_h___
+
+class nsColorNames {
+ public:
+ static void AddRefTable(void);
+ static void ReleaseTable(void);
+};
+
+#endif /* nsColorNames_h___ */
diff --git a/gfx/src/nsCoord.h b/gfx/src/nsCoord.h
new file mode 100644
index 0000000000..fe90127411
--- /dev/null
+++ b/gfx/src/nsCoord.h
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef NSCOORD_H
+#define NSCOORD_H
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdlib>
+#include <math.h>
+
+#include "mozilla/Assertions.h"
+#include "nsMathUtils.h"
+
+/*
+ * Basic type used for the geometry classes.
+ *
+ * Normally all coordinates are maintained in an app unit coordinate
+ * space. An app unit is 1/60th of a CSS device pixel, which is, in turn
+ * an integer number of device pixels, such at the CSS DPI is as close to
+ * 96dpi as possible.
+ */
+
+// This controls whether we're using integers or floats for coordinates. We
+// want to eventually use floats.
+//#define NS_COORD_IS_FLOAT
+
+#ifdef NS_COORD_IS_FLOAT
+typedef float nscoord;
+# define nscoord_MAX (mozilla::PositiveInfinity<float>())
+#else
+typedef int32_t nscoord;
+# define nscoord_MAX nscoord((1 << 30) - 1)
+#endif
+
+#define nscoord_MIN (-nscoord_MAX)
+
+inline void VERIFY_COORD(nscoord aCoord) {
+#ifdef NS_COORD_IS_FLOAT
+ NS_ASSERTION(floorf(aCoord) == aCoord, "Coords cannot have fractions");
+#endif
+}
+
+/**
+ * Divide aSpace by aN. Assign the resulting quotient to aQuotient and
+ * return the remainder.
+ */
+inline nscoord NSCoordDivRem(nscoord aSpace, size_t aN, nscoord* aQuotient) {
+#ifdef NS_COORD_IS_FLOAT
+ *aQuotient = aSpace / aN;
+ return 0.0f;
+#else
+ div_t result = div(aSpace, aN);
+ *aQuotient = nscoord(result.quot);
+ return nscoord(result.rem);
+#endif
+}
+
+inline nscoord NSCoordMulDiv(nscoord aMult1, nscoord aMult2, nscoord aDiv) {
+#ifdef NS_COORD_IS_FLOAT
+ return (aMult1 * aMult2 / aDiv);
+#else
+ return (int64_t(aMult1) * int64_t(aMult2) / int64_t(aDiv));
+#endif
+}
+
+inline nscoord NSToCoordRound(float aValue) {
+#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \
+ !defined(__clang__)
+ return NS_lroundup30(aValue);
+#else
+ return nscoord(floorf(aValue + 0.5f));
+#endif /* XP_WIN && _M_IX86 && !__GNUC__ */
+}
+
+inline nscoord NSToCoordRound(double aValue) {
+#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \
+ !defined(__clang__)
+ return NS_lroundup30((float)aValue);
+#else
+ return nscoord(floor(aValue + 0.5f));
+#endif /* XP_WIN && _M_IX86 && !__GNUC__ */
+}
+
+inline nscoord NSToCoordRoundWithClamp(float aValue) {
+#ifndef NS_COORD_IS_FLOAT
+ // Bounds-check before converting out of float, to avoid overflow
+ if (aValue >= float(nscoord_MAX)) {
+ return nscoord_MAX;
+ }
+ if (aValue <= float(nscoord_MIN)) {
+ return nscoord_MIN;
+ }
+#endif
+ return NSToCoordRound(aValue);
+}
+
+/**
+ * Returns aCoord * aScale, capping the product to nscoord_MAX or nscoord_MIN as
+ * appropriate for the signs of aCoord and aScale. If requireNotNegative is
+ * true, this method will enforce that aScale is not negative; use that
+ * parametrization to get a check of that fact in debug builds.
+ */
+inline nscoord _nscoordSaturatingMultiply(nscoord aCoord, float aScale,
+ bool requireNotNegative) {
+ VERIFY_COORD(aCoord);
+ if (requireNotNegative) {
+ MOZ_ASSERT(aScale >= 0.0f,
+ "negative scaling factors must be handled manually");
+ }
+#ifdef NS_COORD_IS_FLOAT
+ return floorf(aCoord * aScale);
+#else
+ float product = aCoord * aScale;
+ if (requireNotNegative ? aCoord > 0 : (aCoord > 0) == (aScale > 0))
+ return NSToCoordRoundWithClamp(
+ std::min<float>((float)nscoord_MAX, product));
+ return NSToCoordRoundWithClamp(std::max<float>((float)nscoord_MIN, product));
+#endif
+}
+
+/**
+ * Returns aCoord * aScale, capping the product to nscoord_MAX or nscoord_MIN as
+ * appropriate for the sign of aCoord. This method requires aScale to not be
+ * negative; use this method when you know that aScale should never be
+ * negative to get a sanity check of that invariant in debug builds.
+ */
+inline nscoord NSCoordSaturatingNonnegativeMultiply(nscoord aCoord,
+ float aScale) {
+ return _nscoordSaturatingMultiply(aCoord, aScale, true);
+}
+
+/**
+ * Returns aCoord * aScale, capping the product to nscoord_MAX or nscoord_MIN as
+ * appropriate for the signs of aCoord and aScale.
+ */
+inline nscoord NSCoordSaturatingMultiply(nscoord aCoord, float aScale) {
+ return _nscoordSaturatingMultiply(aCoord, aScale, false);
+}
+
+/**
+ * Returns a + b, capping the sum to nscoord_MAX.
+ *
+ * This function assumes that neither argument is nscoord_MIN.
+ *
+ * Note: If/when we start using floats for nscoords, this function won't be as
+ * necessary. Normal float addition correctly handles adding with infinity,
+ * assuming we aren't adding nscoord_MIN. (-infinity)
+ */
+inline nscoord NSCoordSaturatingAdd(nscoord a, nscoord b) {
+ VERIFY_COORD(a);
+ VERIFY_COORD(b);
+
+#ifdef NS_COORD_IS_FLOAT
+ // Float math correctly handles a+b, given that neither is -infinity.
+ return a + b;
+#else
+ if (a == nscoord_MAX || b == nscoord_MAX) {
+ // infinity + anything = anything + infinity = infinity
+ return nscoord_MAX;
+ } else {
+ // a + b = a + b
+ // Cap the result, just in case we're dealing with numbers near nscoord_MAX
+ return std::min(nscoord_MAX, a + b);
+ }
+#endif
+}
+
+/**
+ * Returns a - b, gracefully handling cases involving nscoord_MAX.
+ * This function assumes that neither argument is nscoord_MIN.
+ *
+ * The behavior is as follows:
+ *
+ * a) infinity - infinity -> infMinusInfResult
+ * b) N - infinity -> 0 (unexpected -- triggers NOTREACHED)
+ * c) infinity - N -> infinity
+ * d) N1 - N2 -> N1 - N2
+ *
+ * Note: For float nscoords, cases (c) and (d) are handled by normal float
+ * math. We still need to explicitly specify the behavior for cases (a)
+ * and (b), though. (Under normal float math, those cases would return NaN
+ * and -infinity, respectively.)
+ */
+inline nscoord NSCoordSaturatingSubtract(nscoord a, nscoord b,
+ nscoord infMinusInfResult) {
+ VERIFY_COORD(a);
+ VERIFY_COORD(b);
+
+ if (b == nscoord_MAX) {
+ if (a == nscoord_MAX) {
+ // case (a)
+ return infMinusInfResult;
+ } else {
+ // case (b)
+ MOZ_ASSERT_UNREACHABLE("Attempted to subtract [n - nscoord_MAX]");
+ return 0;
+ }
+ } else {
+#ifdef NS_COORD_IS_FLOAT
+ // case (c) and (d) for floats. (float math handles both)
+ return a - b;
+#else
+ if (a == nscoord_MAX) {
+ // case (c) for integers
+ return nscoord_MAX;
+ } else {
+ // case (d) for integers
+ // Cap the result, in case we're dealing with numbers near nscoord_MAX
+ return std::min(nscoord_MAX, a - b);
+ }
+#endif
+ }
+}
+
+inline float NSCoordToFloat(nscoord aCoord) {
+ VERIFY_COORD(aCoord);
+#ifdef NS_COORD_IS_FLOAT
+ NS_ASSERTION(!mozilla::IsNaN(aCoord), "NaN encountered in float conversion");
+#endif
+ return (float)aCoord;
+}
+
+/*
+ * Coord Rounding Functions
+ */
+inline nscoord NSToCoordFloor(float aValue) { return nscoord(floorf(aValue)); }
+
+inline nscoord NSToCoordFloor(double aValue) { return nscoord(floor(aValue)); }
+
+inline nscoord NSToCoordFloorClamped(float aValue) {
+#ifndef NS_COORD_IS_FLOAT
+ // Bounds-check before converting out of float, to avoid overflow
+ if (aValue >= float(nscoord_MAX)) {
+ return nscoord_MAX;
+ }
+ if (aValue <= float(nscoord_MIN)) {
+ return nscoord_MIN;
+ }
+#endif
+ return NSToCoordFloor(aValue);
+}
+
+inline nscoord NSToCoordCeil(float aValue) { return nscoord(ceilf(aValue)); }
+
+inline nscoord NSToCoordCeil(double aValue) { return nscoord(ceil(aValue)); }
+
+inline nscoord NSToCoordCeilClamped(double aValue) {
+#ifndef NS_COORD_IS_FLOAT
+ // Bounds-check before converting out of double, to avoid overflow
+ if (aValue >= nscoord_MAX) {
+ return nscoord_MAX;
+ }
+ if (aValue <= nscoord_MIN) {
+ return nscoord_MIN;
+ }
+#endif
+ return NSToCoordCeil(aValue);
+}
+
+// The NSToCoordTrunc* functions remove the fractional component of
+// aValue, and are thus equivalent to NSToCoordFloor* for positive
+// values and NSToCoordCeil* for negative values.
+
+inline nscoord NSToCoordTrunc(float aValue) {
+ // There's no need to use truncf() since it matches the default
+ // rules for float to integer conversion.
+ return nscoord(aValue);
+}
+
+inline nscoord NSToCoordTrunc(double aValue) {
+ // There's no need to use trunc() since it matches the default
+ // rules for float to integer conversion.
+ return nscoord(aValue);
+}
+
+inline nscoord NSToCoordTruncClamped(float aValue) {
+#ifndef NS_COORD_IS_FLOAT
+ // Bounds-check before converting out of float, to avoid overflow
+ if (aValue >= float(nscoord_MAX)) {
+ return nscoord_MAX;
+ }
+ if (aValue <= float(nscoord_MIN)) {
+ return nscoord_MIN;
+ }
+#endif
+ return NSToCoordTrunc(aValue);
+}
+
+inline nscoord NSToCoordTruncClamped(double aValue) {
+#ifndef NS_COORD_IS_FLOAT
+ // Bounds-check before converting out of double, to avoid overflow
+ if (aValue >= float(nscoord_MAX)) {
+ return nscoord_MAX;
+ }
+ if (aValue <= float(nscoord_MIN)) {
+ return nscoord_MIN;
+ }
+#endif
+ return NSToCoordTrunc(aValue);
+}
+
+/*
+ * Int Rounding Functions
+ */
+inline int32_t NSToIntFloor(float aValue) { return int32_t(floorf(aValue)); }
+
+inline int32_t NSToIntCeil(float aValue) { return int32_t(ceilf(aValue)); }
+
+inline int32_t NSToIntRound(float aValue) { return NS_lroundf(aValue); }
+
+inline int32_t NSToIntRound(double aValue) { return NS_lround(aValue); }
+
+inline int32_t NSToIntRoundUp(double aValue) {
+ return int32_t(floor(aValue + 0.5));
+}
+
+/*
+ * App Unit/Pixel conversions
+ */
+inline nscoord NSFloatPixelsToAppUnits(float aPixels, float aAppUnitsPerPixel) {
+ return NSToCoordRoundWithClamp(aPixels * aAppUnitsPerPixel);
+}
+
+inline nscoord NSIntPixelsToAppUnits(int32_t aPixels,
+ int32_t aAppUnitsPerPixel) {
+ // The cast to nscoord makes sure we don't overflow if we ever change
+ // nscoord to float
+ nscoord r = aPixels * (nscoord)aAppUnitsPerPixel;
+ VERIFY_COORD(r);
+ return r;
+}
+
+inline float NSAppUnitsToFloatPixels(nscoord aAppUnits,
+ float aAppUnitsPerPixel) {
+ return (float(aAppUnits) / aAppUnitsPerPixel);
+}
+
+inline double NSAppUnitsToDoublePixels(nscoord aAppUnits,
+ double aAppUnitsPerPixel) {
+ return (double(aAppUnits) / aAppUnitsPerPixel);
+}
+
+inline int32_t NSAppUnitsToIntPixels(nscoord aAppUnits,
+ float aAppUnitsPerPixel) {
+ return NSToIntRound(float(aAppUnits) / aAppUnitsPerPixel);
+}
+
+inline float NSCoordScale(nscoord aCoord, int32_t aFromAPP, int32_t aToAPP) {
+ return (NSCoordToFloat(aCoord) * aToAPP) / aFromAPP;
+}
+
+/// handy constants
+#define TWIPS_PER_POINT_INT 20
+#define TWIPS_PER_POINT_FLOAT 20.0f
+#define POINTS_PER_INCH_INT 72
+#define POINTS_PER_INCH_FLOAT 72.0f
+#define CM_PER_INCH_FLOAT 2.54f
+#define MM_PER_INCH_FLOAT 25.4f
+
+/*
+ * Twips/unit conversions
+ */
+inline float NSUnitsToTwips(float aValue, float aPointsPerUnit) {
+ return aValue * aPointsPerUnit * TWIPS_PER_POINT_FLOAT;
+}
+
+inline float NSTwipsToUnits(float aTwips, float aUnitsPerPoint) {
+ return (aTwips * (aUnitsPerPoint / TWIPS_PER_POINT_FLOAT));
+}
+
+/// Unit conversion macros
+//@{
+#define NS_POINTS_TO_TWIPS(x) NSUnitsToTwips((x), 1.0f)
+#define NS_INCHES_TO_TWIPS(x) \
+ NSUnitsToTwips((x), POINTS_PER_INCH_FLOAT) // 72 points per inch
+
+#define NS_MILLIMETERS_TO_TWIPS(x) \
+ NSUnitsToTwips((x), (POINTS_PER_INCH_FLOAT * 0.03937f))
+
+#define NS_POINTS_TO_INT_TWIPS(x) NSToIntRound(NS_POINTS_TO_TWIPS(x))
+#define NS_INCHES_TO_INT_TWIPS(x) NSToIntRound(NS_INCHES_TO_TWIPS(x))
+
+#define NS_TWIPS_TO_INCHES(x) NSTwipsToUnits((x), 1.0f / POINTS_PER_INCH_FLOAT)
+
+#define NS_TWIPS_TO_MILLIMETERS(x) \
+ NSTwipsToUnits((x), 1.0f / (POINTS_PER_INCH_FLOAT * 0.03937f))
+//@}
+
+#endif /* NSCOORD_H */
diff --git a/gfx/src/nsDeviceContext.cpp b/gfx/src/nsDeviceContext.cpp
new file mode 100644
index 0000000000..daa592e1bf
--- /dev/null
+++ b/gfx/src/nsDeviceContext.cpp
@@ -0,0 +1,717 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=4 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceContext.h"
+#include <algorithm> // for max
+#include "gfxASurface.h" // for gfxASurface, etc
+#include "gfxContext.h"
+#include "gfxImageSurface.h" // for gfxImageSurface
+#include "gfxPoint.h" // for gfxSize
+#include "gfxTextRun.h" // for gfxFontGroup
+#include "mozilla/Attributes.h" // for final
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/PrintTarget.h"
+#include "mozilla/Preferences.h" // for Preferences
+#include "mozilla/Services.h" // for GetObserverService
+#include "mozilla/mozalloc.h" // for operator new
+#include "nsCRT.h" // for nsCRT
+#include "nsDebug.h" // for NS_ASSERTION, etc
+#include "nsFont.h" // for nsFont
+#include "nsFontMetrics.h" // for nsFontMetrics
+#include "nsAtom.h" // for nsAtom, NS_Atomize
+#include "nsID.h"
+#include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec
+#include "nsLanguageAtomService.h" // for nsLanguageAtomService
+#include "nsIObserver.h" // for nsIObserver, etc
+#include "nsIObserverService.h" // for nsIObserverService
+#include "nsIScreen.h" // for nsIScreen
+#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
+#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
+#include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW
+#include "nsRect.h" // for nsRect
+#include "nsServiceManagerUtils.h" // for do_GetService
+#include "nsString.h" // for nsDependentString
+#include "nsTArray.h" // for nsTArray, nsTArray_Impl
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/widget/ScreenManager.h" // for ScreenManager
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::services::GetObserverService;
+using mozilla::widget::ScreenManager;
+
+class nsFontCache final : public nsIObserver {
+ public:
+ nsFontCache() : mContext(nullptr) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ void Init(nsDeviceContext* aContext);
+ void Destroy();
+
+ already_AddRefed<nsFontMetrics> GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams);
+
+ void FontMetricsDeleted(const nsFontMetrics* aFontMetrics);
+ void Compact();
+
+ // Flush aFlushCount oldest entries, or all if aFlushCount is negative
+ void Flush(int32_t aFlushCount = -1);
+
+ void UpdateUserFonts(gfxUserFontSet* aUserFontSet);
+
+ protected:
+ // If the array of cached entries is about to exceed this threshold,
+ // we'll discard the oldest ones so as to keep the size reasonable.
+ // In practice, the great majority of cache hits are among the last
+ // few entries; keeping thousands of older entries becomes counter-
+ // productive because it can then take too long to scan the cache.
+ static const int32_t kMaxCacheEntries = 128;
+
+ ~nsFontCache() = default;
+
+ nsDeviceContext* mContext; // owner
+ RefPtr<nsAtom> mLocaleLanguage;
+
+ // We may not flush older entries immediately the array reaches
+ // kMaxCacheEntries length, because this usually happens on a stylo
+ // thread where we can't safely delete metrics objects. So we allocate an
+ // oversized autoarray buffer here, so that we're unlikely to overflow
+ // it and need separate heap allocation before the flush happens on the
+ // main thread.
+ AutoTArray<nsFontMetrics*, kMaxCacheEntries * 2> mFontMetrics;
+
+ bool mFlushPending = false;
+
+ class FlushFontMetricsTask : public mozilla::Runnable {
+ public:
+ explicit FlushFontMetricsTask(nsFontCache* aCache)
+ : mozilla::Runnable("FlushFontMetricsTask"), mCache(aCache) {}
+ NS_IMETHOD Run() override {
+ // Partially flush the cache, leaving the kMaxCacheEntries/2 most
+ // recent entries.
+ mCache->Flush(mCache->mFontMetrics.Length() - kMaxCacheEntries / 2);
+ mCache->mFlushPending = false;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsFontCache> mCache;
+ };
+};
+
+NS_IMPL_ISUPPORTS(nsFontCache, nsIObserver)
+
+// The Init and Destroy methods are necessary because it's not
+// safe to call AddObserver from a constructor or RemoveObserver
+// from a destructor. That should be fixed.
+void nsFontCache::Init(nsDeviceContext* aContext) {
+ mContext = aContext;
+ // register as a memory-pressure observer to free font resources
+ // in low-memory situations.
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) obs->AddObserver(this, "memory-pressure", false);
+
+ mLocaleLanguage = nsLanguageAtomService::GetService()->GetLocaleLanguage();
+ if (!mLocaleLanguage) {
+ mLocaleLanguage = NS_Atomize("x-western");
+ }
+}
+
+void nsFontCache::Destroy() {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) obs->RemoveObserver(this, "memory-pressure");
+ Flush();
+}
+
+NS_IMETHODIMP
+nsFontCache::Observe(nsISupports*, const char* aTopic, const char16_t*) {
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) Compact();
+ return NS_OK;
+}
+
+already_AddRefed<nsFontMetrics> nsFontCache::GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams) {
+ nsAtom* language = aParams.language && !aParams.language->IsEmpty()
+ ? aParams.language
+ : mLocaleLanguage.get();
+
+ // First check our cache
+ // start from the end, which is where we put the most-recent-used element
+ const int32_t n = mFontMetrics.Length() - 1;
+ for (int32_t i = n; i >= 0; --i) {
+ nsFontMetrics* fm = mFontMetrics[i];
+ if (fm->Font().Equals(aFont) &&
+ fm->GetUserFontSet() == aParams.userFontSet &&
+ fm->Language() == language &&
+ fm->Orientation() == aParams.orientation &&
+ fm->ExplicitLanguage() == aParams.explicitLanguage) {
+ if (i != n) {
+ // promote it to the end of the cache
+ mFontMetrics.RemoveElementAt(i);
+ mFontMetrics.AppendElement(fm);
+ }
+ fm->GetThebesFontGroup()->UpdateUserFonts();
+ return do_AddRef(fm);
+ }
+ }
+
+ // It's not in the cache. Get font metrics and then cache them.
+ // If the cache has reached its size limit, drop the older half of the
+ // entries; but if we're on a stylo thread (the usual case), we have
+ // to post a task back to the main thread to do the flush.
+ if (n >= kMaxCacheEntries - 1 && !mFlushPending) {
+ if (NS_IsMainThread()) {
+ Flush(mFontMetrics.Length() - kMaxCacheEntries / 2);
+ } else {
+ mFlushPending = true;
+ nsCOMPtr<nsIRunnable> flushTask = new FlushFontMetricsTask(this);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(flushTask));
+ }
+ }
+
+ nsFontMetrics::Params params = aParams;
+ params.language = language;
+ RefPtr<nsFontMetrics> fm = new nsFontMetrics(aFont, params, mContext);
+ // the mFontMetrics list has the "head" at the end, because append
+ // is cheaper than insert
+ mFontMetrics.AppendElement(do_AddRef(fm).take());
+ return fm.forget();
+}
+
+void nsFontCache::UpdateUserFonts(gfxUserFontSet* aUserFontSet) {
+ for (nsFontMetrics* fm : mFontMetrics) {
+ gfxFontGroup* fg = fm->GetThebesFontGroup();
+ if (fg->GetUserFontSet() == aUserFontSet) {
+ fg->UpdateUserFonts();
+ }
+ }
+}
+
+void nsFontCache::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) {
+ mFontMetrics.RemoveElement(aFontMetrics);
+}
+
+void nsFontCache::Compact() {
+ // Need to loop backward because the running element can be removed on
+ // the way
+ for (int32_t i = mFontMetrics.Length() - 1; i >= 0; --i) {
+ nsFontMetrics* fm = mFontMetrics[i];
+ nsFontMetrics* oldfm = fm;
+ // Destroy() isn't here because we want our device context to be
+ // notified
+ NS_RELEASE(fm); // this will reset fm to nullptr
+ // if the font is really gone, it would have called back in
+ // FontMetricsDeleted() and would have removed itself
+ if (mFontMetrics.IndexOf(oldfm) != mFontMetrics.NoIndex) {
+ // nope, the font is still there, so let's hold onto it too
+ NS_ADDREF(oldfm);
+ }
+ }
+}
+
+// Flush the aFlushCount oldest entries, or all if (aFlushCount < 0)
+void nsFontCache::Flush(int32_t aFlushCount) {
+ int32_t n = aFlushCount < 0
+ ? mFontMetrics.Length()
+ : std::min<int32_t>(aFlushCount, mFontMetrics.Length());
+ for (int32_t i = n - 1; i >= 0; --i) {
+ nsFontMetrics* fm = mFontMetrics[i];
+ // Destroy() will unhook our device context from the fm so that we
+ // won't waste time in triggering the notification of
+ // FontMetricsDeleted() in the subsequent release
+ fm->Destroy();
+ NS_RELEASE(fm);
+ }
+ mFontMetrics.RemoveElementsAt(0, n);
+}
+
+nsDeviceContext::nsDeviceContext()
+ : mWidth(0),
+ mHeight(0),
+ mAppUnitsPerDevPixel(-1),
+ mAppUnitsPerDevPixelAtUnitFullZoom(-1),
+ mAppUnitsPerPhysicalInch(-1),
+ mFullZoom(1.0f),
+ mPrintingScale(1.0f),
+ mPrintingTranslate(gfxPoint(0, 0)),
+ mIsCurrentlyPrintingDoc(false)
+#ifdef DEBUG
+ ,
+ mIsInitialized(false)
+#endif
+{
+ MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
+}
+
+nsDeviceContext::~nsDeviceContext() {
+ if (mFontCache) {
+ mFontCache->Destroy();
+ }
+}
+
+void nsDeviceContext::InitFontCache() {
+ if (!mFontCache) {
+ mFontCache = new nsFontCache();
+ mFontCache->Init(this);
+ }
+}
+
+void nsDeviceContext::UpdateFontCacheUserFonts(gfxUserFontSet* aUserFontSet) {
+ if (mFontCache) {
+ mFontCache->UpdateUserFonts(aUserFontSet);
+ }
+}
+
+already_AddRefed<nsFontMetrics> nsDeviceContext::GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams) {
+ InitFontCache();
+ return mFontCache->GetMetricsFor(aFont, aParams);
+}
+
+nsresult nsDeviceContext::FlushFontCache(void) {
+ if (mFontCache) mFontCache->Flush();
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::FontMetricsDeleted(
+ const nsFontMetrics* aFontMetrics) {
+ if (mFontCache) {
+ mFontCache->FontMetricsDeleted(aFontMetrics);
+ }
+ return NS_OK;
+}
+
+bool nsDeviceContext::IsPrinterContext() { return mPrintTarget != nullptr; }
+
+void nsDeviceContext::SetDPI(double* aScale) {
+ float dpi;
+
+ // Use the printing DC to determine DPI values, if we have one.
+ if (mDeviceContextSpec) {
+ dpi = mDeviceContextSpec->GetDPI();
+ mPrintingScale = mDeviceContextSpec->GetPrintingScale();
+ mPrintingTranslate = mDeviceContextSpec->GetPrintingTranslate();
+ mAppUnitsPerDevPixelAtUnitFullZoom =
+ NS_lround((AppUnitsPerCSSPixel() * 96) / dpi);
+ } else {
+ nsCOMPtr<nsIScreen> primaryScreen;
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.GetPrimaryScreen(getter_AddRefs(primaryScreen));
+ MOZ_ASSERT(primaryScreen);
+
+ // A value of -1 means use the maximum of 96 and the system DPI.
+ // A value of 0 means use the system DPI. A positive value is used as the
+ // DPI. This sets the physical size of a device pixel and thus controls the
+ // interpretation of physical units.
+ int32_t prefDPI = Preferences::GetInt("layout.css.dpi", -1);
+
+ if (prefDPI > 0) {
+ dpi = prefDPI;
+ } else if (mWidget) {
+ // PuppetWidget could return -1 if the value's not available yet.
+ dpi = mWidget->GetDPI();
+ // In case that the widget returns -1, use the primary screen's
+ // value as default.
+ if (dpi < 0) {
+ primaryScreen->GetDpi(&dpi);
+ }
+ if (prefDPI < 0) {
+ dpi = std::max(96.0f, dpi);
+ }
+ } else {
+ dpi = 96.0f;
+ }
+
+ double devPixelsPerCSSPixel;
+ if (aScale && *aScale > 0.0) {
+ // if caller provided a scale, we just use it
+ devPixelsPerCSSPixel = *aScale;
+ } else {
+ // otherwise get from the widget, and return it in aScale for
+ // the caller to pass to child contexts if needed
+ CSSToLayoutDeviceScale scale =
+ mWidget ? mWidget->GetDefaultScale() : CSSToLayoutDeviceScale(1.0);
+ devPixelsPerCSSPixel = scale.scale;
+ // In case that the widget returns -1, use the primary screen's
+ // value as default.
+ if (devPixelsPerCSSPixel < 0) {
+ primaryScreen->GetDefaultCSSScaleFactor(&devPixelsPerCSSPixel);
+ }
+ if (aScale) {
+ *aScale = devPixelsPerCSSPixel;
+ }
+ }
+
+ mAppUnitsPerDevPixelAtUnitFullZoom =
+ std::max(1, NS_lround(AppUnitsPerCSSPixel() / devPixelsPerCSSPixel));
+ }
+
+ NS_ASSERTION(dpi != -1.0, "no dpi set");
+
+ mAppUnitsPerPhysicalInch =
+ NS_lround(dpi * mAppUnitsPerDevPixelAtUnitFullZoom);
+ UpdateAppUnitsForFullZoom();
+}
+
+nsresult nsDeviceContext::Init(nsIWidget* aWidget) {
+#ifdef DEBUG
+ // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument
+ // re-initializes nsDeviceContext objects. We can only assert in
+ // InitForPrinting (below).
+ mIsInitialized = true;
+#endif
+
+ nsresult rv = NS_OK;
+ if (mScreenManager && mWidget == aWidget) return rv;
+
+ mWidget = aWidget;
+ SetDPI();
+
+ if (mScreenManager) return rv;
+
+ mScreenManager = do_GetService("@mozilla.org/gfx/screenmanager;1", &rv);
+
+ return rv;
+}
+
+// XXX This is only for printing. We should make that obvious in the name.
+already_AddRefed<gfxContext> nsDeviceContext::CreateRenderingContext() {
+ return CreateRenderingContextCommon(/* not a reference context */ false);
+}
+
+already_AddRefed<gfxContext>
+nsDeviceContext::CreateReferenceRenderingContext() {
+ return CreateRenderingContextCommon(/* a reference context */ true);
+}
+
+already_AddRefed<gfxContext> nsDeviceContext::CreateRenderingContextCommon(
+ bool aWantReferenceContext) {
+ MOZ_ASSERT(IsPrinterContext());
+ MOZ_ASSERT(mWidth > 0 && mHeight > 0);
+
+ RefPtr<gfx::DrawTarget> dt;
+ if (aWantReferenceContext) {
+ dt = mPrintTarget->GetReferenceDrawTarget();
+ } else {
+ // This will be null if e10s is disabled or print.print_via_parent=false.
+ RefPtr<DrawEventRecorder> recorder;
+ mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder));
+ dt = mPrintTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder);
+ }
+
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote << "Failed to create draw target in device context sized "
+ << mWidth << "x" << mHeight << " and pointer "
+ << hexa(mPrintTarget);
+ return nullptr;
+ }
+
+#ifdef XP_MACOSX
+ // The CGContextRef provided by PMSessionGetCGGraphicsContext is
+ // write-only, so we need to prevent gfxContext::PushGroupAndCopyBackground
+ // trying to read from it or else we'll crash.
+ // XXXjwatt Consider adding a MakeDrawTarget override to PrintTargetCG and
+ // moving this AddUserData call there.
+ dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
+#endif
+ dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
+
+ RefPtr<gfxContext> pContext = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(pContext); // already checked draw target above
+
+ gfxMatrix transform;
+ transform.PreTranslate(mPrintingTranslate);
+ if (mPrintTarget->RotateNeededForLandscape()) {
+ // Rotate page 90 degrees to draw landscape page on portrait paper
+ IntSize size = mPrintTarget->GetSize();
+ transform.PreTranslate(gfxPoint(0, size.width));
+ gfxMatrix rotate(0, -1, 1, 0, 0, 0);
+ transform = rotate * transform;
+ }
+ transform.PreScale(mPrintingScale, mPrintingScale);
+
+ pContext->SetMatrixDouble(transform);
+ return pContext.forget();
+}
+
+nsresult nsDeviceContext::GetDepth(uint32_t& aDepth) {
+ nsCOMPtr<nsIScreen> screen;
+ FindScreen(getter_AddRefs(screen));
+ if (!screen) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.GetPrimaryScreen(getter_AddRefs(screen));
+ MOZ_ASSERT(screen);
+ }
+ screen->GetColorDepth(reinterpret_cast<int32_t*>(&aDepth));
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::GetDeviceSurfaceDimensions(nscoord& aWidth,
+ nscoord& aHeight) {
+ if (IsPrinterContext()) {
+ aWidth = mWidth;
+ aHeight = mHeight;
+ } else {
+ nsRect area;
+ ComputeFullAreaUsingScreen(&area);
+ aWidth = area.Width();
+ aHeight = area.Height();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::GetRect(nsRect& aRect) {
+ if (IsPrinterContext()) {
+ aRect.SetRect(0, 0, mWidth, mHeight);
+ } else
+ ComputeFullAreaUsingScreen(&aRect);
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::GetClientRect(nsRect& aRect) {
+ if (IsPrinterContext()) {
+ aRect.SetRect(0, 0, mWidth, mHeight);
+ } else
+ ComputeClientRectUsingScreen(&aRect);
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::InitForPrinting(nsIDeviceContextSpec* aDevice) {
+ NS_ENSURE_ARG_POINTER(aDevice);
+
+ MOZ_ASSERT(!mIsInitialized,
+ "Only initialize once, immediately after construction");
+
+ // We don't set mIsInitialized here. The Init() call below does that.
+
+ mPrintTarget = aDevice->MakePrintTarget();
+ if (!mPrintTarget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mDeviceContextSpec = aDevice;
+
+ Init(nullptr);
+
+ if (!CalcPrintingSize()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ MOZ_ASSERT(!mIsCurrentlyPrintingDoc,
+ "Mismatched BeginDocument/EndDocument calls");
+
+ nsresult rv = mPrintTarget->BeginPrinting(aTitle, aPrintToFileName,
+ aStartPage, aEndPage);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mDeviceContextSpec) {
+ rv = mDeviceContextSpec->BeginDocument(aTitle, aPrintToFileName,
+ aStartPage, aEndPage);
+ }
+ mIsCurrentlyPrintingDoc = true;
+ }
+
+ // Warn about any failure (except user cancelling):
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_ABORT,
+ "nsDeviceContext::BeginDocument failed");
+
+ return rv;
+}
+
+nsresult nsDeviceContext::EndDocument(void) {
+ MOZ_ASSERT(mIsCurrentlyPrintingDoc,
+ "Mismatched BeginDocument/EndDocument calls");
+
+ mIsCurrentlyPrintingDoc = false;
+
+ nsresult rv = mPrintTarget->EndPrinting();
+ if (NS_SUCCEEDED(rv)) {
+ mPrintTarget->Finish();
+ }
+
+ if (mDeviceContextSpec) mDeviceContextSpec->EndDocument();
+
+ mPrintTarget = nullptr;
+
+ return rv;
+}
+
+nsresult nsDeviceContext::AbortDocument(void) {
+ MOZ_ASSERT(mIsCurrentlyPrintingDoc,
+ "Mismatched BeginDocument/EndDocument calls");
+
+ nsresult rv = mPrintTarget->AbortPrinting();
+
+ mIsCurrentlyPrintingDoc = false;
+
+ if (mDeviceContextSpec) mDeviceContextSpec->EndDocument();
+
+ mPrintTarget = nullptr;
+
+ return rv;
+}
+
+nsresult nsDeviceContext::BeginPage(void) {
+ nsresult rv = NS_OK;
+
+ if (mDeviceContextSpec) rv = mDeviceContextSpec->BeginPage();
+
+ if (NS_FAILED(rv)) return rv;
+
+ return mPrintTarget->BeginPage();
+}
+
+nsresult nsDeviceContext::EndPage(void) {
+ nsresult rv = mPrintTarget->EndPage();
+
+ if (mDeviceContextSpec) mDeviceContextSpec->EndPage();
+
+ return rv;
+}
+
+void nsDeviceContext::ComputeClientRectUsingScreen(nsRect* outRect) {
+ // we always need to recompute the clientRect
+ // because the window may have moved onto a different screen. In the single
+ // monitor case, we only need to do the computation if we haven't done it
+ // once already, and remember that we have because we're assured it won't
+ // change.
+ nsCOMPtr<nsIScreen> screen;
+ FindScreen(getter_AddRefs(screen));
+ if (screen) {
+ int32_t x, y, width, height;
+ screen->GetAvailRect(&x, &y, &width, &height);
+
+ // convert to device units
+ outRect->SetRect(NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()),
+ NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()),
+ NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()),
+ NSIntPixelsToAppUnits(height, AppUnitsPerDevPixel()));
+ }
+}
+
+void nsDeviceContext::ComputeFullAreaUsingScreen(nsRect* outRect) {
+ // if we have more than one screen, we always need to recompute the clientRect
+ // because the window may have moved onto a different screen. In the single
+ // monitor case, we only need to do the computation if we haven't done it
+ // once already, and remember that we have because we're assured it won't
+ // change.
+ nsCOMPtr<nsIScreen> screen;
+ FindScreen(getter_AddRefs(screen));
+ if (screen) {
+ int32_t x, y, width, height;
+ screen->GetRect(&x, &y, &width, &height);
+
+ // convert to device units
+ outRect->SetRect(NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()),
+ NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()),
+ NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()),
+ NSIntPixelsToAppUnits(height, AppUnitsPerDevPixel()));
+ mWidth = outRect->Width();
+ mHeight = outRect->Height();
+ }
+}
+
+//
+// FindScreen
+//
+// Determines which screen intersects the largest area of the given surface.
+//
+void nsDeviceContext::FindScreen(nsIScreen** outScreen) {
+ if (!mWidget || !mScreenManager) {
+ return;
+ }
+
+ CheckDPIChange();
+
+ nsCOMPtr<nsIScreen> screen = mWidget->GetWidgetScreen();
+ screen.forget(outScreen);
+
+ if (!(*outScreen)) {
+ mScreenManager->GetPrimaryScreen(outScreen);
+ }
+}
+
+bool nsDeviceContext::CalcPrintingSize() {
+ gfxSize size(mPrintTarget->GetSize());
+ // For printing, CSS inches and physical inches are identical
+ // so it doesn't matter which we use here
+ mWidth = NSToCoordRound(size.width * AppUnitsPerPhysicalInch() /
+ POINTS_PER_INCH_FLOAT);
+ mHeight = NSToCoordRound(size.height * AppUnitsPerPhysicalInch() /
+ POINTS_PER_INCH_FLOAT);
+
+ return (mWidth > 0 && mHeight > 0);
+}
+
+bool nsDeviceContext::CheckDPIChange(double* aScale) {
+ int32_t oldDevPixels = mAppUnitsPerDevPixelAtUnitFullZoom;
+ int32_t oldInches = mAppUnitsPerPhysicalInch;
+
+ SetDPI(aScale);
+
+ return oldDevPixels != mAppUnitsPerDevPixelAtUnitFullZoom ||
+ oldInches != mAppUnitsPerPhysicalInch;
+}
+
+bool nsDeviceContext::SetFullZoom(float aScale) {
+ if (aScale <= 0) {
+ MOZ_ASSERT_UNREACHABLE("Invalid full zoom value");
+ return false;
+ }
+ int32_t oldAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
+ mFullZoom = aScale;
+ UpdateAppUnitsForFullZoom();
+ return oldAppUnitsPerDevPixel != mAppUnitsPerDevPixel;
+}
+
+void nsDeviceContext::UpdateAppUnitsForFullZoom() {
+ mAppUnitsPerDevPixel = std::max(
+ 1, NSToIntRound(float(mAppUnitsPerDevPixelAtUnitFullZoom) / mFullZoom));
+ // adjust mFullZoom to reflect appunit rounding
+ mFullZoom = float(mAppUnitsPerDevPixelAtUnitFullZoom) / mAppUnitsPerDevPixel;
+}
+
+DesktopToLayoutDeviceScale nsDeviceContext::GetDesktopToDeviceScale() {
+ nsCOMPtr<nsIScreen> screen;
+ FindScreen(getter_AddRefs(screen));
+
+ if (screen) {
+ double scale;
+ screen->GetContentsScaleFactor(&scale);
+ return DesktopToLayoutDeviceScale(scale);
+ }
+
+ return DesktopToLayoutDeviceScale(1.0);
+}
+
+bool nsDeviceContext::IsSyncPagePrinting() const {
+ MOZ_ASSERT(mPrintTarget);
+ return mPrintTarget->IsSyncPagePrinting();
+}
+
+void nsDeviceContext::RegisterPageDoneCallback(
+ PrintTarget::PageDoneCallback&& aCallback) {
+ MOZ_ASSERT(mPrintTarget && aCallback && !IsSyncPagePrinting());
+ mPrintTarget->RegisterPageDoneCallback(std::move(aCallback));
+}
+void nsDeviceContext::UnregisterPageDoneCallback() {
+ if (mPrintTarget) {
+ mPrintTarget->UnregisterPageDoneCallback();
+ }
+}
diff --git a/gfx/src/nsDeviceContext.h b/gfx/src/nsDeviceContext.h
new file mode 100644
index 0000000000..e538537b6b
--- /dev/null
+++ b/gfx/src/nsDeviceContext.h
@@ -0,0 +1,310 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef _NS_DEVICECONTEXT_H_
+#define _NS_DEVICECONTEXT_H_
+
+#include <stdint.h> // for uint32_t
+#include <sys/types.h> // for int32_t
+#include "gfxTypes.h" // for gfxFloat
+#include "gfxFont.h" // for gfxFont::Orientation
+#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsCoord.h" // for nscoord
+#include "nsError.h" // for nsresult
+#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsMathUtils.h" // for NS_round
+#include "nscore.h" // for char16_t, nsAString
+#include "mozilla/AppUnits.h" // for AppUnits
+#include "nsFontMetrics.h" // for nsFontMetrics::Params
+#include "mozilla/gfx/PrintTarget.h" // for PrintTarget::PageDoneCallback
+
+class gfxContext;
+class gfxTextPerfMetrics;
+class gfxUserFontSet;
+struct nsFont;
+class nsFontCache;
+class nsAtom;
+class nsIDeviceContextSpec;
+class nsIScreen;
+class nsIScreenManager;
+class nsIWidget;
+struct nsRect;
+
+class nsDeviceContext final {
+ public:
+ typedef mozilla::gfx::PrintTarget PrintTarget;
+
+ nsDeviceContext();
+
+ NS_INLINE_DECL_REFCOUNTING(nsDeviceContext)
+
+ /**
+ * Initialize the device context from a widget
+ * @param aWidget a widget to initialize the device context from
+ * @return error status
+ */
+ nsresult Init(nsIWidget* aWidget);
+
+ /*
+ * Initialize the font cache if it hasn't been initialized yet.
+ * (Needed for stylo)
+ */
+ void InitFontCache();
+
+ void UpdateFontCacheUserFonts(gfxUserFontSet* aUserFontSet);
+
+ /**
+ * Initialize the device context from a device context spec
+ * @param aDevSpec the specification of the printing device
+ * @return error status
+ */
+ nsresult InitForPrinting(nsIDeviceContextSpec* aDevSpec);
+
+ /**
+ * Create a rendering context and initialize it. Only call this
+ * method on device contexts that were initialized for printing.
+ *
+ * @return the new rendering context (guaranteed to be non-null)
+ */
+ already_AddRefed<gfxContext> CreateRenderingContext();
+
+ /**
+ * Create a reference rendering context and initialize it. Only call this
+ * method on device contexts that were initialized for printing.
+ *
+ * @return the new rendering context.
+ */
+ already_AddRefed<gfxContext> CreateReferenceRenderingContext();
+
+ /**
+ * Gets the number of app units in one device pixel; this number
+ * is usually a factor of AppUnitsPerCSSPixel(), although that is
+ * not guaranteed.
+ */
+ int32_t AppUnitsPerDevPixel() const { return mAppUnitsPerDevPixel; }
+
+ /**
+ * Convert device pixels which is used for gfx/thebes to nearest
+ * (rounded) app units
+ */
+ nscoord GfxUnitsToAppUnits(gfxFloat aGfxUnits) const {
+ return nscoord(NS_round(aGfxUnits * AppUnitsPerDevPixel()));
+ }
+
+ /**
+ * Convert app units to device pixels which is used for gfx/thebes.
+ */
+ gfxFloat AppUnitsToGfxUnits(nscoord aAppUnits) const {
+ return gfxFloat(aAppUnits) / AppUnitsPerDevPixel();
+ }
+
+ /**
+ * Gets the number of app units in one physical inch; this is the
+ * device's DPI times AppUnitsPerDevPixel().
+ */
+ int32_t AppUnitsPerPhysicalInch() const { return mAppUnitsPerPhysicalInch; }
+
+ /**
+ * Get the ratio of app units to dev pixels that would be used at unit
+ * (100%) full zoom.
+ */
+ int32_t AppUnitsPerDevPixelAtUnitFullZoom() const {
+ return mAppUnitsPerDevPixelAtUnitFullZoom;
+ }
+
+ /**
+ * Get the nsFontMetrics that describe the properties of
+ * an nsFont.
+ * @param aFont font description to obtain metrics for
+ */
+ already_AddRefed<nsFontMetrics> GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams);
+
+ /**
+ * Notification when a font metrics instance created for this device is
+ * about to be deleted
+ */
+ nsresult FontMetricsDeleted(const nsFontMetrics* aFontMetrics);
+
+ /**
+ * Attempt to free up resources by flushing out any fonts no longer
+ * referenced by anything other than the font cache itself.
+ * @return error status
+ */
+ nsresult FlushFontCache();
+
+ /**
+ * Return the bit depth of the device.
+ */
+ nsresult GetDepth(uint32_t& aDepth);
+
+ /**
+ * Get the size of the displayable area of the output device
+ * in app units.
+ * @param aWidth out parameter for width
+ * @param aHeight out parameter for height
+ * @return error status
+ */
+ nsresult GetDeviceSurfaceDimensions(nscoord& aWidth, nscoord& aHeight);
+
+ /**
+ * Get the size of the content area of the output device in app
+ * units. This corresponds on a screen device, for instance, to
+ * the entire screen.
+ * @param aRect out parameter for full rect. Position (x,y) will
+ * be (0,0) or relative to the primary monitor if
+ * this is not the primary.
+ * @return error status
+ */
+ nsresult GetRect(nsRect& aRect);
+
+ /**
+ * Get the size of the content area of the output device in app
+ * units. This corresponds on a screen device, for instance, to
+ * the area reported by GetDeviceSurfaceDimensions, minus the
+ * taskbar (Windows) or menubar (Macintosh).
+ * @param aRect out parameter for client rect. Position (x,y) will
+ * be (0,0) adjusted for any upper/left non-client
+ * space if present or relative to the primary
+ * monitor if this is not the primary.
+ * @return error status
+ */
+ nsresult GetClientRect(nsRect& aRect);
+
+ /**
+ * Returns true if we're currently between BeginDocument() and
+ * EndDocument() calls.
+ */
+ bool IsCurrentlyPrintingDocument() const { return mIsCurrentlyPrintingDoc; }
+
+ /**
+ * Inform the output device that output of a document is beginning
+ * Used for print related device contexts. Must be matched 1:1 with
+ * EndDocument() or AbortDocument().
+ *
+ * @param aTitle - title of Document
+ * @param aPrintToFileName - name of file to print to, if empty then don't
+ * print to file
+ * @param aStartPage - starting page number (must be greater than zero)
+ * @param aEndPage - ending page number (must be less than or
+ * equal to number of pages)
+ *
+ * @return error status
+ */
+ nsresult BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName, int32_t aStartPage,
+ int32_t aEndPage);
+
+ /**
+ * Inform the output device that output of a document is ending.
+ * Used for print related device contexts. Must be matched 1:1 with
+ * BeginDocument()
+ * @return error status
+ */
+ nsresult EndDocument();
+
+ /**
+ * Inform the output device that output of a document is being aborted.
+ * Must be matched 1:1 with BeginDocument()
+ * @return error status
+ */
+ nsresult AbortDocument();
+
+ /**
+ * Inform the output device that output of a page is beginning
+ * Used for print related device contexts. Must be matched 1:1 with
+ * EndPage() and within a BeginDocument()/EndDocument() pair.
+ * @return error status
+ */
+ nsresult BeginPage();
+
+ /**
+ * Inform the output device that output of a page is ending
+ * Used for print related device contexts. Must be matched 1:1 with
+ * BeginPage() and within a BeginDocument()/EndDocument() pair.
+ * @return error status
+ */
+ nsresult EndPage();
+
+ /**
+ * Check to see if the DPI has changed, or impose a new DPI scale value.
+ * @param aScale - If non-null, the default (unzoomed) CSS to device pixel
+ * scale factor will be returned here; and if it is > 0.0
+ * on input, the given value will be used instead of
+ * getting it from the widget (if any). This is used to
+ * allow subdocument contexts to inherit the resolution
+ * setting of their parent.
+ * @return whether there was actually a change in the DPI (whether
+ * AppUnitsPerDevPixel() or AppUnitsPerPhysicalInch()
+ * changed)
+ */
+ bool CheckDPIChange(double* aScale = nullptr);
+
+ /**
+ * Set the full zoom factor: all lengths are multiplied by this factor
+ * when we convert them to device pixels. Returns whether the ratio of
+ * app units to dev pixels changed because of the zoom factor.
+ */
+ bool SetFullZoom(float aScale);
+
+ /**
+ * Returns the page full zoom factor applied.
+ */
+ float GetFullZoom() const { return mFullZoom; }
+
+ /**
+ * True if this device context was created for printing.
+ */
+ bool IsPrinterContext();
+
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale();
+
+ bool IsSyncPagePrinting() const;
+ void RegisterPageDoneCallback(PrintTarget::PageDoneCallback&& aCallback);
+ void UnregisterPageDoneCallback();
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsDeviceContext();
+
+ /**
+ * Implementation shared by CreateRenderingContext and
+ * CreateReferenceRenderingContext.
+ */
+ already_AddRefed<gfxContext> CreateRenderingContextCommon(
+ bool aWantReferenceContext);
+
+ void SetDPI(double* aScale = nullptr);
+ void ComputeClientRectUsingScreen(nsRect* outRect);
+ void ComputeFullAreaUsingScreen(nsRect* outRect);
+ void FindScreen(nsIScreen** outScreen);
+
+ // Return false if the surface is not right
+ bool CalcPrintingSize();
+ void UpdateAppUnitsForFullZoom();
+
+ nscoord mWidth;
+ nscoord mHeight;
+ int32_t mAppUnitsPerDevPixel;
+ int32_t mAppUnitsPerDevPixelAtUnitFullZoom;
+ int32_t mAppUnitsPerPhysicalInch;
+ float mFullZoom;
+ float mPrintingScale;
+ gfxPoint mPrintingTranslate;
+
+ RefPtr<nsFontCache> mFontCache;
+ nsCOMPtr<nsIWidget> mWidget;
+ nsCOMPtr<nsIScreenManager> mScreenManager;
+ nsCOMPtr<nsIDeviceContextSpec> mDeviceContextSpec;
+ RefPtr<PrintTarget> mPrintTarget;
+ bool mIsCurrentlyPrintingDoc;
+#ifdef DEBUG
+ bool mIsInitialized;
+#endif
+};
+
+#endif /* _NS_DEVICECONTEXT_H_ */
diff --git a/gfx/src/nsFont.cpp b/gfx/src/nsFont.cpp
new file mode 100644
index 0000000000..0ab8e9c058
--- /dev/null
+++ b/gfx/src/nsFont.cpp
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFont.h"
+#include "gfxFont.h" // for gfxFontStyle
+#include "gfxFontFeatures.h" // for gfxFontFeature, etc
+#include "gfxFontUtils.h" // for TRUETYPE_TAG
+#include "mozilla/ServoStyleConstsInlines.h"
+#include "nsCRT.h" // for nsCRT
+#include "nsDebug.h" // for NS_ASSERTION
+#include "nsISupports.h"
+#include "nsUnicharUtils.h"
+#include "nscore.h" // for char16_t
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+
+nsFont::nsFont(const FontFamilyList& aFontlist, mozilla::Length aSize)
+ : fontlist(aFontlist), size(aSize) {}
+
+nsFont::nsFont(StyleGenericFontFamily aGenericType, mozilla::Length aSize)
+ : fontlist(aGenericType), size(aSize) {}
+
+nsFont::nsFont(const nsFont& aOther) = default;
+
+nsFont::~nsFont() = default;
+
+bool nsFont::Equals(const nsFont& aOther) const {
+ return CalcDifference(aOther) == MaxDifference::eNone;
+}
+
+nsFont::MaxDifference nsFont::CalcDifference(const nsFont& aOther) const {
+ if ((style != aOther.style) || (systemFont != aOther.systemFont) ||
+ (weight != aOther.weight) || (stretch != aOther.stretch) ||
+ (size != aOther.size) || (sizeAdjust != aOther.sizeAdjust) ||
+ (fontlist != aOther.fontlist) || (kerning != aOther.kerning) ||
+ (opticalSizing != aOther.opticalSizing) ||
+ (synthesis != aOther.synthesis) ||
+ (fontFeatureSettings != aOther.fontFeatureSettings) ||
+ (fontVariationSettings != aOther.fontVariationSettings) ||
+ (languageOverride != aOther.languageOverride) ||
+ (variantAlternates != aOther.variantAlternates) ||
+ (variantCaps != aOther.variantCaps) ||
+ (variantEastAsian != aOther.variantEastAsian) ||
+ (variantLigatures != aOther.variantLigatures) ||
+ (variantNumeric != aOther.variantNumeric) ||
+ (variantPosition != aOther.variantPosition) ||
+ (variantWidth != aOther.variantWidth)) {
+ return MaxDifference::eLayoutAffecting;
+ }
+
+ if ((smoothing != aOther.smoothing) ||
+ (fontSmoothingBackgroundColor != aOther.fontSmoothingBackgroundColor)) {
+ return MaxDifference::eVisual;
+ }
+
+ return MaxDifference::eNone;
+}
+
+nsFont& nsFont::operator=(const nsFont& aOther) = default;
+
+// mapping from bitflag to font feature tag/value pair
+//
+// these need to be kept in sync with the constants listed
+// in gfxFontConstants.h (e.g. NS_FONT_VARIANT_EAST_ASIAN_JIS78)
+
+// NS_FONT_VARIANT_EAST_ASIAN_xxx values
+const gfxFontFeature eastAsianDefaults[] = {
+ {TRUETYPE_TAG('j', 'p', '7', '8'), 1},
+ {TRUETYPE_TAG('j', 'p', '8', '3'), 1},
+ {TRUETYPE_TAG('j', 'p', '9', '0'), 1},
+ {TRUETYPE_TAG('j', 'p', '0', '4'), 1},
+ {TRUETYPE_TAG('s', 'm', 'p', 'l'), 1},
+ {TRUETYPE_TAG('t', 'r', 'a', 'd'), 1},
+ {TRUETYPE_TAG('f', 'w', 'i', 'd'), 1},
+ {TRUETYPE_TAG('p', 'w', 'i', 'd'), 1},
+ {TRUETYPE_TAG('r', 'u', 'b', 'y'), 1}};
+
+static_assert(MOZ_ARRAY_LENGTH(eastAsianDefaults) ==
+ NS_FONT_VARIANT_EAST_ASIAN_COUNT,
+ "eastAsianDefaults[] should be correct");
+
+// NS_FONT_VARIANT_LIGATURES_xxx values
+const gfxFontFeature ligDefaults[] = {
+ {TRUETYPE_TAG('l', 'i', 'g', 'a'), 0}, // none value means all off
+ {TRUETYPE_TAG('l', 'i', 'g', 'a'), 1},
+ {TRUETYPE_TAG('l', 'i', 'g', 'a'), 0},
+ {TRUETYPE_TAG('d', 'l', 'i', 'g'), 1},
+ {TRUETYPE_TAG('d', 'l', 'i', 'g'), 0},
+ {TRUETYPE_TAG('h', 'l', 'i', 'g'), 1},
+ {TRUETYPE_TAG('h', 'l', 'i', 'g'), 0},
+ {TRUETYPE_TAG('c', 'a', 'l', 't'), 1},
+ {TRUETYPE_TAG('c', 'a', 'l', 't'), 0}};
+
+static_assert(MOZ_ARRAY_LENGTH(ligDefaults) == NS_FONT_VARIANT_LIGATURES_COUNT,
+ "ligDefaults[] should be correct");
+
+// NS_FONT_VARIANT_NUMERIC_xxx values
+const gfxFontFeature numericDefaults[] = {
+ {TRUETYPE_TAG('l', 'n', 'u', 'm'), 1},
+ {TRUETYPE_TAG('o', 'n', 'u', 'm'), 1},
+ {TRUETYPE_TAG('p', 'n', 'u', 'm'), 1},
+ {TRUETYPE_TAG('t', 'n', 'u', 'm'), 1},
+ {TRUETYPE_TAG('f', 'r', 'a', 'c'), 1},
+ {TRUETYPE_TAG('a', 'f', 'r', 'c'), 1},
+ {TRUETYPE_TAG('z', 'e', 'r', 'o'), 1},
+ {TRUETYPE_TAG('o', 'r', 'd', 'n'), 1}};
+
+static_assert(MOZ_ARRAY_LENGTH(numericDefaults) ==
+ NS_FONT_VARIANT_NUMERIC_COUNT,
+ "numericDefaults[] should be correct");
+
+static void AddFontFeaturesBitmask(uint32_t aValue, uint32_t aMin,
+ uint32_t aMax,
+ const gfxFontFeature aFeatureDefaults[],
+ nsTArray<gfxFontFeature>& aFeaturesOut)
+
+{
+ uint32_t i, m;
+
+ for (i = 0, m = aMin; m <= aMax; i++, m <<= 1) {
+ if (m & aValue) {
+ const gfxFontFeature& feature = aFeatureDefaults[i];
+ aFeaturesOut.AppendElement(feature);
+ }
+ }
+}
+
+static uint32_t FontFeatureTagForVariantWidth(uint32_t aVariantWidth) {
+ switch (aVariantWidth) {
+ case NS_FONT_VARIANT_WIDTH_FULL:
+ return TRUETYPE_TAG('f', 'w', 'i', 'd');
+ case NS_FONT_VARIANT_WIDTH_HALF:
+ return TRUETYPE_TAG('h', 'w', 'i', 'd');
+ case NS_FONT_VARIANT_WIDTH_THIRD:
+ return TRUETYPE_TAG('t', 'w', 'i', 'd');
+ case NS_FONT_VARIANT_WIDTH_QUARTER:
+ return TRUETYPE_TAG('q', 'w', 'i', 'd');
+ default:
+ return 0;
+ }
+}
+
+void nsFont::AddFontFeaturesToStyle(gfxFontStyle* aStyle,
+ bool aVertical) const {
+ // add in font-variant features
+ gfxFontFeature setting;
+
+ // -- kerning
+ setting.mTag = aVertical ? TRUETYPE_TAG('v', 'k', 'r', 'n')
+ : TRUETYPE_TAG('k', 'e', 'r', 'n');
+ switch (kerning) {
+ case NS_FONT_KERNING_NONE:
+ setting.mValue = 0;
+ aStyle->featureSettings.AppendElement(setting);
+ break;
+ case NS_FONT_KERNING_NORMAL:
+ setting.mValue = 1;
+ aStyle->featureSettings.AppendElement(setting);
+ break;
+ default:
+ // auto case implies use user agent default
+ break;
+ }
+
+ // -- alternates
+ //
+ // NOTE(emilio): We handle historical-forms here because it doesn't depend on
+ // other values set by @font-face and thus may be less expensive to do here
+ // than after font-matching.
+ for (auto& alternate : variantAlternates.AsSpan()) {
+ if (alternate.IsHistoricalForms()) {
+ setting.mValue = 1;
+ setting.mTag = TRUETYPE_TAG('h', 'i', 's', 't');
+ aStyle->featureSettings.AppendElement(setting);
+ break;
+ }
+ }
+
+ // -- copy font-specific alternate info into style
+ // (this will be resolved after font-matching occurs)
+ aStyle->variantAlternates = variantAlternates;
+
+ // -- caps
+ aStyle->variantCaps = variantCaps;
+
+ // -- east-asian
+ if (variantEastAsian) {
+ AddFontFeaturesBitmask(variantEastAsian, NS_FONT_VARIANT_EAST_ASIAN_JIS78,
+ NS_FONT_VARIANT_EAST_ASIAN_RUBY, eastAsianDefaults,
+ aStyle->featureSettings);
+ }
+
+ // -- ligatures
+ if (variantLigatures) {
+ AddFontFeaturesBitmask(variantLigatures, NS_FONT_VARIANT_LIGATURES_NONE,
+ NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL, ligDefaults,
+ aStyle->featureSettings);
+
+ if (variantLigatures & NS_FONT_VARIANT_LIGATURES_COMMON) {
+ // liga already enabled, need to enable clig also
+ setting.mTag = TRUETYPE_TAG('c', 'l', 'i', 'g');
+ setting.mValue = 1;
+ aStyle->featureSettings.AppendElement(setting);
+ } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NO_COMMON) {
+ // liga already disabled, need to disable clig also
+ setting.mTag = TRUETYPE_TAG('c', 'l', 'i', 'g');
+ setting.mValue = 0;
+ aStyle->featureSettings.AppendElement(setting);
+ } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NONE) {
+ // liga already disabled, need to disable dlig, hlig, calt, clig
+ setting.mValue = 0;
+ setting.mTag = TRUETYPE_TAG('d', 'l', 'i', 'g');
+ aStyle->featureSettings.AppendElement(setting);
+ setting.mTag = TRUETYPE_TAG('h', 'l', 'i', 'g');
+ aStyle->featureSettings.AppendElement(setting);
+ setting.mTag = TRUETYPE_TAG('c', 'a', 'l', 't');
+ aStyle->featureSettings.AppendElement(setting);
+ setting.mTag = TRUETYPE_TAG('c', 'l', 'i', 'g');
+ aStyle->featureSettings.AppendElement(setting);
+ }
+ }
+
+ // -- numeric
+ if (variantNumeric) {
+ AddFontFeaturesBitmask(variantNumeric, NS_FONT_VARIANT_NUMERIC_LINING,
+ NS_FONT_VARIANT_NUMERIC_ORDINAL, numericDefaults,
+ aStyle->featureSettings);
+ }
+
+ // -- position
+ aStyle->variantSubSuper = variantPosition;
+
+ // -- width
+ setting.mTag = FontFeatureTagForVariantWidth(variantWidth);
+ if (setting.mTag) {
+ setting.mValue = 1;
+ aStyle->featureSettings.AppendElement(setting);
+ }
+
+ // indicate common-path case when neither variantCaps or variantSubSuper are
+ // set
+ aStyle->noFallbackVariantFeatures =
+ (aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL) &&
+ (variantPosition == NS_FONT_VARIANT_POSITION_NORMAL);
+
+ // If the feature list is not empty, we insert a "fake" feature with tag=0
+ // as delimiter between the above "high-level" features from font-variant-*
+ // etc and those coming from the low-level font-feature-settings property.
+ // This will allow us to distinguish high- and low-level settings when it
+ // comes to potentially disabling ligatures because of letter-spacing.
+ if (!aStyle->featureSettings.IsEmpty() || !fontFeatureSettings.IsEmpty()) {
+ aStyle->featureSettings.AppendElement(gfxFontFeature{0, 0});
+ }
+
+ // add in features from font-feature-settings
+ aStyle->featureSettings.AppendElements(fontFeatureSettings);
+
+ // enable grayscale antialiasing for text
+ if (smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
+ aStyle->useGrayscaleAntialiasing = true;
+ }
+
+ aStyle->fontSmoothingBackgroundColor = fontSmoothingBackgroundColor.ToColor();
+}
+
+void nsFont::AddFontVariationsToStyle(gfxFontStyle* aStyle) const {
+ // If auto optical sizing is enabled, and if there's no 'opsz' axis in
+ // fontVariationSettings, then set the automatic value on the style.
+ class VariationTagComparator {
+ public:
+ bool Equals(const gfxFontVariation& aVariation, uint32_t aTag) const {
+ return aVariation.mTag == aTag;
+ }
+ };
+ const uint32_t kTagOpsz = TRUETYPE_TAG('o', 'p', 's', 'z');
+ if (opticalSizing == NS_FONT_OPTICAL_SIZING_AUTO &&
+ !fontVariationSettings.Contains(kTagOpsz, VariationTagComparator())) {
+ gfxFontVariation opsz = {kTagOpsz, size.ToCSSPixels()};
+ aStyle->variationSettings.AppendElement(opsz);
+ }
+
+ // Add in arbitrary values from font-variation-settings
+ aStyle->variationSettings.AppendElements(fontVariationSettings);
+}
diff --git a/gfx/src/nsFont.h b/gfx/src/nsFont.h
new file mode 100644
index 0000000000..9217aeab10
--- /dev/null
+++ b/gfx/src/nsFont.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsFont_h___
+#define nsFont_h___
+
+#include <cstdint>
+#include "gfxFontFamilyList.h"
+#include "gfxFontConstants.h" // for NS_FONT_KERNING_AUTO, etc
+#include "gfxFontVariations.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/ServoStyleConstsInlines.h"
+#include "mozilla/StyleColorInlines.h" // for StyleRGBA
+#include "nsTArray.h" // for nsTArray
+
+struct gfxFontFeature;
+struct gfxFontStyle;
+
+// Font structure.
+struct nsFont final {
+ typedef mozilla::FontStretch FontStretch;
+ typedef mozilla::FontSlantStyle FontSlantStyle;
+ typedef mozilla::FontWeight FontWeight;
+
+ // List of font families, either named or generic.
+ // This contains a RefPtr and a uint32_t field.
+ mozilla::FontFamilyList fontlist;
+
+ // Font features from CSS font-feature-settings
+ CopyableTArray<gfxFontFeature> fontFeatureSettings;
+
+ // Font variations from CSS font-variation-settings
+ CopyableTArray<gfxFontVariation> fontVariationSettings;
+
+ // The logical size of the font, in CSS Pixels
+ mozilla::NonNegativeLength size{0};
+
+ // The aspect-value (ie., the ratio actualsize:actualxheight) that any
+ // actual physical font created from this font structure must have when
+ // rendering or measuring a string. A value of -1.0 means no adjustment
+ // needs to be done; otherwise the value must be nonnegative.
+ float sizeAdjust = -1.0f;
+
+ // The estimated background color behind the text. Enables a special
+ // rendering mode when NS_GET_A(.) > 0. Only used for text in the chrome.
+ mozilla::StyleRGBA fontSmoothingBackgroundColor =
+ mozilla::StyleRGBA::Transparent();
+
+ // Language system tag, to override document language;
+ // this is an OpenType "language system" tag represented as a 32-bit integer
+ // (see http://www.microsoft.com/typography/otspec/languagetags.htm).
+ uint32_t languageOverride = 0;
+
+ // Font-selection/rendering properties corresponding to CSS font-style,
+ // font-weight, font-stretch. These are all 16-bit types.
+ FontSlantStyle style = FontSlantStyle::Normal();
+ FontWeight weight = FontWeight::Normal();
+ FontStretch stretch = FontStretch::Normal();
+
+ // Some font-variant-alternates property values require
+ // font-specific settings defined via @font-feature-values rules.
+ // These are resolved *after* font matching occurs.
+ mozilla::StyleVariantAlternatesList variantAlternates;
+
+ // Variant subproperties
+ uint16_t variantLigatures = NS_FONT_VARIANT_LIGATURES_NORMAL;
+ uint16_t variantEastAsian = NS_FONT_VARIANT_EAST_ASIAN_NORMAL;
+
+ uint8_t variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+ uint8_t variantNumeric = NS_FONT_VARIANT_NUMERIC_NORMAL;
+ uint8_t variantPosition = NS_FONT_VARIANT_POSITION_NORMAL;
+ uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL;
+
+ // Smoothing - controls subpixel-antialiasing (currently OSX only)
+ uint8_t smoothing = NS_FONT_SMOOTHING_AUTO;
+
+ // Kerning
+ uint8_t kerning = NS_FONT_KERNING_AUTO;
+
+ // Whether automatic optical sizing should be applied to variation fonts
+ // that include an 'opsz' axis
+ uint8_t opticalSizing = NS_FONT_OPTICAL_SIZING_AUTO;
+
+ // Synthesis setting, controls use of fake bolding/italics
+ uint8_t synthesis = NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE;
+
+ // Force this font to not be considered a 'generic' font, even if
+ // the name is the same as a CSS generic font family.
+ bool systemFont = false;
+
+ // initialize the font with a fontlist
+ nsFont(const mozilla::FontFamilyList& aFontlist, mozilla::Length aSize);
+
+ // initialize the font with a single generic
+ nsFont(mozilla::StyleGenericFontFamily, mozilla::Length aSize);
+
+ // Make a copy of the given font
+ nsFont(const nsFont& aFont);
+
+ // leave members uninitialized
+ nsFont() = default;
+ ~nsFont();
+
+ bool operator==(const nsFont& aOther) const { return Equals(aOther); }
+
+ bool operator!=(const nsFont& aOther) const { return !Equals(aOther); }
+
+ bool Equals(const nsFont& aOther) const;
+
+ nsFont& operator=(const nsFont& aOther);
+
+ enum class MaxDifference : uint8_t { eNone, eVisual, eLayoutAffecting };
+
+ MaxDifference CalcDifference(const nsFont& aOther) const;
+
+ // Add featureSettings into style
+ void AddFontFeaturesToStyle(gfxFontStyle* aStyle, bool aVertical) const;
+
+ void AddFontVariationsToStyle(gfxFontStyle* aStyle) const;
+};
+
+#define NS_FONT_VARIANT_NORMAL 0
+#define NS_FONT_VARIANT_SMALL_CAPS 1
+
+#endif /* nsFont_h___ */
diff --git a/gfx/src/nsFontMetrics.cpp b/gfx/src/nsFontMetrics.cpp
new file mode 100644
index 0000000000..767b979f3a
--- /dev/null
+++ b/gfx/src/nsFontMetrics.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFontMetrics.h"
+#include <math.h> // for floor, ceil
+#include <algorithm> // for max
+#include "gfxContext.h" // for gfxContext
+#include "gfxFontConstants.h" // for NS_FONT_SYNTHESIS_*
+#include "gfxPlatform.h" // for gfxPlatform
+#include "gfxPoint.h" // for gfxPoint
+#include "gfxRect.h" // for gfxRect
+#include "gfxTextRun.h" // for gfxFontGroup
+#include "gfxTypes.h" // for gfxFloat
+#include "nsBoundingMetrics.h" // for nsBoundingMetrics
+#include "nsDebug.h" // for NS_ERROR
+#include "nsDeviceContext.h" // for nsDeviceContext
+#include "nsAtom.h" // for nsAtom
+#include "nsMathUtils.h" // for NS_round
+#include "nsString.h" // for nsString
+#include "nsStyleConsts.h" // for StyleHyphens::None
+#include "mozilla/Assertions.h" // for MOZ_ASSERT
+#include "mozilla/UniquePtr.h" // for UniquePtr
+
+class gfxUserFontSet;
+using namespace mozilla;
+
+namespace {
+
+class AutoTextRun {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ AutoTextRun(nsFontMetrics* aMetrics, DrawTarget* aDrawTarget,
+ const char* aString, int32_t aLength) {
+ mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
+ reinterpret_cast<const uint8_t*>(aString), aLength, aDrawTarget,
+ aMetrics->AppUnitsPerDevPixel(), ComputeFlags(aMetrics),
+ nsTextFrameUtils::Flags(), nullptr);
+ }
+
+ AutoTextRun(nsFontMetrics* aMetrics, DrawTarget* aDrawTarget,
+ const char16_t* aString, int32_t aLength) {
+ mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
+ aString, aLength, aDrawTarget, aMetrics->AppUnitsPerDevPixel(),
+ ComputeFlags(aMetrics), nsTextFrameUtils::Flags(), nullptr);
+ }
+
+ gfxTextRun* get() { return mTextRun.get(); }
+ gfxTextRun* operator->() { return mTextRun.get(); }
+
+ private:
+ static gfx::ShapedTextFlags ComputeFlags(nsFontMetrics* aMetrics) {
+ gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
+ if (aMetrics->GetTextRunRTL()) {
+ flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
+ }
+ if (aMetrics->GetVertical()) {
+ switch (aMetrics->GetTextOrientation()) {
+ case StyleTextOrientation::Mixed:
+ flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED;
+ break;
+ case StyleTextOrientation::Upright:
+ flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ break;
+ case StyleTextOrientation::Sideways:
+ flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ break;
+ }
+ }
+ return flags;
+ }
+
+ RefPtr<gfxTextRun> mTextRun;
+};
+
+class StubPropertyProvider final : public gfxTextRun::PropertyProvider {
+ public:
+ void GetHyphenationBreaks(
+ gfxTextRun::Range aRange,
+ gfxTextRun::HyphenType* aBreakBefore) const override {
+ NS_ERROR(
+ "This shouldn't be called because we never call BreakAndMeasureText");
+ }
+ mozilla::StyleHyphens GetHyphensOption() const override {
+ NS_ERROR(
+ "This shouldn't be called because we never call BreakAndMeasureText");
+ return mozilla::StyleHyphens::None;
+ }
+ gfxFloat GetHyphenWidth() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return 0;
+ }
+ already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return nullptr;
+ }
+ uint32_t GetAppUnitsPerDevUnit() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return 60;
+ }
+ void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) const override {
+ NS_ERROR("This shouldn't be called because we never enable spacing");
+ }
+};
+
+} // namespace
+
+nsFontMetrics::nsFontMetrics(const nsFont& aFont, const Params& aParams,
+ nsDeviceContext* aContext)
+ : mFont(aFont),
+ mLanguage(aParams.language),
+ mDeviceContext(aContext),
+ mP2A(aContext->AppUnitsPerDevPixel()),
+ mOrientation(aParams.orientation),
+ mExplicitLanguage(aParams.explicitLanguage),
+ mTextRunRTL(false),
+ mVertical(false),
+ mTextOrientation(mozilla::StyleTextOrientation::Mixed) {
+ gfxFontStyle style(aFont.style, aFont.weight, aFont.stretch,
+ gfxFloat(aFont.size.ToAppUnits()) / mP2A, aFont.sizeAdjust,
+ aFont.systemFont, mDeviceContext->IsPrinterContext(),
+ aFont.synthesis & NS_FONT_SYNTHESIS_WEIGHT,
+ aFont.synthesis & NS_FONT_SYNTHESIS_STYLE,
+ aFont.languageOverride);
+
+ aFont.AddFontFeaturesToStyle(&style, mOrientation == eVertical);
+ style.featureValueLookup = aParams.featureValueLookup;
+
+ aFont.AddFontVariationsToStyle(&style);
+
+ gfxFloat devToCssSize = gfxFloat(mP2A) / gfxFloat(AppUnitsPerCSSPixel());
+ mFontGroup = gfxPlatform::GetPlatform()->CreateFontGroup(
+ aFont.fontlist, &style, mLanguage, mExplicitLanguage, aParams.textPerf,
+ aParams.fontStats, aParams.userFontSet, devToCssSize);
+}
+
+nsFontMetrics::~nsFontMetrics() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDeviceContext) {
+ mDeviceContext->FontMetricsDeleted(this);
+ }
+}
+
+void nsFontMetrics::Destroy() { mDeviceContext = nullptr; }
+
+// XXXTODO get rid of this macro
+#define ROUND_TO_TWIPS(x) (nscoord) floor(((x)*mP2A) + 0.5)
+#define CEIL_TO_TWIPS(x) (nscoord) ceil((x)*mP2A)
+
+static const gfxFont::Metrics& GetMetrics(
+ nsFontMetrics* aFontMetrics, nsFontMetrics::FontOrientation aOrientation) {
+ return aFontMetrics->GetThebesFontGroup()->GetFirstValidFont()->GetMetrics(
+ aOrientation);
+}
+
+static const gfxFont::Metrics& GetMetrics(nsFontMetrics* aFontMetrics) {
+ return GetMetrics(aFontMetrics, aFontMetrics->Orientation());
+}
+
+nscoord nsFontMetrics::XHeight() {
+ return ROUND_TO_TWIPS(GetMetrics(this).xHeight);
+}
+
+nscoord nsFontMetrics::CapHeight() {
+ return ROUND_TO_TWIPS(GetMetrics(this).capHeight);
+}
+
+nscoord nsFontMetrics::SuperscriptOffset() {
+ return ROUND_TO_TWIPS(GetMetrics(this).emHeight *
+ NS_FONT_SUPERSCRIPT_OFFSET_RATIO);
+}
+
+nscoord nsFontMetrics::SubscriptOffset() {
+ return ROUND_TO_TWIPS(GetMetrics(this).emHeight *
+ NS_FONT_SUBSCRIPT_OFFSET_RATIO);
+}
+
+void nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) {
+ aOffset = ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset);
+ aSize = ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize);
+}
+
+void nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) {
+ aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset());
+ aSize = ROUND_TO_TWIPS(GetMetrics(this).underlineSize);
+}
+
+// GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the
+// text-decoration lines drawable area. See bug 421353.
+// BE CAREFUL for rounding each values. The logic MUST be same as
+// nsCSSRendering::GetTextDecorationRectInternal's.
+
+static gfxFloat ComputeMaxDescent(const gfxFont::Metrics& aMetrics,
+ gfxFontGroup* aFontGroup) {
+ gfxFloat offset = floor(-aFontGroup->GetUnderlineOffset() + 0.5);
+ gfxFloat size = NS_round(aMetrics.underlineSize);
+ gfxFloat minDescent = offset + size;
+ return floor(std::max(minDescent, aMetrics.maxDescent) + 0.5);
+}
+
+static gfxFloat ComputeMaxAscent(const gfxFont::Metrics& aMetrics) {
+ return floor(aMetrics.maxAscent + 0.5);
+}
+
+nscoord nsFontMetrics::InternalLeading() {
+ return ROUND_TO_TWIPS(GetMetrics(this).internalLeading);
+}
+
+nscoord nsFontMetrics::ExternalLeading() {
+ return ROUND_TO_TWIPS(GetMetrics(this).externalLeading);
+}
+
+nscoord nsFontMetrics::EmHeight() {
+ return ROUND_TO_TWIPS(GetMetrics(this).emHeight);
+}
+
+nscoord nsFontMetrics::EmAscent() {
+ return ROUND_TO_TWIPS(GetMetrics(this).emAscent);
+}
+
+nscoord nsFontMetrics::EmDescent() {
+ return ROUND_TO_TWIPS(GetMetrics(this).emDescent);
+}
+
+nscoord nsFontMetrics::MaxHeight() {
+ return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) +
+ CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup));
+}
+
+nscoord nsFontMetrics::MaxAscent() {
+ return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this)));
+}
+
+nscoord nsFontMetrics::MaxDescent() {
+ return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup));
+}
+
+nscoord nsFontMetrics::MaxAdvance() {
+ return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance);
+}
+
+nscoord nsFontMetrics::AveCharWidth() {
+ // Use CEIL instead of ROUND for consistency with GetMaxAdvance
+ return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth);
+}
+
+nscoord nsFontMetrics::SpaceWidth() {
+ // For vertical text with mixed or sideways orientation, we want the
+ // width of a horizontal space (even if we're using vertical line-spacing
+ // metrics, as with "writing-mode:vertical-*;text-orientation:mixed").
+ return CEIL_TO_TWIPS(
+ GetMetrics(this,
+ mVertical && mTextOrientation == StyleTextOrientation::Upright
+ ? eVertical
+ : eHorizontal)
+ .spaceWidth);
+}
+
+int32_t nsFontMetrics::GetMaxStringLength() {
+ const gfxFont::Metrics& m = GetMetrics(this);
+ const double x = 32767.0 / std::max(1.0, m.maxAdvance);
+ int32_t len = (int32_t)floor(x);
+ return std::max(1, len);
+}
+
+nscoord nsFontMetrics::GetWidth(const char* aString, uint32_t aLength,
+ DrawTarget* aDrawTarget) {
+ if (aLength == 0) return 0;
+
+ if (aLength == 1 && aString[0] == ' ') return SpaceWidth();
+
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aDrawTarget, aString, aLength);
+ if (textRun.get()) {
+ return NSToCoordRound(
+ textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider));
+ }
+ return 0;
+}
+
+nscoord nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength,
+ DrawTarget* aDrawTarget) {
+ if (aLength == 0) return 0;
+
+ if (aLength == 1 && aString[0] == ' ') return SpaceWidth();
+
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aDrawTarget, aString, aLength);
+ if (textRun.get()) {
+ return NSToCoordRound(
+ textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider));
+ }
+ return 0;
+}
+
+// Draw a string using this font handle on the surface passed in.
+void nsFontMetrics::DrawString(const char* aString, uint32_t aLength,
+ nscoord aX, nscoord aY, gfxContext* aContext) {
+ if (aLength == 0) return;
+
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength);
+ if (!textRun.get()) {
+ return;
+ }
+ gfx::Point pt(aX, aY);
+ gfxTextRun::Range range(0, aLength);
+ if (mTextRunRTL) {
+ if (mVertical) {
+ pt.y += textRun->GetAdvanceWidth(range, &provider);
+ } else {
+ pt.x += textRun->GetAdvanceWidth(range, &provider);
+ }
+ }
+ gfxTextRun::DrawParams params(aContext);
+ params.provider = &provider;
+ textRun->Draw(range, pt, params);
+}
+
+void nsFontMetrics::DrawString(const char16_t* aString, uint32_t aLength,
+ nscoord aX, nscoord aY, gfxContext* aContext,
+ DrawTarget* aTextRunConstructionDrawTarget) {
+ if (aLength == 0) return;
+
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength);
+ if (!textRun.get()) {
+ return;
+ }
+ gfx::Point pt(aX, aY);
+ gfxTextRun::Range range(0, aLength);
+ if (mTextRunRTL) {
+ if (mVertical) {
+ pt.y += textRun->GetAdvanceWidth(range, &provider);
+ } else {
+ pt.x += textRun->GetAdvanceWidth(range, &provider);
+ }
+ }
+ gfxTextRun::DrawParams params(aContext);
+ params.provider = &provider;
+ textRun->Draw(range, pt, params);
+}
+
+static nsBoundingMetrics GetTextBoundingMetrics(
+ nsFontMetrics* aMetrics, const char16_t* aString, uint32_t aLength,
+ mozilla::gfx::DrawTarget* aDrawTarget, gfxFont::BoundingBoxType aType) {
+ if (aLength == 0) return nsBoundingMetrics();
+
+ StubPropertyProvider provider;
+ AutoTextRun textRun(aMetrics, aDrawTarget, aString, aLength);
+ nsBoundingMetrics m;
+ if (textRun.get()) {
+ gfxTextRun::Metrics theMetrics = textRun->MeasureText(
+ gfxTextRun::Range(0, aLength), aType, aDrawTarget, &provider);
+
+ m.leftBearing = NSToCoordFloor(theMetrics.mBoundingBox.X());
+ m.rightBearing = NSToCoordCeil(theMetrics.mBoundingBox.XMost());
+ m.ascent = NSToCoordCeil(-theMetrics.mBoundingBox.Y());
+ m.descent = NSToCoordCeil(theMetrics.mBoundingBox.YMost());
+ m.width = NSToCoordRound(theMetrics.mAdvanceWidth);
+ }
+ return m;
+}
+
+nsBoundingMetrics nsFontMetrics::GetBoundingMetrics(const char16_t* aString,
+ uint32_t aLength,
+ DrawTarget* aDrawTarget) {
+ return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget,
+ gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS);
+}
+
+nsBoundingMetrics nsFontMetrics::GetInkBoundsForInkOverflow(
+ const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) {
+ return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget,
+ gfxFont::LOOSE_INK_EXTENTS);
+}
+
+gfxUserFontSet* nsFontMetrics::GetUserFontSet() const {
+ return mFontGroup->GetUserFontSet();
+}
diff --git a/gfx/src/nsFontMetrics.h b/gfx/src/nsFontMetrics.h
new file mode 100644
index 0000000000..c98dac0051
--- /dev/null
+++ b/gfx/src/nsFontMetrics.h
@@ -0,0 +1,276 @@
+/* -*- 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/. */
+
+#ifndef NSFONTMETRICS__H__
+#define NSFONTMETRICS__H__
+
+#include <stdint.h> // for uint32_t
+#include <sys/types.h> // for int32_t
+#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsCoord.h" // for nscoord
+#include "nsError.h" // for nsresult
+#include "nsFont.h" // for nsFont
+#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING
+#include "nsStyleConsts.h"
+#include "nscore.h" // for char16_t
+
+class gfxContext;
+class gfxFontGroup;
+class gfxUserFontSet;
+class gfxTextPerfMetrics;
+class nsDeviceContext;
+class nsAtom;
+struct nsBoundingMetrics;
+struct FontMatchingStats;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * Font metrics
+ *
+ * This class may be somewhat misnamed. A better name might be
+ * nsFontList. The style system uses the nsFont struct for various
+ * font properties, one of which is font-family, which can contain a
+ * *list* of font names. The nsFont struct is "realized" by asking the
+ * device context to cough up an nsFontMetrics object, which contains
+ * a list of real font handles, one for each font mentioned in
+ * font-family (and for each fallback when we fall off the end of that
+ * list).
+ *
+ * The style system needs to have access to certain metrics, such as
+ * the em height (for the CSS "em" unit), and we use the first Western
+ * font's metrics for that purpose. The platform-specific
+ * implementations are expected to select non-Western fonts that "fit"
+ * reasonably well with the Western font that is loaded at Init time.
+ */
+class nsFontMetrics final {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ enum FontOrientation { eHorizontal, eVertical };
+
+ struct MOZ_STACK_CLASS Params {
+ nsAtom* language = nullptr;
+ bool explicitLanguage = false;
+ FontOrientation orientation = eHorizontal;
+ gfxUserFontSet* userFontSet = nullptr;
+ gfxTextPerfMetrics* textPerf = nullptr;
+ FontMatchingStats* fontStats = nullptr;
+ gfxFontFeatureValueSet* featureValueLookup = nullptr;
+ };
+
+ nsFontMetrics(const nsFont& aFont, const Params& aParams,
+ nsDeviceContext* aContext);
+
+ // Used by stylo
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsFontMetrics)
+
+ /**
+ * Destroy this font metrics. This breaks the association between
+ * the font metrics and the device context.
+ */
+ void Destroy();
+
+ /**
+ * Return the font's x-height.
+ */
+ nscoord XHeight();
+
+ /**
+ * Return the font's cap-height.
+ */
+ nscoord CapHeight();
+
+ /**
+ * Return the font's superscript offset (the distance from the
+ * baseline to where a superscript's baseline should be placed).
+ * The value returned will be positive.
+ */
+ nscoord SuperscriptOffset();
+
+ /**
+ * Return the font's subscript offset (the distance from the
+ * baseline to where a subscript's baseline should be placed).
+ * The value returned will be positive.
+ */
+ nscoord SubscriptOffset();
+
+ /**
+ * Return the font's strikeout offset (the distance from the
+ * baseline to where a strikeout should be placed) and size.
+ * Positive values are above the baseline, negative below.
+ */
+ void GetStrikeout(nscoord& aOffset, nscoord& aSize);
+
+ /**
+ * Return the font's underline offset (the distance from the
+ * baseline to where a underline should be placed) and size.
+ * Positive values are above the baseline, negative below.
+ */
+ void GetUnderline(nscoord& aOffset, nscoord& aSize);
+
+ /**
+ * Returns the amount of internal leading for the font.
+ * This is normally the difference between the max ascent
+ * and the em ascent.
+ */
+ nscoord InternalLeading();
+
+ /**
+ * Returns the amount of external leading for the font.
+ * em ascent(?) plus external leading is the font designer's
+ * recommended line-height for this font.
+ */
+ nscoord ExternalLeading();
+
+ /**
+ * Returns the height of the em square.
+ * This is em ascent plus em descent.
+ */
+ nscoord EmHeight();
+
+ /**
+ * Returns the ascent part of the em square.
+ */
+ nscoord EmAscent();
+
+ /**
+ * Returns the descent part of the em square.
+ */
+ nscoord EmDescent();
+
+ /**
+ * Returns the height of the bounding box.
+ * This is max ascent plus max descent.
+ */
+ nscoord MaxHeight();
+
+ /**
+ * Returns the maximum distance characters in this font extend
+ * above the base line.
+ */
+ nscoord MaxAscent();
+
+ /**
+ * Returns the maximum distance characters in this font extend
+ * below the base line.
+ */
+ nscoord MaxDescent();
+
+ /**
+ * Returns the maximum character advance for the font.
+ */
+ nscoord MaxAdvance();
+
+ /**
+ * Returns the average character width
+ */
+ nscoord AveCharWidth();
+
+ /**
+ * Returns the often needed width of the space character
+ */
+ nscoord SpaceWidth();
+
+ /**
+ * Returns the font associated with these metrics. The return value
+ * is only defined after Init() has been called.
+ */
+ const nsFont& Font() const { return mFont; }
+
+ /**
+ * Returns the language associated with these metrics
+ */
+ nsAtom* Language() const { return mLanguage; }
+
+ /**
+ * Returns the orientation (horizontal/vertical) of these metrics.
+ */
+ FontOrientation Orientation() const { return mOrientation; }
+
+ int32_t GetMaxStringLength();
+
+ // Get the width for this string. aWidth will be updated with the
+ // width in points, not twips. Callers must convert it if they
+ // want it in another format.
+ nscoord GetWidth(const char* aString, uint32_t aLength,
+ DrawTarget* aDrawTarget);
+ nscoord GetWidth(const char16_t* aString, uint32_t aLength,
+ DrawTarget* aDrawTarget);
+
+ // Draw a string using this font handle on the surface passed in.
+ void DrawString(const char* aString, uint32_t aLength, nscoord aX, nscoord aY,
+ gfxContext* aContext);
+ void DrawString(const char16_t* aString, uint32_t aLength, nscoord aX,
+ nscoord aY, gfxContext* aContext,
+ DrawTarget* aTextRunConstructionDrawTarget);
+
+ nsBoundingMetrics GetBoundingMetrics(const char16_t* aString,
+ uint32_t aLength,
+ DrawTarget* aDrawTarget);
+
+ // Returns the LOOSE_INK_EXTENTS bounds of the text for determing the
+ // overflow area of the string.
+ nsBoundingMetrics GetInkBoundsForInkOverflow(const char16_t* aString,
+ uint32_t aLength,
+ DrawTarget* aDrawTarget);
+
+ void SetTextRunRTL(bool aIsRTL) { mTextRunRTL = aIsRTL; }
+ bool GetTextRunRTL() const { return mTextRunRTL; }
+
+ void SetVertical(bool aVertical) { mVertical = aVertical; }
+ bool GetVertical() const { return mVertical; }
+
+ void SetTextOrientation(mozilla::StyleTextOrientation aTextOrientation) {
+ mTextOrientation = aTextOrientation;
+ }
+ mozilla::StyleTextOrientation GetTextOrientation() const {
+ return mTextOrientation;
+ }
+
+ bool ExplicitLanguage() const { return mExplicitLanguage; }
+
+ gfxFontGroup* GetThebesFontGroup() const { return mFontGroup; }
+ gfxUserFontSet* GetUserFontSet() const;
+
+ int32_t AppUnitsPerDevPixel() const { return mP2A; }
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsFontMetrics();
+
+ nsFont mFont;
+ RefPtr<gfxFontGroup> mFontGroup;
+ RefPtr<nsAtom> mLanguage;
+ // Pointer to the device context for which this fontMetrics object was
+ // created.
+ nsDeviceContext* MOZ_NON_OWNING_REF mDeviceContext;
+ int32_t mP2A;
+
+ // The font orientation (horizontal or vertical) for which these metrics
+ // have been initialized. This determines which line metrics (ascent and
+ // descent) they will return.
+ FontOrientation mOrientation;
+
+ // Whether mLanguage comes from explicit markup (in which case it should be
+ // used to tailor effects like case-conversion) or is an inferred/default
+ // value.
+ bool mExplicitLanguage;
+
+ // These fields may be set by clients to control the behavior of methods
+ // like GetWidth and DrawString according to the writing mode, direction
+ // and text-orientation desired.
+ bool mTextRunRTL;
+ bool mVertical;
+ mozilla::StyleTextOrientation mTextOrientation;
+};
+
+#endif /* NSFONTMETRICS__H__ */
diff --git a/gfx/src/nsGfxCIID.h b/gfx/src/nsGfxCIID.h
new file mode 100644
index 0000000000..65f09ed47b
--- /dev/null
+++ b/gfx/src/nsGfxCIID.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsGfxCIID_h__
+#define nsGfxCIID_h__
+
+#define NS_FONT_ENUMERATOR_CID \
+ { \
+ 0xa6cf9115, 0x15b3, 0x11d2, { \
+ 0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 \
+ } \
+ }
+
+#define NS_SCRIPTABLE_REGION_CID \
+ { \
+ 0xda5b130a, 0x1dd1, 0x11b2, { \
+ 0xad, 0x47, 0xf4, 0x55, 0xb1, 0x81, 0x4a, 0x78 \
+ } \
+ }
+
+#define NS_GFX_INITIALIZATION_CID \
+ { \
+ 0x67c41576, 0x9664, 0x4ed5, { \
+ 0x90, 0xc1, 0xf6, 0x68, 0x3f, 0xd5, 0x2c, 0x8f \
+ } \
+ }
+
+#endif
diff --git a/gfx/src/nsIFontEnumerator.idl b/gfx/src/nsIFontEnumerator.idl
new file mode 100644
index 0000000000..812f467a68
--- /dev/null
+++ b/gfx/src/nsIFontEnumerator.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(924d98d9-3518-4cb4-8708-c74fe8e3ec3c)]
+interface nsIFontEnumerator : nsISupports
+{
+ /**
+ * Return a sorted array of the names of all installed fonts.
+ *
+ * @return array of names
+ * @return void
+ */
+ Array<AString> EnumerateAllFonts();
+
+ /**
+ * Return a sorted array of names of fonts that support the given language
+ * group and are suitable for use as the given CSS generic font.
+ *
+ * @param aLangGroup language group
+ * @param aGeneric CSS generic font
+ * @return array of names
+ * @return void
+ */
+ Array<AString> EnumerateFonts(in string aLangGroup, in string aGeneric);
+
+ /**
+ * Return a promise that resolves to a sorted array of the names of all
+ * installed fonts.
+ *
+ * @return Promise that resolves to Array
+ */
+ [implicit_jscontext]
+ jsval EnumerateAllFontsAsync();
+
+ /**
+ * Return a promise that resolves to a sorted array of names of fonts
+ * that support the given language group and are suitable for use as the given
+ * CSS generic font.
+ *
+ * @param aLangGroup language group
+ * @param aGeneric CSS generic font
+ * @return Promise that resolves to Array
+ */
+ [implicit_jscontext]
+ jsval EnumerateFontsAsync(in string aLangGroup, in string aGeneric);
+
+ /**
+ @param aLangGroup language group
+ @return bool do we have a font for this language group
+ */
+ void HaveFontFor(in string aLangGroup, [retval] out boolean aResult);
+
+ /**
+ * @param aLangGroup language group
+ * @param aGeneric CSS generic font
+ * @return suggested default font for this language group and generic family
+ */
+ wstring getDefaultFont(in string aLangGroup, in string aGeneric);
+
+ /**
+ * update the global font list
+ * return true if font list is changed
+ */
+ boolean updateFontList();
+
+ /**
+ * get the standard family name on the system from given family
+ * @param aName family name which may be alias
+ * @return the standard family name on the system, if given name does not
+ * exist, returns empty string
+ */
+ wstring getStandardFamilyName(in wstring aName);
+};
diff --git a/gfx/src/nsITheme.h b/gfx/src/nsITheme.h
new file mode 100644
index 0000000000..fdda5e3b5a
--- /dev/null
+++ b/gfx/src/nsITheme.h
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* service providing platform-specific native rendering for widgets */
+
+#ifndef nsITheme_h_
+#define nsITheme_h_
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsISupports.h"
+#include "nsID.h"
+#include "nscore.h"
+#include "Units.h"
+
+struct nsRect;
+class gfxContext;
+class nsAttrValue;
+class nsPresContext;
+class nsDeviceContext;
+class nsIFrame;
+class nsAtom;
+class nsIWidget;
+
+namespace mozilla {
+class ComputedStyle;
+enum class StyleAppearance : uint8_t;
+namespace layers {
+class StackingContextHelper;
+class RenderRootStateManager;
+} // namespace layers
+namespace wr {
+class DisplayListBuilder;
+class IpcResourceUpdateQueue;
+} // namespace wr
+} // namespace mozilla
+
+// IID for the nsITheme interface
+// {7329f760-08cb-450f-8225-dae729096dec}
+#define NS_ITHEME_IID \
+ { \
+ 0x7329f760, 0x08cb, 0x450f, { \
+ 0x82, 0x25, 0xda, 0xe7, 0x29, 0x09, 0x6d, 0xec \
+ } \
+ }
+
+/**
+ * nsITheme is a service that provides platform-specific native
+ * rendering for widgets. In other words, it provides the necessary
+ * operations to draw a rendering object (an nsIFrame) as a native
+ * widget.
+ *
+ * All the methods on nsITheme take a rendering context or device
+ * context, a frame (the rendering object), and a widget type (one of
+ * the constants in nsThemeConstants.h).
+ */
+class nsITheme : public nsISupports {
+ protected:
+ using LayoutDeviceIntMargin = mozilla::LayoutDeviceIntMargin;
+ using StyleAppearance = mozilla::StyleAppearance;
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITHEME_IID)
+
+ /**
+ * Draw the actual theme background.
+ * @param aContext the context to draw into
+ * @param aFrame the frame for the widget that we're drawing
+ * @param aWidgetType the -moz-appearance value to draw
+ * @param aRect the rectangle defining the area occupied by the widget
+ * @param aDirtyRect the rectangle that needs to be drawn
+ */
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) = 0;
+
+ /**
+ * Create WebRender commands for the theme background.
+ * @return true if the theme knows how to create WebRender commands for the
+ * given widget type, false if DrawWidgetBackground need sto be called
+ * instead.
+ */
+ virtual bool CreateWebRenderCommandsForWidget(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aWidgetType, const nsRect& aRect) {
+ return false;
+ }
+
+ /**
+ * Return the border for the widget, in device pixels.
+ */
+ [[nodiscard]] virtual LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aWidgetType) = 0;
+
+ /**
+ * This method can return false to indicate that the CSS padding
+ * value should be used. Otherwise, it will fill in aResult with the
+ * computed padding, in pixels, and return true.
+ *
+ * XXXldb This ought to be required to return true for non-containers
+ * so that we don't let specified padding that has no effect change
+ * the computed padding and potentially the size.
+ */
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aWidgetType,
+ LayoutDeviceIntMargin* aResult) = 0;
+
+ /**
+ * On entry, *aResult is positioned at 0,0 and sized to the new size
+ * of aFrame (aFrame->GetSize() may be stale and should not be used).
+ * This method can return false to indicate that no special
+ * overflow area is required by the native widget. Otherwise it will
+ * fill in aResult with the desired overflow area, in appunits, relative
+ * to the frame origin, and return true.
+ *
+ * This overflow area is used to determine what area needs to be
+ * repainted when the widget changes. However, it does not affect the
+ * widget's size or what area is reachable by scrollbars. (In other
+ * words, in layout terms, it affects ink overflow but not
+ * scrollable overflow.)
+ */
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aWidgetType,
+ /*INOUT*/ nsRect* aOverflowRect) {
+ return false;
+ }
+
+ /**
+ * Get the minimum border-box size of a widget, in *pixels* (in
+ * |aResult|). If |aIsOverridable| is set to true, this size is a
+ * minimum size; if false, this size is the only valid size for the
+ * widget.
+ */
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) = 0;
+
+ enum Transparency { eOpaque = 0, eTransparent, eUnknownTransparency };
+
+ /**
+ * Returns what we know about the transparency of the widget.
+ */
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame,
+ StyleAppearance aWidgetType) {
+ return eUnknownTransparency;
+ }
+
+ /**
+ * Sets |*aShouldRepaint| to indicate whether an attribute or content state
+ * change should trigger a repaint. Call with null |aAttribute| (and
+ * null |aOldValue|) for content state changes.
+ */
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aWidgetType,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) = 0;
+
+ NS_IMETHOD ThemeChanged() = 0;
+
+ virtual bool WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aWidgetType) {
+ return false;
+ }
+
+ /**
+ * ThemeGeometryType values are used for describing themed nsIFrames in
+ * calls to nsIWidget::UpdateThemeGeometries. We don't simply pass the
+ * -moz-appearance value ("widget type") of the frame because the widget may
+ * want to treat different frames with the same -moz-appearance differently
+ * based on other properties of the frame. So we give the theme a first look
+ * at the frame in nsITheme::ThemeGeometryTypeForWidget and pass the
+ * returned ThemeGeometryType along to the widget.
+ * Each theme backend defines the ThemeGeometryType values it needs in its
+ * own nsITheme subclass. eThemeGeometryTypeUnknown is the only value that's
+ * shared between backends.
+ */
+ typedef uint8_t ThemeGeometryType;
+ enum { eThemeGeometryTypeUnknown = 0 };
+
+ /**
+ * Returns the theme geometry type that should be used in the ThemeGeometry
+ * array that's passed to the widget using nsIWidget::UpdateThemeGeometries.
+ * A return value of eThemeGeometryTypeUnknown means that this frame will
+ * not be included in the ThemeGeometry array.
+ */
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aWidgetType) {
+ return eThemeGeometryTypeUnknown;
+ }
+
+ /**
+ * Can the nsITheme implementation handle this widget?
+ */
+ virtual bool ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aWidgetType) = 0;
+
+ virtual bool WidgetIsContainer(StyleAppearance aWidgetType) = 0;
+
+ /**
+ * Does the nsITheme implementation draw its own focus ring for this widget?
+ */
+ virtual bool ThemeDrawsFocusForWidget(StyleAppearance aWidgetType) = 0;
+
+ /**
+ * Whether we want an inner focus ring for buttons and such.
+ *
+ * Usually, we don't want it if we have our own focus indicators, but windows
+ * is special, because it wants it even though focus also alters the border
+ * color and such.
+ */
+ virtual bool ThemeWantsButtonInnerFocusRing(StyleAppearance aAppearance) {
+ return !ThemeDrawsFocusForWidget(aAppearance);
+ }
+
+ /**
+ * Should we insert a dropmarker inside of combobox button?
+ */
+ virtual bool ThemeNeedsComboboxDropmarker() = 0;
+
+ virtual bool ThemeSupportsScrollbarButtons() { return true; }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITheme, NS_ITHEME_IID)
+
+// Singleton accessor functions, these should never return null.
+//
+// Do not use directly, use nsPresContext::Theme instead.
+extern already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly();
+extern already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly();
+
+#endif
diff --git a/gfx/src/nsMargin.h b/gfx/src/nsMargin.h
new file mode 100644
index 0000000000..cf05894ff3
--- /dev/null
+++ b/gfx/src/nsMargin.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef NSMARGIN_H
+#define NSMARGIN_H
+
+#include "nsCoord.h"
+#include "mozilla/gfx/BaseMargin.h"
+#include "mozilla/gfx/Rect.h"
+
+struct nsMargin : public mozilla::gfx::BaseMargin<nscoord, nsMargin> {
+ typedef mozilla::gfx::BaseMargin<nscoord, nsMargin> Super;
+
+ // Constructors
+ nsMargin() = default;
+ nsMargin(const nsMargin& aMargin) = default;
+ nsMargin(nscoord aTop, nscoord aRight, nscoord aBottom, nscoord aLeft)
+ : Super(aTop, aRight, aBottom, aLeft) {}
+};
+
+typedef mozilla::gfx::IntMargin nsIntMargin;
+
+#endif /* NSMARGIN_H */
diff --git a/gfx/src/nsPoint.h b/gfx/src/nsPoint.h
new file mode 100644
index 0000000000..86688d2142
--- /dev/null
+++ b/gfx/src/nsPoint.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef NSPOINT_H
+#define NSPOINT_H
+
+#include <cstdint>
+#include "nsCoord.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/Point.h"
+
+// nsIntPoint represents a point in one of the types of pixels.
+// Uses of nsIntPoint should eventually be converted to CSSIntPoint,
+// LayoutDeviceIntPoint, etc. (see layout/base/Units.h).
+typedef mozilla::gfx::IntPoint nsIntPoint;
+
+// nsPoint represents a point in app units.
+
+struct nsPoint : public mozilla::gfx::BasePoint<nscoord, nsPoint> {
+ typedef mozilla::gfx::BasePoint<nscoord, nsPoint> Super;
+
+ nsPoint() = default;
+ nsPoint(const nsPoint& aPoint) = default;
+ nsPoint(nscoord aX, nscoord aY) : Super(aX, aY) {}
+
+ inline nsIntPoint ScaleToNearestPixels(float aXScale, float aYScale,
+ nscoord aAppUnitsPerPixel) const;
+ inline nsIntPoint ToNearestPixels(nscoord aAppUnitsPerPixel) const;
+
+ /**
+ * Return this point scaled to a different appunits per pixel (APP) ratio.
+ * @param aFromAPP the APP to scale from
+ * @param aToAPP the APP to scale to
+ */
+ [[nodiscard]] inline nsPoint ScaleToOtherAppUnits(int32_t aFromAPP,
+ int32_t aToAPP) const;
+
+ [[nodiscard]] inline nsPoint RemoveResolution(const float resolution) const;
+ [[nodiscard]] inline nsPoint ApplyResolution(const float resolution) const;
+};
+
+inline nsPoint ToAppUnits(const nsIntPoint& aPoint, nscoord aAppUnitsPerPixel);
+
+inline nsIntPoint nsPoint::ScaleToNearestPixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
+ return nsIntPoint(
+ NSToIntRoundUp(NSAppUnitsToDoublePixels(x, aAppUnitsPerPixel) * aXScale),
+ NSToIntRoundUp(NSAppUnitsToDoublePixels(y, aAppUnitsPerPixel) * aYScale));
+}
+
+inline nsIntPoint nsPoint::ToNearestPixels(nscoord aAppUnitsPerPixel) const {
+ return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel);
+}
+
+inline nsPoint nsPoint::ScaleToOtherAppUnits(int32_t aFromAPP,
+ int32_t aToAPP) const {
+ if (aFromAPP != aToAPP) {
+ nsPoint point;
+ point.x = NSToCoordRound(NSCoordScale(x, aFromAPP, aToAPP));
+ point.y = NSToCoordRound(NSCoordScale(y, aFromAPP, aToAPP));
+ return point;
+ }
+ return *this;
+}
+
+inline nsPoint nsPoint::RemoveResolution(const float resolution) const {
+ if (resolution != 1.0f) {
+ nsPoint point;
+ point.x = NSToCoordRound(NSCoordToFloat(x) / resolution);
+ point.y = NSToCoordRound(NSCoordToFloat(y) / resolution);
+ return point;
+ }
+ return *this;
+}
+
+inline nsPoint nsPoint::ApplyResolution(const float resolution) const {
+ if (resolution != 1.0f) {
+ nsPoint point;
+ point.x = NSToCoordRound(NSCoordToFloat(x) * resolution);
+ point.y = NSToCoordRound(NSCoordToFloat(y) * resolution);
+ return point;
+ }
+ return *this;
+}
+
+// app units are integer multiples of pixels, so no rounding needed
+inline nsPoint ToAppUnits(const nsIntPoint& aPoint, nscoord aAppUnitsPerPixel) {
+ return nsPoint(NSIntPixelsToAppUnits(aPoint.x, aAppUnitsPerPixel),
+ NSIntPixelsToAppUnits(aPoint.y, aAppUnitsPerPixel));
+}
+
+#endif /* NSPOINT_H */
diff --git a/gfx/src/nsRect.cpp b/gfx/src/nsRect.cpp
new file mode 100644
index 0000000000..74ffbf0fb2
--- /dev/null
+++ b/gfx/src/nsRect.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRect.h"
+#include "mozilla/gfx/Types.h" // for eSideBottom, etc
+#include "mozilla/CheckedInt.h" // for CheckedInt
+#include "nsDeviceContext.h" // for nsDeviceContext
+#include "nsString.h" // for nsAutoString, etc
+#include "nsMargin.h" // for nsMargin
+
+static_assert(
+ (int(mozilla::eSideTop) == 0) && (int(mozilla::eSideRight) == 1) &&
+ (int(mozilla::eSideBottom) == 2) && (int(mozilla::eSideLeft) == 3),
+ "The mozilla::Side sequence must match the nsMargin nscoord sequence");
+
+const mozilla::gfx::IntRect& GetMaxSizedIntRect() {
+ static const mozilla::gfx::IntRect r(0, 0, INT32_MAX, INT32_MAX);
+ return r;
+}
+
+bool nsRect::Overflows() const {
+#ifdef NS_COORD_IS_FLOAT
+ return false;
+#else
+ mozilla::CheckedInt<int32_t> xMost = this->x;
+ xMost += this->width;
+ mozilla::CheckedInt<int32_t> yMost = this->y;
+ yMost += this->height;
+ return !xMost.isValid() || !yMost.isValid();
+#endif
+}
diff --git a/gfx/src/nsRect.h b/gfx/src/nsRect.h
new file mode 100644
index 0000000000..4119b7b83d
--- /dev/null
+++ b/gfx/src/nsRect.h
@@ -0,0 +1,493 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef NSRECT_H
+#define NSRECT_H
+
+#include <stdint.h> // for int32_t, int64_t
+#include <algorithm> // for min/max
+#include "mozilla/Likely.h" // for MOZ_UNLIKELY
+#include "mozilla/gfx/BaseRect.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsCoord.h" // for nscoord, etc
+#include "nsISupports.h" // for MOZ_COUNT_CTOR, etc
+#include "nsPoint.h" // for nsIntPoint, nsPoint
+#include "nsSize.h" // for IntSize, nsSize
+#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
+ (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
+# if defined(_MSC_VER) && !defined(__clang__)
+# include "smmintrin.h"
+# else
+# include "emmintrin.h"
+# endif
+#endif
+
+struct nsMargin;
+
+typedef mozilla::gfx::IntRect nsIntRect;
+
+struct nsRect : public mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize,
+ nsMargin> {
+ typedef mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin>
+ Super;
+
+ static void VERIFY_COORD(nscoord aValue) { ::VERIFY_COORD(aValue); }
+
+ // Constructors
+ nsRect() : Super() { MOZ_COUNT_CTOR(nsRect); }
+ nsRect(const nsRect& aRect) : Super(aRect) { MOZ_COUNT_CTOR(nsRect); }
+ nsRect(const nsPoint& aOrigin, const nsSize& aSize) : Super(aOrigin, aSize) {
+ MOZ_COUNT_CTOR(nsRect);
+ }
+ nsRect(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight)
+ : Super(aX, aY, aWidth, aHeight) {
+ MOZ_COUNT_CTOR(nsRect);
+ }
+ nsRect& operator=(const nsRect&) = default;
+
+ MOZ_COUNTED_DTOR(nsRect)
+
+ // We have saturating versions of all the Union methods. These avoid
+ // overflowing nscoord values in the 'width' and 'height' fields by
+ // clamping the width and height values to nscoord_MAX if necessary.
+
+ [[nodiscard]] nsRect SaturatingUnion(const nsRect& aRect) const {
+ if (IsEmpty()) {
+ return aRect;
+ } else if (aRect.IsEmpty()) {
+ return *static_cast<const nsRect*>(this);
+ } else {
+ return SaturatingUnionEdges(aRect);
+ }
+ }
+
+ [[nodiscard]] nsRect SaturatingUnionEdges(const nsRect& aRect) const {
+#ifdef NS_COORD_IS_FLOAT
+ return UnionEdges(aRect);
+#else
+ nscoord resultX = std::min(aRect.X(), x);
+ int64_t w =
+ std::max(int64_t(aRect.X()) + aRect.Width(), int64_t(x) + width) -
+ resultX;
+ if (MOZ_UNLIKELY(w > nscoord_MAX)) {
+ // Clamp huge negative x to nscoord_MIN / 2 and try again.
+ resultX = std::max(resultX, nscoord_MIN / 2);
+ w = std::max(int64_t(aRect.X()) + aRect.Width(), int64_t(x) + width) -
+ resultX;
+ if (MOZ_UNLIKELY(w > nscoord_MAX)) {
+ w = nscoord_MAX;
+ }
+ }
+
+ nscoord resultY = std::min(aRect.y, y);
+ int64_t h =
+ std::max(int64_t(aRect.Y()) + aRect.Height(), int64_t(y) + height) -
+ resultY;
+ if (MOZ_UNLIKELY(h > nscoord_MAX)) {
+ // Clamp huge negative y to nscoord_MIN / 2 and try again.
+ resultY = std::max(resultY, nscoord_MIN / 2);
+ h = std::max(int64_t(aRect.Y()) + aRect.Height(), int64_t(y) + height) -
+ resultY;
+ if (MOZ_UNLIKELY(h > nscoord_MAX)) {
+ h = nscoord_MAX;
+ }
+ }
+ return nsRect(resultX, resultY, nscoord(w), nscoord(h));
+#endif
+ }
+
+#ifndef NS_COORD_IS_FLOAT
+ // Make all nsRect Union methods be saturating.
+ [[nodiscard]] nsRect UnionEdges(const nsRect& aRect) const {
+ return SaturatingUnionEdges(aRect);
+ }
+ void UnionRectEdges(const nsRect& aRect1, const nsRect& aRect2) {
+ *this = aRect1.UnionEdges(aRect2);
+ }
+ [[nodiscard]] nsRect Union(const nsRect& aRect) const {
+ return SaturatingUnion(aRect);
+ }
+ [[nodiscard]] nsRect UnsafeUnion(const nsRect& aRect) const {
+ return Super::Union(aRect);
+ }
+ void UnionRect(const nsRect& aRect1, const nsRect& aRect2) {
+ *this = aRect1.Union(aRect2);
+ }
+
+# if defined(_MSC_VER) && !defined(__clang__) && \
+ (defined(_M_X64) || defined(_M_IX86))
+ // Only MSVC supports inlining intrinsics for archs you're not compiling for.
+ [[nodiscard]] nsRect Intersect(const nsRect& aRect) const {
+ nsRect result;
+ if (mozilla::gfx::Factory::HasSSE4()) {
+ __m128i rect1 = _mm_loadu_si128((__m128i*)&aRect); // x1, y1, w1, h1
+ __m128i rect2 = _mm_loadu_si128((__m128i*)this); // x2, y2, w2, h2
+
+ __m128i resultRect = _mm_max_epi32(rect1, rect2); // xr, yr, zz, zz
+
+ // result.width = std::min<int32_t>(x - result.x + width,
+ // aRect.x - result.x + aRect.width);
+ // result.height = std::min<int32_t>(y - result.y + height,
+ // aRect.y - result.y + aRect.height);
+ __m128i widthheight = _mm_min_epi32(
+ _mm_add_epi32(_mm_sub_epi32(rect1, resultRect),
+ _mm_srli_si128(rect1, 8)),
+ _mm_add_epi32(_mm_sub_epi32(rect2, resultRect),
+ _mm_srli_si128(rect2, 8))); // w, h, zz, zz
+ widthheight = _mm_slli_si128(widthheight, 8); // 00, 00, wr, hr
+
+ resultRect =
+ _mm_blend_epi16(resultRect, widthheight, 0xF0); // xr, yr, wr, hr
+
+ if ((_mm_movemask_ps(_mm_castsi128_ps(
+ _mm_cmplt_epi32(resultRect, _mm_setzero_si128()))) &
+ 0xC) != 0) {
+ // It's potentially more efficient to store all 0s. But the non SSE4
+ // code leaves x/y intact so let's do the same here.
+ resultRect = _mm_and_si128(resultRect,
+ _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
+ }
+
+ _mm_storeu_si128((__m128i*)&result, resultRect);
+
+ return result;
+ }
+
+ result.x = std::max<int32_t>(x, aRect.x);
+ result.y = std::max<int32_t>(y, aRect.y);
+ result.width = std::min<int32_t>(x - result.x + width,
+ aRect.x - result.x + aRect.width);
+ result.height = std::min<int32_t>(y - result.y + height,
+ aRect.y - result.y + aRect.height);
+ if (result.width < 0 || result.height < 0) {
+ result.SizeTo(0, 0);
+ }
+ return result;
+ }
+
+ bool IntersectRect(const nsRect& aRect1, const nsRect& aRect2) {
+ if (mozilla::gfx::Factory::HasSSE4()) {
+ __m128i rect1 = _mm_loadu_si128((__m128i*)&aRect1); // x1, y1, w1, h1
+ __m128i rect2 = _mm_loadu_si128((__m128i*)&aRect2); // x2, y2, w2, h2
+
+ __m128i resultRect = _mm_max_epi32(rect1, rect2); // xr, yr, zz, zz
+ // result.width = std::min<int32_t>(x - result.x + width,
+ // aRect.x - result.x + aRect.width);
+ // result.height = std::min<int32_t>(y - result.y + height,
+ // aRect.y - result.y + aRect.height);
+ __m128i widthheight = _mm_min_epi32(
+ _mm_add_epi32(_mm_sub_epi32(rect1, resultRect),
+ _mm_srli_si128(rect1, 8)),
+ _mm_add_epi32(_mm_sub_epi32(rect2, resultRect),
+ _mm_srli_si128(rect2, 8))); // w, h, zz, zz
+ widthheight = _mm_slli_si128(widthheight, 8); // 00, 00, wr, hr
+
+ resultRect =
+ _mm_blend_epi16(resultRect, widthheight, 0xF0); // xr, yr, wr, hr
+
+ if ((_mm_movemask_ps(_mm_castsi128_ps(
+ _mm_cmpgt_epi32(resultRect, _mm_setzero_si128()))) &
+ 0xC) != 0xC) {
+ // It's potentially more efficient to store all 0s. But the non SSE4
+ // code leaves x/y intact so let's do the same here.
+ resultRect = _mm_and_si128(resultRect,
+ _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
+ _mm_storeu_si128((__m128i*)this, resultRect);
+ return false;
+ }
+
+ _mm_storeu_si128((__m128i*)this, resultRect);
+
+ return true;
+ }
+
+ int32_t newX = std::max<int32_t>(aRect1.x, aRect2.x);
+ int32_t newY = std::max<int32_t>(aRect1.y, aRect2.y);
+ width = std::min<int32_t>(aRect1.x - newX + aRect1.width,
+ aRect2.x - newX + aRect2.width);
+ height = std::min<int32_t>(aRect1.y - newY + aRect1.height,
+ aRect2.y - newY + aRect2.height);
+ x = newX;
+ y = newY;
+ if (width <= 0 || height <= 0) {
+ SizeTo(0, 0);
+ return false;
+ }
+ return true;
+ }
+# endif
+#endif
+
+ void SaturatingUnionRect(const nsRect& aRect1, const nsRect& aRect2) {
+ *this = aRect1.SaturatingUnion(aRect2);
+ }
+ void SaturatingUnionRectEdges(const nsRect& aRect1, const nsRect& aRect2) {
+ *this = aRect1.SaturatingUnionEdges(aRect2);
+ }
+
+ // Return whether this rect's right or bottom edge overflow int32.
+ bool Overflows() const;
+
+ /**
+ * Return this rect scaled to a different appunits per pixel (APP) ratio.
+ * In the RoundOut version we make the rect the smallest rect containing the
+ * unrounded result. In the RoundIn version we make the rect the largest rect
+ * contained in the unrounded result.
+ * @param aFromAPP the APP to scale from
+ * @param aToAPP the APP to scale to
+ * @note this can turn an empty rectangle into a non-empty rectangle
+ */
+ [[nodiscard]] inline nsRect ScaleToOtherAppUnitsRoundOut(
+ int32_t aFromAPP, int32_t aToAPP) const;
+ [[nodiscard]] inline nsRect ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
+ int32_t aToAPP) const;
+
+ [[nodiscard]] inline mozilla::gfx::IntRect ScaleToNearestPixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
+
+ [[nodiscard]] inline mozilla::gfx::IntRect ToNearestPixels(
+ nscoord aAppUnitsPerPixel) const;
+
+ // Note: this can turn an empty rectangle into a non-empty rectangle
+ [[nodiscard]] inline mozilla::gfx::IntRect ScaleToOutsidePixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
+
+ // Note: this can turn an empty rectangle into a non-empty rectangle
+ [[nodiscard]] inline mozilla::gfx::IntRect ToOutsidePixels(
+ nscoord aAppUnitsPerPixel) const;
+
+ [[nodiscard]] inline mozilla::gfx::IntRect ScaleToInsidePixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
+
+ [[nodiscard]] inline mozilla::gfx::IntRect ToInsidePixels(
+ nscoord aAppUnitsPerPixel) const;
+
+ // This is here only to keep IPDL-generated code happy. DO NOT USE.
+ bool operator==(const nsRect& aRect) const { return IsEqualEdges(aRect); }
+
+ [[nodiscard]] inline nsRect RemoveResolution(const float aResolution) const;
+};
+
+/*
+ * App Unit/Pixel conversions
+ */
+
+inline nsRect nsRect::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
+ int32_t aToAPP) const {
+ if (aFromAPP == aToAPP) {
+ return *this;
+ }
+
+ nsRect rect;
+ rect.SetBox(NSToCoordFloor(NSCoordScale(x, aFromAPP, aToAPP)),
+ NSToCoordFloor(NSCoordScale(y, aFromAPP, aToAPP)),
+ NSToCoordCeil(NSCoordScale(XMost(), aFromAPP, aToAPP)),
+ NSToCoordCeil(NSCoordScale(YMost(), aFromAPP, aToAPP)));
+ return rect;
+}
+
+inline nsRect nsRect::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
+ int32_t aToAPP) const {
+ if (aFromAPP == aToAPP) {
+ return *this;
+ }
+
+ nsRect rect;
+ rect.SetBox(NSToCoordCeil(NSCoordScale(x, aFromAPP, aToAPP)),
+ NSToCoordCeil(NSCoordScale(y, aFromAPP, aToAPP)),
+ NSToCoordFloor(NSCoordScale(XMost(), aFromAPP, aToAPP)),
+ NSToCoordFloor(NSCoordScale(YMost(), aFromAPP, aToAPP)));
+ return rect;
+}
+
+#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
+ (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
+// Life would be so much better if we had SSE4 here.
+static MOZ_ALWAYS_INLINE __m128i floor_ps2epi32(__m128 x) {
+ __m128 one = _mm_set_ps(1.0f, 1.0f, 1.0f, 1.0f);
+
+ __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(x));
+ __m128 r = _mm_sub_ps(t, _mm_and_ps(_mm_cmplt_ps(x, t), one));
+
+ return _mm_cvttps_epi32(r);
+}
+
+static MOZ_ALWAYS_INLINE __m128i ceil_ps2epi32(__m128 x) {
+ __m128 t = _mm_sub_ps(_mm_setzero_ps(), x);
+ __m128i r = _mm_sub_epi32(_mm_setzero_si128(), floor_ps2epi32(t));
+
+ return r;
+}
+#endif
+
+// scale the rect but round to preserve centers
+inline mozilla::gfx::IntRect nsRect::ScaleToNearestPixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
+ mozilla::gfx::IntRect rect;
+ // Android x86 builds have bindgen issues.
+#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
+ (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
+ __m128 appUnitsPacked = _mm_set_ps(aAppUnitsPerPixel, aAppUnitsPerPixel,
+ aAppUnitsPerPixel, aAppUnitsPerPixel);
+ __m128 scalesPacked = _mm_set_ps(aYScale, aXScale, aYScale, aXScale);
+ __m128 biasesPacked = _mm_set_ps(0.5f, 0.5f, 0.5f, 0.5f);
+
+ __m128i rectPacked = _mm_loadu_si128((__m128i*)this);
+ __m128i topLeft = _mm_slli_si128(rectPacked, 8);
+
+ rectPacked = _mm_add_epi32(rectPacked, topLeft); // X, Y, XMost(), YMost()
+
+ __m128 rectFloat = _mm_cvtepi32_ps(rectPacked);
+
+ // Scale, i.e. ([ x y xmost ymost ] / aAppUnitsPerPixel) * [ aXScale aYScale
+ // aXScale aYScale ]
+ rectFloat = _mm_mul_ps(_mm_div_ps(rectFloat, appUnitsPacked), scalesPacked);
+
+ // Floor
+ // Executed with bias and roundmode down, since round-nearest rounds 0.5
+ // downward half the time.
+ rectFloat = _mm_add_ps(rectFloat, biasesPacked);
+ rectPacked = floor_ps2epi32(rectFloat);
+
+ topLeft = _mm_slli_si128(rectPacked, 8);
+ rectPacked = _mm_sub_epi32(rectPacked, topLeft); // X, Y, Width, Height
+
+ // Avoid negative width/height due to overflow.
+ __m128i mask = _mm_or_si128(_mm_cmpgt_epi32(rectPacked, _mm_setzero_si128()),
+ _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
+ // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF)
+ // (height <= 0 ? 0 : 0xFFFFFFFF) ]
+ rectPacked = _mm_and_si128(rectPacked, mask);
+
+ _mm_storeu_si128((__m128i*)&rect, rectPacked);
+#else
+ rect.SetNonEmptyBox(
+ NSToIntRoundUp(NSAppUnitsToFloatPixels(x, aAppUnitsPerPixel) * aXScale),
+ NSToIntRoundUp(NSAppUnitsToFloatPixels(y, aAppUnitsPerPixel) * aYScale),
+ NSToIntRoundUp(NSAppUnitsToFloatPixels(XMost(), aAppUnitsPerPixel) *
+ aXScale),
+ NSToIntRoundUp(NSAppUnitsToFloatPixels(YMost(), aAppUnitsPerPixel) *
+ aYScale));
+#endif
+ return rect;
+}
+
+// scale the rect but round to smallest containing rect
+inline mozilla::gfx::IntRect nsRect::ScaleToOutsidePixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
+ mozilla::gfx::IntRect rect;
+ // Android x86 builds have bindgen issues.
+#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \
+ (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
+ __m128 appUnitsPacked = _mm_set_ps(aAppUnitsPerPixel, aAppUnitsPerPixel,
+ aAppUnitsPerPixel, aAppUnitsPerPixel);
+ __m128 scalesPacked = _mm_set_ps(aYScale, aXScale, aYScale, aXScale);
+
+ __m128i rectPacked = _mm_loadu_si128((__m128i*)this); // x, y, w, h
+ __m128i topLeft = _mm_slli_si128(rectPacked, 8); // 0, 0, x, y
+
+ rectPacked = _mm_add_epi32(rectPacked, topLeft); // X, Y, XMost(), YMost()
+
+ __m128 rectFloat = _mm_cvtepi32_ps(rectPacked);
+
+ // Scale i.e. ([ x y xmost ymost ] / aAppUnitsPerPixel) *
+ // [ aXScale aYScale aXScale aYScale ]
+ rectFloat = _mm_mul_ps(_mm_div_ps(rectFloat, appUnitsPacked), scalesPacked);
+ rectPacked = ceil_ps2epi32(rectFloat); // xx, xx, XMost(), YMost()
+ __m128i tmp = floor_ps2epi32(rectFloat); // x, y, xx, xx
+
+ // _mm_move_sd is 1 cycle method of getting the blending we want.
+ rectPacked = _mm_castpd_si128(
+ _mm_move_sd(_mm_castsi128_pd(rectPacked),
+ _mm_castsi128_pd(tmp))); // x, y, XMost(), YMost()
+
+ topLeft = _mm_slli_si128(rectPacked, 8); // 0, 0, r.x, r.y
+ rectPacked = _mm_sub_epi32(rectPacked, topLeft); // r.x, r.y, r.w, r.h
+
+ // Avoid negative width/height due to overflow.
+ __m128i mask = _mm_or_si128(_mm_cmpgt_epi32(rectPacked, _mm_setzero_si128()),
+ _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
+ // clang-format off
+ // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF) (height <= 0 ? 0 : 0xFFFFFFFF) ]
+ // clang-format on
+ rectPacked = _mm_and_si128(rectPacked, mask);
+
+ _mm_storeu_si128((__m128i*)&rect, rectPacked);
+#else
+ rect.SetNonEmptyBox(
+ NSToIntFloor(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) *
+ aXScale),
+ NSToIntFloor(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) *
+ aYScale),
+ NSToIntCeil(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel)) *
+ aXScale),
+ NSToIntCeil(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel)) *
+ aYScale));
+#endif
+ return rect;
+}
+
+// scale the rect but round to largest contained rect
+inline mozilla::gfx::IntRect nsRect::ScaleToInsidePixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
+ mozilla::gfx::IntRect rect;
+ rect.SetNonEmptyBox(
+ NSToIntCeil(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) *
+ aXScale),
+ NSToIntCeil(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) *
+ aYScale),
+ NSToIntFloor(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel)) *
+ aXScale),
+ NSToIntFloor(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel)) *
+ aYScale));
+ return rect;
+}
+
+inline mozilla::gfx::IntRect nsRect::ToNearestPixels(
+ nscoord aAppUnitsPerPixel) const {
+ return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel);
+}
+
+inline mozilla::gfx::IntRect nsRect::ToOutsidePixels(
+ nscoord aAppUnitsPerPixel) const {
+ return ScaleToOutsidePixels(1.0f, 1.0f, aAppUnitsPerPixel);
+}
+
+inline mozilla::gfx::IntRect nsRect::ToInsidePixels(
+ nscoord aAppUnitsPerPixel) const {
+ return ScaleToInsidePixels(1.0f, 1.0f, aAppUnitsPerPixel);
+}
+
+inline nsRect nsRect::RemoveResolution(const float aResolution) const {
+ MOZ_ASSERT(aResolution > 0.0f);
+ nsRect rect;
+ rect.MoveTo(NSToCoordRound(NSCoordToFloat(x) / aResolution),
+ NSToCoordRound(NSCoordToFloat(y) / aResolution));
+ // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1
+ // rect as well instead of possibly rounding the width or height to zero.
+ if (width == 1 && height == 1) {
+ rect.SizeTo(1, 1);
+ } else {
+ rect.SizeTo(NSToCoordCeil(NSCoordToFloat(width) / aResolution),
+ NSToCoordCeil(NSCoordToFloat(height) / aResolution));
+ }
+
+ return rect;
+}
+
+const mozilla::gfx::IntRect& GetMaxSizedIntRect();
+
+// app units are integer multiples of pixels, so no rounding needed
+template <class units>
+nsRect ToAppUnits(const mozilla::gfx::IntRectTyped<units>& aRect,
+ nscoord aAppUnitsPerPixel) {
+ return nsRect(NSIntPixelsToAppUnits(aRect.X(), aAppUnitsPerPixel),
+ NSIntPixelsToAppUnits(aRect.Y(), aAppUnitsPerPixel),
+ NSIntPixelsToAppUnits(aRect.Width(), aAppUnitsPerPixel),
+ NSIntPixelsToAppUnits(aRect.Height(), aAppUnitsPerPixel));
+}
+
+#endif /* NSRECT_H */
diff --git a/gfx/src/nsRectAbsolute.h b/gfx/src/nsRectAbsolute.h
new file mode 100644
index 0000000000..2ebedbffd7
--- /dev/null
+++ b/gfx/src/nsRectAbsolute.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef NSRECTABSOLUTE_H
+#define NSRECTABSOLUTE_H
+
+#include "mozilla/gfx/RectAbsolute.h"
+#include "nsCoord.h"
+#include "nsMargin.h"
+#include "nsRect.h"
+
+struct nsRectAbsolute
+ : public mozilla::gfx::BaseRectAbsolute<nscoord, nsRectAbsolute, nsPoint,
+ nsRect> {
+ typedef mozilla::gfx::BaseRectAbsolute<nscoord, nsRectAbsolute, nsPoint,
+ nsRect>
+ Super;
+
+ nsRectAbsolute() : Super() {}
+ nsRectAbsolute(nscoord aX1, nscoord aY1, nscoord aX2, nscoord aY2)
+ : Super(aX1, aY1, aX2, aY2) {}
+
+ MOZ_ALWAYS_INLINE nscoord SafeWidth() const {
+ int64_t width = right;
+ width -= left;
+ return nscoord(
+ std::min<int64_t>(std::numeric_limits<nscoord>::max(), width));
+ }
+ MOZ_ALWAYS_INLINE nscoord SafeHeight() const {
+ int64_t height = bottom;
+ height -= top;
+ return nscoord(
+ std::min<int64_t>(std::numeric_limits<nscoord>::max(), height));
+ }
+
+ nsRect ToNSRect() const {
+ return nsRect(left, top, nscoord(SafeWidth()), nscoord(SafeHeight()));
+ }
+
+ [[nodiscard]] nsRectAbsolute UnsafeUnion(const nsRectAbsolute& aRect) const {
+ return Super::Union(aRect);
+ }
+
+ void Inflate(const nsMargin& aMargin) {
+ left -= aMargin.left;
+ top -= aMargin.top;
+ right += aMargin.right;
+ bottom += aMargin.bottom;
+ }
+};
+
+#endif /* NSRECTABSOLUTE_H */
diff --git a/gfx/src/nsRegion.cpp b/gfx/src/nsRegion.cpp
new file mode 100644
index 0000000000..aa50d44fc4
--- /dev/null
+++ b/gfx/src/nsRegion.cpp
@@ -0,0 +1,1024 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRegion.h"
+#include "nsTArray.h"
+#include "gfxUtils.h"
+#include "gfx2DGlue.h"
+#include "mozilla/ToString.h"
+
+void nsRegion::AssertStateInternal() const {
+ bool failed = false;
+ // Verify consistent state inside the region.
+ int32_t lastY = INT32_MIN;
+ int32_t lowestX = INT32_MAX;
+ int32_t highestX = INT32_MIN;
+ for (auto iter = mBands.begin(); iter != mBands.end(); iter++) {
+ const Band& band = *iter;
+ if (band.bottom <= band.top) {
+ failed = true;
+ break;
+ }
+ if (band.top < lastY) {
+ failed = true;
+ break;
+ }
+ lastY = band.bottom;
+
+ lowestX = std::min(lowestX, band.mStrips.begin()->left);
+ highestX = std::max(highestX, band.mStrips.LastElement().right);
+
+ int32_t lastX = INT32_MIN;
+ if (iter != mBands.begin()) {
+ auto prev = iter;
+ prev--;
+
+ if (prev->bottom == iter->top) {
+ if (band.EqualStrips(*prev)) {
+ failed = true;
+ break;
+ }
+ }
+ }
+ for (const Strip& strip : band.mStrips) {
+ if (strip.right <= strip.left) {
+ failed = true;
+ break;
+ }
+ if (strip.left <= lastX) {
+ failed = true;
+ break;
+ }
+ lastX = strip.right;
+ }
+ if (failed) {
+ break;
+ }
+ }
+
+ if (!(mBounds.IsEqualEdges(CalculateBounds()))) {
+ failed = true;
+ }
+
+ if (failed) {
+#ifdef DEBUG_REGIONS
+ if (mCurrentOpGenerator) {
+ mCurrentOpGenerator->OutputOp();
+ }
+#endif
+ MOZ_ASSERT(false);
+ }
+}
+
+bool nsRegion::Contains(const nsRegion& aRgn) const {
+ // XXX this could be made faster by iterating over
+ // both regions at the same time some how
+ for (auto iter = aRgn.RectIter(); !iter.Done(); iter.Next()) {
+ if (!Contains(iter.Get())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool nsRegion::Intersects(const nsRectAbsolute& aRect) const {
+ if (mBands.IsEmpty()) {
+ return mBounds.Intersects(aRect);
+ }
+
+ if (!mBounds.Intersects(aRect)) {
+ return false;
+ }
+
+ Strip rectStrip(aRect.X(), aRect.XMost());
+
+ auto iter = mBands.begin();
+ while (iter != mBands.end()) {
+ if (iter->top >= aRect.YMost()) {
+ return false;
+ }
+
+ if (iter->bottom <= aRect.Y()) {
+ // This band is entirely before aRect, move on.
+ iter++;
+ continue;
+ }
+
+ if (!iter->Intersects(rectStrip)) {
+ // This band does not intersect aRect horizontally. Move on.
+ iter++;
+ continue;
+ }
+
+ // This band intersects with aRect.
+ return true;
+ }
+
+ return false;
+}
+
+void nsRegion::Inflate(const nsMargin& aMargin) {
+ nsRegion newRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRectAbsolute rect = iter.GetAbsolute();
+ rect.Inflate(aMargin);
+ newRegion.AddRect(rect);
+ }
+
+ *this = std::move(newRegion);
+}
+
+void nsRegion::SimplifyOutward(uint32_t aMaxRects) {
+ MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count");
+
+ if (GetNumRects() <= aMaxRects) {
+ return;
+ }
+
+ // Try combining rects in horizontal bands into a single rect
+ // The goal here is to try to keep groups of rectangles that are vertically
+ // discontiguous as separate rectangles in the final region. This is
+ // simple and fast to implement and page contents tend to vary more
+ // vertically than horizontally (which is why our rectangles are stored
+ // sorted by y-coordinate, too).
+ //
+ // Note: if boxes share y1 because of the canonical representation they
+ // will share y2
+
+ size_t idx = 0;
+
+ while (idx < mBands.Length()) {
+ size_t oldIdx = idx;
+ mBands[idx].mStrips.begin()->right =
+ mBands[idx].mStrips.LastElement().right;
+ mBands[idx].mStrips.TruncateLength(1);
+ idx++;
+
+ // Merge any bands with the same bounds.
+ while (idx < mBands.Length() &&
+ mBands[idx].mStrips.begin()->left ==
+ mBands[oldIdx].mStrips.begin()->left &&
+ mBands[idx].mStrips.LastElement().right ==
+ mBands[oldIdx].mStrips.begin()->right) {
+ mBands[oldIdx].bottom = mBands[idx].bottom;
+ mBands.RemoveElementAt(idx);
+ }
+ }
+
+ AssertState();
+
+ // mBands.size() is now equal to our rect count.
+ if (mBands.Length() > aMaxRects) {
+ *this = GetBounds();
+ }
+}
+
+// compute the covered area difference between two rows.
+// by iterating over both rows simultaneously and adding up
+// the additional increase in area caused by extending each
+// of the rectangles to the combined height of both rows
+uint32_t nsRegion::ComputeMergedAreaIncrease(const Band& aTopBand,
+ const Band& aBottomBand) {
+ uint32_t totalArea = 0;
+
+ uint32_t topHeight = aBottomBand.top - aTopBand.top;
+ uint32_t bottomHeight = aBottomBand.bottom - aTopBand.bottom;
+ uint32_t currentStripBottom = 0;
+
+ // This could be done with slightly better worse case performance by merging
+ // these two for-loops, but this makes the code a lot easier to understand.
+ for (auto& strip : aTopBand.mStrips) {
+ if (currentStripBottom == aBottomBand.mStrips.Length() ||
+ strip.right < aBottomBand.mStrips[currentStripBottom].left) {
+ totalArea += bottomHeight * strip.Size();
+ continue;
+ }
+
+ int32_t currentX = strip.left;
+ while (currentStripBottom != aBottomBand.mStrips.Length() &&
+ aBottomBand.mStrips[currentStripBottom].left < strip.right) {
+ if (currentX >= strip.right) {
+ break;
+ }
+ if (currentX < aBottomBand.mStrips[currentStripBottom].left) {
+ // Add the part that's not intersecting.
+ totalArea += (aBottomBand.mStrips[currentStripBottom].left - currentX) *
+ bottomHeight;
+ }
+
+ currentX =
+ std::max(aBottomBand.mStrips[currentStripBottom].right, currentX);
+ currentStripBottom++;
+ }
+
+ // Add remainder of this strip.
+ if (currentX < strip.right) {
+ totalArea += (strip.right - currentX) * bottomHeight;
+ }
+ if (currentStripBottom) {
+ currentStripBottom--;
+ }
+ }
+ uint32_t currentStripTop = 0;
+ for (auto& strip : aBottomBand.mStrips) {
+ if (currentStripTop == aTopBand.mStrips.Length() ||
+ strip.right < aTopBand.mStrips[currentStripTop].left) {
+ totalArea += topHeight * strip.Size();
+ continue;
+ }
+
+ int32_t currentX = strip.left;
+ while (currentStripTop != aTopBand.mStrips.Length() &&
+ aTopBand.mStrips[currentStripTop].left < strip.right) {
+ if (currentX >= strip.right) {
+ break;
+ }
+ if (currentX < aTopBand.mStrips[currentStripTop].left) {
+ // Add the part that's not intersecting.
+ totalArea +=
+ (aTopBand.mStrips[currentStripTop].left - currentX) * topHeight;
+ }
+
+ currentX = std::max(aTopBand.mStrips[currentStripTop].right, currentX);
+ currentStripTop++;
+ }
+
+ // Add remainder of this strip.
+ if (currentX < strip.right) {
+ totalArea += (strip.right - currentX) * topHeight;
+ }
+ if (currentStripTop) {
+ currentStripTop--;
+ }
+ }
+ return totalArea;
+}
+
+void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) {
+ if (mBands.Length() < 2) {
+ // We have only one or no row and we're done.
+ return;
+ }
+
+ uint32_t currentBand = 0;
+ do {
+ Band& band = mBands[currentBand];
+
+ uint32_t totalArea =
+ ComputeMergedAreaIncrease(band, mBands[currentBand + 1]);
+
+ if (totalArea <= aThreshold) {
+ for (Strip& strip : mBands[currentBand + 1].mStrips) {
+ // This could use an optimized function to merge two bands.
+ band.InsertStrip(strip);
+ }
+ band.bottom = mBands[currentBand + 1].bottom;
+ mBands.RemoveElementAt(currentBand + 1);
+ } else {
+ currentBand++;
+ }
+ } while (currentBand + 1 < mBands.Length());
+
+ EnsureSimplified();
+ AssertState();
+}
+
+typedef void (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2,
+ int y2);
+
+void nsRegion::VisitEdges(visit_fn visit, void* closure) const {
+ if (mBands.IsEmpty()) {
+ visit(closure, VisitSide::LEFT, mBounds.X(), mBounds.Y(), mBounds.X(),
+ mBounds.YMost());
+ visit(closure, VisitSide::RIGHT, mBounds.XMost(), mBounds.Y(),
+ mBounds.XMost(), mBounds.YMost());
+ visit(closure, VisitSide::TOP, mBounds.X() - 1, mBounds.Y(),
+ mBounds.XMost() + 1, mBounds.Y());
+ visit(closure, VisitSide::BOTTOM, mBounds.X() - 1, mBounds.YMost(),
+ mBounds.XMost() + 1, mBounds.YMost());
+ return;
+ }
+
+ auto band = std::begin(mBands);
+ auto bandFinal = std::end(mBands);
+ bandFinal--;
+ for (const Strip& strip : band->mStrips) {
+ visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left,
+ band->bottom);
+ visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right,
+ band->bottom);
+ visit(closure, VisitSide::TOP, strip.left - 1, band->top, strip.right + 1,
+ band->top);
+ }
+
+ if (band != bandFinal) {
+ do {
+ const Band& topBand = *band;
+ band++;
+
+ for (const Strip& strip : band->mStrips) {
+ visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left,
+ band->bottom);
+ visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right,
+ band->bottom);
+ }
+
+ if (band->top == topBand.bottom) {
+ // Two bands touching each other vertically.
+ const Band& bottomBand = *band;
+ auto topStrip = std::begin(topBand.mStrips);
+ auto bottomStrip = std::begin(bottomBand.mStrips);
+
+ int y = topBand.bottom;
+
+ // State from this point on along the vertical edge:
+ // 0 - Empty
+ // 1 - Touched by top rect
+ // 2 - Touched by bottom rect
+ // 3 - Touched on both sides
+ int state;
+ const int TouchedByNothing = 0;
+ const int TouchedByTop = 1;
+ const int TouchedByBottom = 2;
+ // We always start with nothing.
+ int oldState = TouchedByNothing;
+ // Last state change, adjusted by -1 if the last state change was
+ // a change away from 0.
+ int lastX = std::min(topStrip->left, bottomStrip->left) - 1;
+
+ // Current edge being considered for top and bottom,
+ // 0 - left, 1 - right.
+ bool topEdgeIsLeft = true;
+ bool bottomEdgeIsLeft = true;
+ while (topStrip != std::end(topBand.mStrips) &&
+ bottomStrip != std::end(bottomBand.mStrips)) {
+ int topPos;
+ int bottomPos;
+ if (topEdgeIsLeft) {
+ topPos = topStrip->left;
+ } else {
+ topPos = topStrip->right;
+ }
+ if (bottomEdgeIsLeft) {
+ bottomPos = bottomStrip->left;
+ } else {
+ bottomPos = bottomStrip->right;
+ }
+
+ int currentX = std::min(topPos, bottomPos);
+ if (topPos < bottomPos) {
+ if (topEdgeIsLeft) {
+ state = oldState | TouchedByTop;
+ } else {
+ state = oldState ^ TouchedByTop;
+ topStrip++;
+ }
+ topEdgeIsLeft = !topEdgeIsLeft;
+ } else if (bottomPos < topPos) {
+ if (bottomEdgeIsLeft) {
+ state = oldState | TouchedByBottom;
+ } else {
+ state = oldState ^ TouchedByBottom;
+ bottomStrip++;
+ }
+ bottomEdgeIsLeft = !bottomEdgeIsLeft;
+ } else {
+ // bottomPos == topPos
+ state = TouchedByNothing;
+ if (bottomEdgeIsLeft) {
+ state = TouchedByBottom;
+ } else {
+ bottomStrip++;
+ }
+ if (topEdgeIsLeft) {
+ state |= TouchedByTop;
+ } else {
+ topStrip++;
+ }
+ topEdgeIsLeft = !topEdgeIsLeft;
+ bottomEdgeIsLeft = !bottomEdgeIsLeft;
+ }
+
+ MOZ_ASSERT(state != oldState);
+ if (oldState == TouchedByNothing) {
+ // We had nothing before, make sure the left edge will be padded.
+ lastX = currentX - 1;
+ } else if (oldState == TouchedByTop) {
+ if (state == TouchedByNothing) {
+ visit(closure, VisitSide::BOTTOM, lastX, y, currentX + 1, y);
+ } else {
+ visit(closure, VisitSide::BOTTOM, lastX, y, currentX, y);
+ lastX = currentX;
+ }
+ } else if (oldState == TouchedByBottom) {
+ if (state == TouchedByNothing) {
+ visit(closure, VisitSide::TOP, lastX, y, currentX + 1, y);
+ } else {
+ visit(closure, VisitSide::TOP, lastX, y, currentX, y);
+ lastX = currentX;
+ }
+ } else {
+ lastX = currentX;
+ }
+ oldState = state;
+ }
+
+ MOZ_ASSERT(!state || (topEdgeIsLeft || bottomEdgeIsLeft));
+ if (topStrip != std::end(topBand.mStrips)) {
+ if (!topEdgeIsLeft) {
+ visit(closure, VisitSide::BOTTOM, lastX, y, topStrip->right + 1, y);
+ topStrip++;
+ }
+ while (topStrip != std::end(topBand.mStrips)) {
+ visit(closure, VisitSide::BOTTOM, topStrip->left - 1, y,
+ topStrip->right + 1, y);
+ topStrip++;
+ }
+ } else if (bottomStrip != std::end(bottomBand.mStrips)) {
+ if (!bottomEdgeIsLeft) {
+ visit(closure, VisitSide::TOP, lastX, y, bottomStrip->right + 1, y);
+ bottomStrip++;
+ }
+ while (bottomStrip != std::end(bottomBand.mStrips)) {
+ visit(closure, VisitSide::TOP, bottomStrip->left - 1, y,
+ bottomStrip->right + 1, y);
+ bottomStrip++;
+ }
+ }
+ } else {
+ for (const Strip& strip : topBand.mStrips) {
+ visit(closure, VisitSide::BOTTOM, strip.left - 1, topBand.bottom,
+ strip.right + 1, topBand.bottom);
+ }
+ for (const Strip& strip : band->mStrips) {
+ visit(closure, VisitSide::TOP, strip.left - 1, band->top,
+ strip.right + 1, band->top);
+ }
+ }
+ } while (band != bandFinal);
+ }
+
+ for (const Strip& strip : band->mStrips) {
+ visit(closure, VisitSide::BOTTOM, strip.left - 1, band->bottom,
+ strip.right + 1, band->bottom);
+ }
+}
+
+void nsRegion::SimplifyInward(uint32_t aMaxRects) {
+ NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
+
+ if (GetNumRects() <= aMaxRects) return;
+
+ SetEmpty();
+}
+
+uint64_t nsRegion::Area() const {
+ if (mBands.IsEmpty()) {
+ return mBounds.Area();
+ }
+
+ uint64_t area = 0;
+ for (const Band& band : mBands) {
+ uint32_t height = band.bottom - band.top;
+ for (const Strip& strip : band.mStrips) {
+ area += (strip.right - strip.left) * height;
+ }
+ }
+
+ return area;
+}
+
+nsRegion& nsRegion::ScaleRoundOut(float aXScale, float aYScale) {
+ if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) &&
+ mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) {
+ return *this;
+ }
+
+ nsRegion newRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRectAbsolute rect = iter.GetAbsolute();
+ rect.ScaleRoundOut(aXScale, aYScale);
+ newRegion.AddRect(rect);
+ }
+
+ *this = std::move(newRegion);
+ return *this;
+}
+
+nsRegion& nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) {
+ nsRegion newRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRectAbsolute rect = iter.GetAbsolute();
+ rect.ScaleInverseRoundOut(aXScale, aYScale);
+ newRegion.AddRect(rect);
+ }
+
+ *this = std::move(newRegion);
+ return *this;
+}
+
+static mozilla::gfx::IntRect TransformRect(
+ const mozilla::gfx::IntRect& aRect,
+ const mozilla::gfx::Matrix4x4& aTransform) {
+ if (aRect.IsEmpty()) {
+ return mozilla::gfx::IntRect();
+ }
+
+ mozilla::gfx::RectDouble rect(aRect.X(), aRect.Y(), aRect.Width(),
+ aRect.Height());
+ rect = aTransform.TransformAndClipBounds(
+ rect, mozilla::gfx::RectDouble::MaxIntRect());
+ rect.RoundOut();
+
+ mozilla::gfx::IntRect intRect;
+ if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) {
+ return mozilla::gfx::IntRect();
+ }
+
+ return intRect;
+}
+
+nsRegion& nsRegion::Transform(const mozilla::gfx::Matrix4x4& aTransform) {
+ nsRegion newRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRect rect = nsIntRegion::ToRect(
+ TransformRect(nsIntRegion::FromRect(iter.Get()), aTransform));
+ newRegion.AddRect(nsRectAbsolute::FromRect(rect));
+ }
+
+ *this = std::move(newRegion);
+ return *this;
+}
+
+nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
+ int32_t aToAPP) const {
+ if (aFromAPP == aToAPP) {
+ return *this;
+ }
+ nsRegion newRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRect rect = iter.Get();
+ rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP);
+ newRegion.AddRect(nsRectAbsolute::FromRect(rect));
+ }
+
+ return newRegion;
+}
+
+nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
+ int32_t aToAPP) const {
+ if (aFromAPP == aToAPP) {
+ return *this;
+ }
+
+ nsRegion newRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRect rect = iter.Get();
+ rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP);
+ newRegion.AddRect(nsRectAbsolute::FromRect(rect));
+ }
+
+ return newRegion;
+}
+
+nsIntRegion nsRegion::ToPixels(nscoord aAppUnitsPerPixel,
+ bool aOutsidePixels) const {
+ nsIntRegion intRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ mozilla::gfx::IntRect deviceRect;
+ nsRect rect = iter.Get();
+ if (aOutsidePixels)
+ deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel);
+ else
+ deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel);
+ intRegion.OrWith(deviceRect);
+ }
+
+ return intRegion;
+}
+
+nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const {
+ return ToPixels(aAppUnitsPerPixel, true);
+}
+
+nsIntRegion nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel) const {
+ return ToPixels(aAppUnitsPerPixel, false);
+}
+
+nsIntRegion nsRegion::ScaleToNearestPixels(float aScaleX, float aScaleY,
+ nscoord aAppUnitsPerPixel) const {
+ nsIntRegion result;
+ for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
+ mozilla::gfx::IntRect deviceRect =
+ iter.Get().ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel);
+ result.Or(result, deviceRect);
+ }
+ return result;
+}
+
+nsIntRegion nsRegion::ScaleToOutsidePixels(float aScaleX, float aScaleY,
+ nscoord aAppUnitsPerPixel) const {
+ // make a copy of the region so that we can mutate it inplace
+ nsIntRegion intRegion;
+ for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRect rect = iter.Get();
+ intRegion.OrWith(
+ rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel));
+ }
+ return intRegion;
+}
+
+nsIntRegion nsRegion::ScaleToInsidePixels(float aScaleX, float aScaleY,
+ nscoord aAppUnitsPerPixel) const {
+ /* When scaling a rect, walk forward through the rect list up until the y
+ * value is greater than the current rect's YMost() value.
+ *
+ * For each rect found, check if the rects have a touching edge (in unscaled
+ * coordinates), and if one edge is entirely contained within the other.
+ *
+ * If it is, then the contained edge can be moved (in scaled pixels) to ensure
+ * that no gap exists.
+ *
+ * Since this could be potentially expensive - O(n^2), we only attempt this
+ * algorithm for the first rect.
+ */
+
+ if (mBands.IsEmpty()) {
+ nsIntRect rect = mBounds.ToNSRect().ScaleToInsidePixels(aScaleX, aScaleY,
+ aAppUnitsPerPixel);
+ return nsIntRegion(rect);
+ }
+
+ nsIntRegion intRegion;
+ RectIterator iter = RectIterator(*this);
+
+ nsRect first = iter.Get();
+
+ mozilla::gfx::IntRect firstDeviceRect =
+ first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
+
+ for (iter.Next(); !iter.Done(); iter.Next()) {
+ nsRect rect = iter.Get();
+ mozilla::gfx::IntRect deviceRect =
+ rect.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
+
+ if (rect.Y() <= first.YMost()) {
+ if (rect.XMost() == first.X() && rect.YMost() <= first.YMost()) {
+ // rect is touching on the left edge of the first rect and contained
+ // within the length of its left edge
+ deviceRect.SetRightEdge(firstDeviceRect.X());
+ } else if (rect.X() == first.XMost() && rect.YMost() <= first.YMost()) {
+ // rect is touching on the right edge of the first rect and contained
+ // within the length of its right edge
+ deviceRect.SetLeftEdge(firstDeviceRect.XMost());
+ } else if (rect.Y() == first.YMost()) {
+ // The bottom of the first rect is on the same line as the top of rect,
+ // but they aren't necessarily contained.
+ if (rect.X() <= first.X() && rect.XMost() >= first.XMost()) {
+ // The top of rect contains the bottom of the first rect
+ firstDeviceRect.SetBottomEdge(deviceRect.Y());
+ } else if (rect.X() >= first.X() && rect.XMost() <= first.XMost()) {
+ // The bottom of the first contains the top of rect
+ deviceRect.SetTopEdge(firstDeviceRect.YMost());
+ }
+ }
+ }
+
+ intRegion.OrWith(deviceRect);
+ }
+
+ intRegion.OrWith(firstDeviceRect);
+ return intRegion;
+}
+
+// A cell's "value" is a pair consisting of
+// a) the area of the subrectangle it corresponds to, if it's in
+// aContainingRect and in the region, 0 otherwise
+// b) the area of the subrectangle it corresponds to, if it's in the region,
+// 0 otherwise
+// Addition, subtraction and identity are defined on these values in the
+// obvious way. Partial order is lexicographic.
+// A "large negative value" is defined with large negative numbers for both
+// fields of the pair. This negative value has the property that adding any
+// number of non-negative values to it always results in a negative value.
+//
+// The GetLargestRectangle algorithm works in three phases:
+// 1) Convert the region into a grid by adding vertical/horizontal lines for
+// each edge of each rectangle in the region.
+// 2) For each rectangle in the region, for each cell it contains, set that
+// cells's value as described above.
+// 3) Calculate the submatrix with the largest sum such that none of its cells
+// contain any 0s (empty regions). The rectangle represented by the
+// submatrix is the largest rectangle in the region.
+//
+// Let k be the number of rectangles in the region.
+// Let m be the height of the grid generated in step 1.
+// Let n be the width of the grid generated in step 1.
+//
+// Step 1 is O(k) in time and O(m+n) in space for the sparse grid.
+// Step 2 is O(mn) in time and O(mn) in additional space for the full grid.
+// Step 3 is O(m^2 n) in time and O(mn) in additional space
+//
+// The implementation of steps 1 and 2 are rather straightforward. However our
+// implementation of step 3 uses dynamic programming to achieve its efficiency.
+//
+// Psuedo code for step 3 is as follows where G is the grid from step 1 and A
+// is the array from step 2:
+// Phase3 = function (G, A, m, n) {
+// let (t,b,l,r,_) = MaxSum2D(A,m,n)
+// return rect(G[t],G[l],G[r],G[b]);
+// }
+// MaxSum2D = function (A, m, n) {
+// S = array(m+1,n+1)
+// S[0][i] = 0 for i in [0,n]
+// S[j][0] = 0 for j in [0,m]
+// S[j][i] = (if A[j-1][i-1] = 0 then some large negative value
+// else A[j-1][i-1])
+// + S[j-1][n] + S[j][i-1] - S[j-1][i-1]
+//
+// // top, bottom, left, right, area
+// var maxRect = (-1, -1, -1, -1, 0);
+//
+// for all (m',m'') in [0, m]^2 {
+// let B = { S[m'][i] - S[m''][i] | 0 <= i <= n }
+// let ((l,r),area) = MaxSum1D(B,n+1)
+// if (area > maxRect.area) {
+// maxRect := (m', m'', l, r, area)
+// }
+// }
+//
+// return maxRect;
+// }
+//
+// Originally taken from Improved algorithms for the k-maximum subarray problem
+// for small k - SE Bae, T Takaoka but modified to show the explicit tracking
+// of indices and we already have the prefix sums from our one call site so
+// there's no need to construct them.
+// MaxSum1D = function (A,n) {
+// var minIdx = 0;
+// var min = 0;
+// var maxIndices = (0,0);
+// var max = 0;
+// for i in range(n) {
+// let cand = A[i] - min;
+// if (cand > max) {
+// max := cand;
+// maxIndices := (minIdx, i)
+// }
+// if (min > A[i]) {
+// min := A[i];
+// minIdx := i;
+// }
+// }
+// return (minIdx, maxIdx, max);
+// }
+
+namespace {
+// This class represents a partitioning of an axis delineated by coordinates.
+// It internally maintains a sorted array of coordinates.
+class AxisPartition {
+ public:
+ // Adds a new partition at the given coordinate to this partitioning. If
+ // the coordinate is already present in the partitioning, this does nothing.
+ void InsertCoord(nscoord c) {
+ uint32_t i = mStops.IndexOfFirstElementGt(c);
+ if (i == 0 || mStops[i - 1] != c) {
+ mStops.InsertElementAt(i, c);
+ }
+ }
+
+ // Returns the array index of the given partition point. The partition
+ // point must already be present in the partitioning.
+ int32_t IndexOf(nscoord p) const { return mStops.BinaryIndexOf(p); }
+
+ // Returns the partition at the given index which must be non-zero and
+ // less than the number of partitions in this partitioning.
+ nscoord StopAt(int32_t index) const { return mStops[index]; }
+
+ // Returns the size of the gap between the partition at the given index and
+ // the next partition in this partitioning. If the index is the last index
+ // in the partitioning, the result is undefined.
+ nscoord StopSize(int32_t index) const {
+ return mStops[index + 1] - mStops[index];
+ }
+
+ // Returns the number of partitions in this partitioning.
+ int32_t GetNumStops() const { return mStops.Length(); }
+
+ private:
+ nsTArray<nscoord> mStops;
+};
+
+const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll;
+
+struct SizePair {
+ int64_t mSizeContainingRect;
+ int64_t mSize;
+
+ SizePair() : mSizeContainingRect(0), mSize(0) {}
+
+ static SizePair VeryLargeNegative() {
+ SizePair result;
+ result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
+ return result;
+ }
+ bool operator<(const SizePair& aOther) const {
+ if (mSizeContainingRect < aOther.mSizeContainingRect) return true;
+ if (mSizeContainingRect > aOther.mSizeContainingRect) return false;
+ return mSize < aOther.mSize;
+ }
+ bool operator>(const SizePair& aOther) const {
+ return aOther.operator<(*this);
+ }
+ SizePair operator+(const SizePair& aOther) const {
+ SizePair result = *this;
+ result.mSizeContainingRect += aOther.mSizeContainingRect;
+ result.mSize += aOther.mSize;
+ return result;
+ }
+ SizePair operator-(const SizePair& aOther) const {
+ SizePair result = *this;
+ result.mSizeContainingRect -= aOther.mSizeContainingRect;
+ result.mSize -= aOther.mSize;
+ return result;
+ }
+};
+
+// Returns the sum and indices of the subarray with the maximum sum of the
+// given array (A,n), assuming the array is already in prefix sum form.
+SizePair MaxSum1D(const nsTArray<SizePair>& A, int32_t n, int32_t* minIdx,
+ int32_t* maxIdx) {
+ // The min/max indicies of the largest subarray found so far
+ SizePair min, max;
+ int32_t currentMinIdx = 0;
+
+ *minIdx = 0;
+ *maxIdx = 0;
+
+ // Because we're given the array in prefix sum form, we know the first
+ // element is 0
+ for (int32_t i = 1; i < n; i++) {
+ SizePair cand = A[i] - min;
+ if (cand > max) {
+ max = cand;
+ *minIdx = currentMinIdx;
+ *maxIdx = i;
+ }
+ if (min > A[i]) {
+ min = A[i];
+ currentMinIdx = i;
+ }
+ }
+
+ return max;
+}
+} // namespace
+
+nsRect nsRegion::GetLargestRectangle(const nsRect& aContainingRect) const {
+ nsRect bestRect;
+
+ if (GetNumRects() <= 1) {
+ bestRect = GetBounds();
+ return bestRect;
+ }
+
+ AxisPartition xaxis, yaxis;
+
+ // Step 1: Calculate the grid lines
+ for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
+ const nsRect& rect = iter.Get();
+ xaxis.InsertCoord(rect.X());
+ xaxis.InsertCoord(rect.XMost());
+ yaxis.InsertCoord(rect.Y());
+ yaxis.InsertCoord(rect.YMost());
+ }
+ if (!aContainingRect.IsEmpty()) {
+ xaxis.InsertCoord(aContainingRect.X());
+ xaxis.InsertCoord(aContainingRect.XMost());
+ yaxis.InsertCoord(aContainingRect.Y());
+ yaxis.InsertCoord(aContainingRect.YMost());
+ }
+
+ // Step 2: Fill out the grid with the areas
+ // Note: due to the ordering of rectangles in the region, it is not always
+ // possible to combine steps 2 and 3 so we don't try to be clever.
+ int32_t matrixHeight = yaxis.GetNumStops() - 1;
+ int32_t matrixWidth = xaxis.GetNumStops() - 1;
+ int32_t matrixSize = matrixHeight * matrixWidth;
+ nsTArray<SizePair> areas(matrixSize);
+ areas.SetLength(matrixSize);
+
+ for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
+ const nsRect& rect = iter.Get();
+ int32_t xstart = xaxis.IndexOf(rect.X());
+ int32_t xend = xaxis.IndexOf(rect.XMost());
+ int32_t y = yaxis.IndexOf(rect.Y());
+ int32_t yend = yaxis.IndexOf(rect.YMost());
+
+ for (; y < yend; y++) {
+ nscoord height = yaxis.StopSize(y);
+ for (int32_t x = xstart; x < xend; x++) {
+ nscoord width = xaxis.StopSize(x);
+ int64_t size = width * int64_t(height);
+ if (rect.Intersects(aContainingRect)) {
+ areas[y * matrixWidth + x].mSizeContainingRect = size;
+ }
+ areas[y * matrixWidth + x].mSize = size;
+ }
+ }
+ }
+
+ // Step 3: Find the maximum submatrix sum that does not contain a rectangle
+ {
+ // First get the prefix sum array
+ int32_t m = matrixHeight + 1;
+ int32_t n = matrixWidth + 1;
+ nsTArray<SizePair> pareas(m * n);
+ pareas.SetLength(m * n);
+ for (int32_t y = 1; y < m; y++) {
+ for (int32_t x = 1; x < n; x++) {
+ SizePair area = areas[(y - 1) * matrixWidth + x - 1];
+ if (!area.mSize) {
+ area = SizePair::VeryLargeNegative();
+ }
+ area = area + pareas[y * n + x - 1] + pareas[(y - 1) * n + x] -
+ pareas[(y - 1) * n + x - 1];
+ pareas[y * n + x] = area;
+ }
+ }
+
+ // No longer need the grid
+ areas.SetLength(0);
+
+ SizePair bestArea;
+ struct {
+ int32_t left, top, right, bottom;
+ } bestRectIndices = {0, 0, 0, 0};
+ for (int32_t m1 = 0; m1 < m; m1++) {
+ for (int32_t m2 = m1 + 1; m2 < m; m2++) {
+ nsTArray<SizePair> B;
+ B.SetLength(n);
+ for (int32_t i = 0; i < n; i++) {
+ B[i] = pareas[m2 * n + i] - pareas[m1 * n + i];
+ }
+ int32_t minIdx, maxIdx;
+ SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx);
+ if (area > bestArea) {
+ bestRectIndices.left = minIdx;
+ bestRectIndices.top = m1;
+ bestRectIndices.right = maxIdx;
+ bestRectIndices.bottom = m2;
+ bestArea = area;
+ }
+ }
+ }
+
+ bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left),
+ yaxis.StopAt(bestRectIndices.top));
+ bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.X(),
+ yaxis.StopAt(bestRectIndices.bottom) - bestRect.Y());
+ }
+
+ return bestRect;
+}
+
+std::ostream& operator<<(std::ostream& stream, const nsRegion& m) {
+ stream << "[";
+
+ bool first = true;
+ for (auto iter = m.RectIter(); !iter.Done(); iter.Next()) {
+ if (!first) {
+ stream << "; ";
+ } else {
+ first = false;
+ }
+ const nsRect& rect = iter.Get();
+ stream << rect.X() << "," << rect.Y() << "," << rect.XMost() << ","
+ << rect.YMost();
+ }
+
+ stream << "]";
+ return stream;
+}
+
+void nsRegion::OutputToStream(std::string aObjName,
+ std::ostream& stream) const {
+ auto iter = RectIter();
+ nsRect r = iter.Get();
+ stream << "nsRegion " << aObjName << "(nsRect(" << r.X() << ", " << r.Y()
+ << ", " << r.Width() << ", " << r.Height() << "));\n";
+ iter.Next();
+
+ for (; !iter.Done(); iter.Next()) {
+ nsRect r = iter.Get();
+ stream << aObjName << ".OrWith(nsRect(" << r.X() << ", " << r.Y() << ", "
+ << r.Width() << ", " << r.Height() << "));\n";
+ }
+}
+
+nsCString nsRegion::ToString() const {
+ return nsCString(mozilla::ToString(*this).c_str());
+}
diff --git a/gfx/src/nsRegion.h b/gfx/src/nsRegion.h
new file mode 100644
index 0000000000..9c0fd8ed00
--- /dev/null
+++ b/gfx/src/nsRegion.h
@@ -0,0 +1,2524 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsRegion_h__
+#define nsRegion_h__
+
+#include <stddef.h> // for size_t
+#include <stdint.h> // for uint32_t, uint64_t
+
+#include <ostream> // for std::ostream
+#include <utility> // for mozilla::Move
+
+#include "mozilla/ArrayView.h" // for ArrayView
+#include "mozilla/gfx/MatrixFwd.h" // for mozilla::gfx::Matrix4x4
+#include "nsCoord.h" // for nscoord
+#include "nsMargin.h" // for nsIntMargin
+#include "nsPoint.h" // for nsIntPoint, nsPoint
+#include "nsRect.h" // for mozilla::gfx::IntRect, nsRect
+#include "nsRectAbsolute.h"
+#include "nsRegionFwd.h" // for nsIntRegion
+#include "nsString.h" // for nsCString
+#include "nsTArray.h"
+#include "pixman.h"
+
+// Uncomment this line to get additional integrity checking.
+//#define DEBUG_REGIONS
+#ifdef DEBUG_REGIONS
+# include <sstream>
+#endif
+
+/* For information on the internal representation look at pixman-region.c
+ *
+ * This replaces an older homebrew implementation of nsRegion. The
+ * representation used here may use more rectangles than nsRegion however, the
+ * representation is canonical. This means that there's no need for an
+ * Optimize() method because for a paticular region there is only one
+ * representation. This means that nsIntRegion will have more predictable
+ * performance characteristics than the old nsRegion and should not become
+ * degenerate.
+ *
+ * The pixman region code originates from X11 which has spread to a variety of
+ * projects including Qt, Gtk, Wine. It should perform reasonably well.
+ */
+
+enum class VisitSide { TOP, BOTTOM, LEFT, RIGHT };
+
+namespace regiondetails {
+struct Band;
+}
+
+template <>
+struct nsTArray_RelocationStrategy<regiondetails::Band> {
+ typedef nsTArray_RelocateUsingMoveConstructor<regiondetails::Band> Type;
+};
+
+namespace regiondetails {
+
+template <typename T, typename E>
+class UncheckedArray : public T {
+ public:
+ using T::Elements;
+ using T::Length;
+
+ UncheckedArray() = default;
+ MOZ_IMPLICIT UncheckedArray(T&& aSrc) : T(std::move(aSrc)) {}
+
+ E& operator[](size_t aIndex) { return Elements()[aIndex]; }
+ const E& operator[](size_t aIndex) const { return Elements()[aIndex]; }
+ E& LastElement() { return Elements()[Length() - 1]; }
+ const E& LastElement() const { return Elements()[Length() - 1]; }
+
+ using iterator = E*;
+ using const_iterator = const E*;
+
+ iterator begin() { return iterator(Elements()); }
+ const_iterator begin() const { return const_iterator(Elements()); }
+ const_iterator cbegin() const { return begin(); }
+ iterator end() { return iterator(Elements() + Length()); }
+ const_iterator end() const { return const_iterator(Elements() + Length()); }
+ const_iterator cend() const { return end(); }
+};
+
+struct Strip {
+ // Default constructor should never be called, but is required for
+ // vector::resize to compile.
+ Strip() { MOZ_CRASH(); }
+ Strip(int32_t aLeft, int32_t aRight) : left(aLeft), right(aRight) {}
+
+ bool operator!=(const Strip& aOther) const {
+ return left != aOther.left || right != aOther.right;
+ }
+
+ uint32_t Size() const { return right - left; }
+
+ int32_t left;
+ int32_t right;
+};
+
+struct Band {
+ using Strip = regiondetails::Strip;
+#ifndef DEBUG
+ using StripArray =
+ regiondetails::UncheckedArray<CopyableAutoTArray<Strip, 2>, Strip>;
+#else
+ using StripArray = CopyableAutoTArray<Strip, 2>;
+#endif
+
+ MOZ_IMPLICIT Band(const nsRectAbsolute& aRect)
+ : top(aRect.Y()), bottom(aRect.YMost()) {
+ mStrips.AppendElement(Strip{aRect.X(), aRect.XMost()});
+ }
+
+ Band(const Band& aOther) = default;
+ Band(Band&& aOther) = default;
+
+ void InsertStrip(const Strip& aStrip) {
+ for (size_t i = 0; i < mStrips.Length(); i++) {
+ Strip& strip = mStrips[i];
+ if (strip.left > aStrip.right) {
+ // Current strip is beyond aStrip, insert aStrip before.
+ mStrips.InsertElementAt(i, aStrip);
+ return;
+ }
+
+ if (strip.right < aStrip.left) {
+ // Current strip is before aStrip, try the next.
+ continue;
+ }
+
+ // Current strip intersects with aStrip, extend to the lext.
+ strip.left = std::min(strip.left, aStrip.left);
+
+ if (strip.right >= aStrip.right) {
+ // Current strip extends beyond aStrip, done.
+ return;
+ }
+
+ size_t next = i;
+ next++;
+ // Consume any subsequent strips intersecting with aStrip.
+ while (next < mStrips.Length() && mStrips[next].left <= aStrip.right) {
+ strip.right = mStrips[next].right;
+
+ mStrips.RemoveElementAt(next);
+ }
+
+ // Extend the strip in case the aStrip goes on beyond it.
+ strip.right = std::max(strip.right, aStrip.right);
+ return;
+ }
+ mStrips.AppendElement(aStrip);
+ }
+
+ void SubStrip(const Strip& aStrip) {
+ for (size_t i = 0; i < mStrips.Length(); i++) {
+ Strip& strip = mStrips[i];
+ if (strip.left > aStrip.right) {
+ // Strip is entirely to the right of aStrip. Done.
+ return;
+ }
+
+ if (strip.right < aStrip.left) {
+ // Strip is entirely to the left of aStrip. Move on.
+ continue;
+ }
+
+ if (strip.left < aStrip.left) {
+ if (strip.right <= aStrip.right) {
+ strip.right = aStrip.left;
+ // This strip lies to the left of the start of aStrip.
+ continue;
+ }
+
+ // aStrip is completely contained by this strip.
+ Strip newStrip(aStrip.right, strip.right);
+ strip.right = aStrip.left;
+ if (i < mStrips.Length()) {
+ i++;
+ mStrips.InsertElementAt(i, newStrip);
+ } else {
+ mStrips.AppendElement(newStrip);
+ }
+ return;
+ }
+
+ // This strip lies to the right of the start of aStrip.
+ if (strip.right <= aStrip.right) {
+ // aStrip completely contains this strip.
+ mStrips.RemoveElementAt(i);
+ // Make sure we evaluate the strip now at i. This loop will increment.
+ i--;
+ continue;
+ }
+ strip.left = aStrip.right;
+ return;
+ }
+ }
+
+ bool Intersects(const Strip& aStrip) const {
+ for (const Strip& strip : mStrips) {
+ if (strip.left >= aStrip.right) {
+ return false;
+ }
+
+ if (strip.right <= aStrip.left) {
+ continue;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ bool IntersectStripBounds(Strip& aStrip) const {
+ bool intersected = false;
+
+ int32_t rightMost;
+ for (const Strip& strip : mStrips) {
+ if (strip.left > aStrip.right) {
+ break;
+ }
+
+ if (strip.right <= aStrip.left) {
+ continue;
+ }
+
+ if (!intersected) {
+ // First intersection, this is where the left side begins.
+ aStrip.left = std::max(aStrip.left, strip.left);
+ }
+
+ intersected = true;
+ // Expand to the right for each intersecting strip found.
+ rightMost = std::min(strip.right, aStrip.right);
+ }
+
+ if (intersected) {
+ aStrip.right = rightMost;
+ } else {
+ aStrip.right = aStrip.left = 0;
+ }
+ return intersected;
+ }
+
+ bool ContainsStrip(const Strip& aStrip) const {
+ for (const Strip& strip : mStrips) {
+ if (strip.left > aStrip.left) {
+ return false;
+ }
+
+ if (strip.right >= aStrip.right) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool EqualStrips(const Band& aBand) const {
+ if (mStrips.Length() != aBand.mStrips.Length()) {
+ return false;
+ }
+
+ for (auto iter1 = mStrips.begin(), iter2 = aBand.mStrips.begin();
+ iter1 != mStrips.end(); iter1++, iter2++) {
+ if (*iter1 != *iter2) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void IntersectStrip(const Strip& aStrip) {
+ size_t i = 0;
+
+ while (i < mStrips.Length()) {
+ Strip& strip = mStrips[i];
+ if (strip.right <= aStrip.left) {
+ mStrips.RemoveElementAt(i);
+ continue;
+ }
+
+ if (strip.left >= aStrip.right) {
+ mStrips.TruncateLength(i);
+ return;
+ }
+
+ strip.left = std::max(aStrip.left, strip.left);
+ strip.right = std::min(aStrip.right, strip.right);
+ i++;
+ }
+ }
+
+ void IntersectStrips(const Band& aOther) {
+ auto iter = mStrips.begin();
+ auto iterOther = aOther.mStrips.begin();
+
+ StripArray newStrips;
+
+ // This function finds the intersection between two sets of strips.
+ while (true) {
+ while (true) {
+ while (iter != mStrips.end() && iter->right <= iterOther->left) {
+ // Increment our current strip until it ends beyond aOther's current
+ // strip.
+ iter++;
+ }
+
+ if (iter == mStrips.end()) {
+ // End of our strips. Done.
+ break;
+ }
+
+ while (iterOther != aOther.mStrips.end() &&
+ iterOther->right <= iter->left) {
+ // Increment aOther's current strip until it lies beyond our current
+ // strip.
+ iterOther++;
+ }
+
+ if (iterOther == aOther.mStrips.end()) {
+ // End of aOther's strips. Done.
+ break;
+ }
+
+ if (iterOther->left < iter->right) {
+ // Intersection!
+ break;
+ }
+ }
+
+ if (iter == mStrips.end() || iterOther == aOther.mStrips.end()) {
+ break;
+ }
+
+ newStrips.AppendElement(Strip(std::max(iter->left, iterOther->left),
+ std::min(iterOther->right, iter->right)));
+
+ if (iterOther->right < iter->right) {
+ iterOther++;
+ if (iterOther == aOther.mStrips.end()) {
+ break;
+ }
+ } else {
+ iter++;
+ }
+ }
+
+ mStrips = std::move(newStrips);
+ }
+
+ bool Intersects(const Band& aOther) const {
+ auto iter = mStrips.begin();
+ auto iterOther = aOther.mStrips.begin();
+
+ // This function finds the intersection between two sets of strips.
+ while (true) {
+ while (true) {
+ while (iter != mStrips.end() && iter->right <= iterOther->left) {
+ // Increment our current strip until it ends beyond aOther's current
+ // strip.
+ iter++;
+ }
+
+ if (iter == mStrips.end()) {
+ // End of our strips. Done.
+ break;
+ }
+
+ while (iterOther != aOther.mStrips.end() &&
+ iterOther->right <= iter->left) {
+ // Increment aOther's current strip until it lies beyond our current
+ // strip.
+ iterOther++;
+ }
+
+ if (iterOther == aOther.mStrips.end()) {
+ // End of aOther's strips. Done.
+ break;
+ }
+
+ if (iterOther->left < iter->right) {
+ // Intersection!
+ break;
+ }
+ }
+
+ if (iter == mStrips.end() || iterOther == aOther.mStrips.end()) {
+ break;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ void SubStrips(const Band& aOther) {
+ size_t idx = 0;
+ auto iterOther = aOther.mStrips.begin();
+
+ // This function finds the intersection between two sets of strips.
+ while (true) {
+ while (true) {
+ while (idx < mStrips.Length() &&
+ mStrips[idx].right <= iterOther->left) {
+ // Increment our current strip until it ends beyond aOther's current
+ // strip.
+ idx++;
+ }
+
+ if (idx == mStrips.Length()) {
+ // End of our strips. Done.
+ break;
+ }
+
+ while (iterOther != aOther.mStrips.end() &&
+ iterOther->right <= mStrips[idx].left) {
+ // Increment aOther's current strip until it lies beyond our current
+ // strip.
+ iterOther++;
+ }
+
+ if (iterOther == aOther.mStrips.end()) {
+ // End of aOther's strips. Done.
+ break;
+ }
+
+ if (iterOther->left < mStrips[idx].right) {
+ // Intersection!
+ break;
+ }
+ }
+
+ if (idx == mStrips.Length() || iterOther == aOther.mStrips.end()) {
+ break;
+ }
+
+ if (mStrips[idx].left < iterOther->left) {
+ size_t oldIdx = idx;
+ // Our strip starts beyond other's
+ if (mStrips[idx].right > iterOther->right) {
+ // Our strip ends beyond other's as well.
+ Strip newStrip(mStrips[idx]);
+ newStrip.left = iterOther->right;
+ mStrips.InsertElementAt(idx + 1, newStrip);
+ idx++;
+ }
+ mStrips[oldIdx].right = iterOther->left;
+ // Either idx was just incremented, or the current index no longer
+ // intersects with iterOther.
+ continue;
+ } else if (mStrips[idx].right > iterOther->right) {
+ mStrips[idx].left = iterOther->right;
+ // Current strip no longer intersects, continue.
+ iterOther++;
+ if (iterOther == aOther.mStrips.end()) {
+ break;
+ }
+ continue;
+ }
+
+ // Our current strip is completely contained by the other strip.
+ mStrips.RemoveElementAt(idx);
+ }
+ }
+
+ int32_t top;
+ int32_t bottom;
+ StripArray mStrips;
+};
+} // namespace regiondetails
+
+class nsRegion {
+ public:
+ using Band = regiondetails::Band;
+ using Strip = regiondetails::Strip;
+#ifndef DEBUG
+ using BandArray = regiondetails::UncheckedArray<nsTArray<Band>, Band>;
+ using StripArray = regiondetails::UncheckedArray<AutoTArray<Strip, 2>, Strip>;
+#else
+ using BandArray = nsTArray<Band>;
+ using StripArray = AutoTArray<Strip, 2>;
+#endif
+
+ typedef nsRect RectType;
+ typedef nsPoint PointType;
+ typedef nsMargin MarginType;
+
+ nsRegion() = default;
+ MOZ_IMPLICIT nsRegion(const nsRect& aRect) {
+ mBounds = nsRectAbsolute::FromRect(aRect);
+ }
+ MOZ_IMPLICIT nsRegion(const nsRectAbsolute& aRect) { mBounds = aRect; }
+ explicit nsRegion(mozilla::gfx::ArrayView<pixman_box32_t> aRects) {
+ for (uint32_t i = 0; i < aRects.Length(); i++) {
+ AddRect(BoxToRect(aRects[i]));
+ }
+ }
+
+ nsRegion(const nsRegion& aRegion) { Copy(aRegion); }
+ nsRegion(nsRegion&& aRegion)
+ : mBands(std::move(aRegion.mBands)), mBounds(aRegion.mBounds) {
+ aRegion.SetEmpty();
+ }
+ nsRegion& operator=(nsRegion&& aRegion) {
+ mBands = std::move(aRegion.mBands);
+ mBounds = aRegion.mBounds;
+ aRegion.SetEmpty();
+ return *this;
+ }
+ nsRegion& operator=(const nsRect& aRect) {
+ Copy(aRect);
+ return *this;
+ }
+ nsRegion& operator=(const nsRegion& aRegion) {
+ Copy(aRegion);
+ return *this;
+ }
+ bool operator==(const nsRegion& aRgn) const { return IsEqual(aRgn); }
+ bool operator!=(const nsRegion& aRgn) const { return !(*this == aRgn); }
+
+ friend std::ostream& operator<<(std::ostream& stream, const nsRegion& m);
+ void OutputToStream(std::string aObjName, std::ostream& stream) const;
+
+ private:
+#ifdef DEBUG_REGIONS
+ class OperationStringGenerator {
+ public:
+ virtual ~OperationStringGenerator() = default;
+
+ virtual void OutputOp() = 0;
+ };
+#endif
+ public:
+ void AssertStateInternal() const;
+ void AssertState() const {
+#ifdef DEBUG_REGIONS
+ AssertStateInternal();
+#endif
+ }
+
+ private:
+ void And(BandArray& aOut, const BandArray& aIn1, const BandArray& aIn2) {
+ size_t idx = 0;
+ size_t idxOther = 0;
+
+ // This algorithm essentially forms a new list of bands, by iterating over
+ // both regions' lists of band simultaneously, and building a new band
+ // wherever the two regions intersect.
+ while (true) {
+ while (true) {
+ while (idx != aIn1.Length() && aIn1[idx].bottom <= aIn2[idxOther].top) {
+ // Increment our current band until it ends beyond aOther's current
+ // band.
+ idx++;
+ }
+
+ if (idx == aIn1.Length()) {
+ // This region is out of bands, the other region's future bands are
+ // ignored.
+ break;
+ }
+
+ while (idxOther != aIn2.Length() &&
+ aIn2[idxOther].bottom <= aIn1[idx].top) {
+ // Increment aOther's current band until it ends beyond our current
+ // band.
+ idxOther++;
+ }
+
+ if (idxOther == aIn2.Length()) {
+ // The other region's bands are all processed, all our future bands
+ // are ignored.
+ break;
+ }
+
+ if (aIn2[idxOther].top < aIn1[idx].bottom) {
+ // We know the other band's bottom lies beyond our band's top because
+ // otherwise we would've incremented above. Intersecting bands found.
+ break;
+ }
+ }
+
+ if (idx == aIn1.Length() || idxOther == aIn2.Length()) {
+ // The above loop executed a break because we're done.
+ break;
+ }
+
+ Band newBand(aIn1[idx]);
+ // The new band is the intersection of the two current bands from both
+ // regions.
+ newBand.top = std::max(aIn1[idx].top, aIn2[idxOther].top);
+ newBand.bottom = std::min(aIn1[idx].bottom, aIn2[idxOther].bottom);
+ newBand.IntersectStrips(aIn2[idxOther]);
+
+ if (newBand.mStrips.Length()) {
+ // The intersecting area of the bands had overlapping strips, if it is
+ // identical to the band above it merge, otherwise append.
+ if (aOut.Length() && aOut.LastElement().bottom == newBand.top &&
+ aOut.LastElement().EqualStrips(newBand)) {
+ aOut.LastElement().bottom = newBand.bottom;
+ } else {
+ aOut.AppendElement(std::move(newBand));
+ }
+ }
+
+ if (aIn2[idxOther].bottom < aIn1[idx].bottom) {
+ idxOther++;
+ if (idxOther == aIn2.Length()) {
+ // Since we will access idxOther the next iteration, check if we're
+ // not done.
+ break;
+ }
+ } else {
+ // No need to check here since we do at the beginning of the next
+ // iteration.
+ idx++;
+ }
+ }
+ }
+
+ public:
+ nsRegion& AndWith(const nsRegion& aRegion) {
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorAndWith : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorAndWith(nsRegion& aRegion,
+ const nsRegion& aOtherRegion)
+ : mRegion(&aRegion),
+ mRegionCopy(aRegion),
+ mOtherRegion(aOtherRegion) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorAndWith() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionCopy.OutputToStream("r1", stream);
+ mOtherRegion.OutputToStream("r2", stream);
+ stream << "r1.AndWith(r2);\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionCopy;
+ nsRegion mOtherRegion;
+ };
+
+ OperationStringGeneratorAndWith opGenerator(*this, aRegion);
+#endif
+ if (mBounds.IsEmpty()) {
+ // Region is empty, stays empty.
+ return *this;
+ }
+
+ if (aRegion.IsEmpty()) {
+ SetEmpty();
+ return *this;
+ }
+
+ if (aRegion.mBands.IsEmpty()) {
+ // Other region is a rect.
+ return AndWith(aRegion.mBounds);
+ }
+
+ if (mBands.IsEmpty()) {
+ mBands.AppendElement(mBounds);
+ }
+
+ BandArray newBands;
+
+ And(newBands, mBands, aRegion.mBands);
+
+ mBands = std::move(newBands);
+ if (!mBands.Length()) {
+ mBounds = nsRectAbsolute();
+ } else {
+ mBounds = CalculateBounds();
+ }
+
+ EnsureSimplified();
+ AssertState();
+ return *this;
+ }
+
+ nsRegion& AndWith(const nsRectAbsolute& aRect) {
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorAndWith : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorAndWith(nsRegion& aRegion,
+ const nsRectAbsolute& aRect)
+ : mRegion(&aRegion), mRegionCopy(aRegion), mRect(aRect) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorAndWith() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionCopy.OutputToStream("r", stream);
+ stream << "r.AndWith(nsRect(" << mRect.X() << ", " << mRect.Y() << ", "
+ << mRect.Width() << ", " << mRect.Height() << "));\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionCopy;
+ nsRectAbsolute mRect;
+ };
+
+ OperationStringGeneratorAndWith opGenerator(*this, aRect);
+#endif
+ if (aRect.IsEmpty()) {
+ SetEmpty();
+ return *this;
+ }
+
+ if (mBands.IsEmpty()) {
+ mBounds = mBounds.Intersect(aRect);
+ return *this;
+ }
+
+ size_t idx = 0;
+
+ size_t removeStart = 0;
+
+ // This removes all bands that do not intersect with aRect, and intersects
+ // the remaining ones with aRect.
+
+ // Start by figuring out how much to remove from the start.
+ while (idx != mBands.Length() && mBands[idx].bottom <= aRect.Y()) {
+ idx++;
+ }
+
+ // We'll remove these later to avoid needless copying in the array.
+ removeStart = idx;
+
+ while (idx != mBands.Length()) {
+ if (mBands[idx].top >= aRect.YMost()) {
+ mBands.TruncateLength(idx);
+ break;
+ }
+
+ mBands[idx].top = std::max(mBands[idx].top, aRect.Y());
+ mBands[idx].bottom = std::min(mBands[idx].bottom, aRect.YMost());
+
+ mBands[idx].IntersectStrip(Strip(aRect.X(), aRect.XMost()));
+
+ if (!mBands[idx].mStrips.Length()) {
+ mBands.RemoveElementAt(idx);
+ } else {
+ if (idx > removeStart) {
+ CompressBefore(idx);
+ }
+ idx++;
+ }
+ }
+
+ if (removeStart) {
+ mBands.RemoveElementsAt(0, removeStart);
+ }
+
+ if (mBands.Length()) {
+ mBounds = CalculateBounds();
+ } else {
+ mBounds.SetEmpty();
+ }
+ EnsureSimplified();
+ AssertState();
+ return *this;
+ }
+ nsRegion& AndWith(const nsRect& aRect) {
+ return AndWith(nsRectAbsolute::FromRect(aRect));
+ }
+ nsRegion& And(const nsRegion& aRgn1, const nsRegion& aRgn2) {
+ if (&aRgn1 == this) {
+ return AndWith(aRgn2);
+ }
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorAnd : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorAnd(nsRegion& aRegion, const nsRegion& aRegion1,
+ const nsRegion& aRegion2)
+ : mRegion(&aRegion), mRegion1(aRegion1), mRegion2(aRegion2) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorAnd() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegion1.OutputToStream("r1", stream);
+ mRegion2.OutputToStream("r2", stream);
+ stream << "nsRegion r3;\nr3.And(r1, r2);\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegion1;
+ nsRegion mRegion2;
+ };
+
+ OperationStringGeneratorAnd opGenerator(*this, aRgn1, aRgn2);
+#endif
+ mBands.Clear();
+
+ if (aRgn1.IsEmpty() || aRgn2.IsEmpty()) {
+ mBounds.SetEmpty();
+ return *this;
+ }
+
+ if (aRgn1.mBands.IsEmpty() && aRgn2.mBands.IsEmpty()) {
+ mBounds = aRgn1.mBounds.Intersect(aRgn2.mBounds);
+ return *this;
+ } else if (aRgn1.mBands.IsEmpty()) {
+ return And(aRgn2, aRgn1.mBounds);
+ } else if (aRgn2.mBands.IsEmpty()) {
+ return And(aRgn1, aRgn2.mBounds);
+ }
+
+ And(mBands, aRgn1.mBands, aRgn2.mBands);
+
+ if (!mBands.Length()) {
+ mBounds = nsRectAbsolute();
+ } else {
+ mBounds = CalculateBounds();
+ }
+
+ EnsureSimplified();
+ AssertState();
+ return *this;
+ }
+ nsRegion& And(const nsRect& aRect, const nsRegion& aRegion) {
+ return And(aRegion, aRect);
+ }
+ nsRegion& And(const nsRegion& aRegion, const nsRectAbsolute& aRect) {
+ if (&aRegion == this) {
+ return AndWith(aRect);
+ }
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorAnd : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorAnd(nsRegion& aThisRegion,
+ const nsRegion& aRegion,
+ const nsRectAbsolute& aRect)
+ : mThisRegion(&aThisRegion), mRegion(aRegion), mRect(aRect) {
+ aThisRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorAnd() {
+ mThisRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegion.OutputToStream("r", stream);
+ stream << "nsRegion r2;\nr.And(r2, nsRect(" << mRect.X() << ", "
+ << mRect.Y() << ", " << mRect.Width() << ", " << mRect.Height()
+ << "));\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mThisRegion;
+ nsRegion mRegion;
+ nsRectAbsolute mRect;
+ };
+
+ OperationStringGeneratorAnd opGenerator(*this, aRegion, aRect);
+#endif
+ mBands.Clear();
+
+ if (aRect.IsEmpty()) {
+ mBounds.SetEmpty();
+ return *this;
+ }
+
+ if (aRegion.mBands.IsEmpty()) {
+ mBounds = aRegion.mBounds.Intersect(aRect);
+ return *this;
+ }
+
+ size_t idx = 0;
+ const BandArray& bands = aRegion.mBands;
+
+ mBands.SetCapacity(bands.Length() + 3);
+ while (idx != bands.Length()) {
+ // Ignore anything before.
+ if (bands[idx].bottom <= aRect.Y()) {
+ idx++;
+ continue;
+ }
+ // We're done once we've reached the bottom.
+ if (bands[idx].top >= aRect.YMost()) {
+ break;
+ }
+
+ // Now deal with bands actually intersecting the rectangle.
+ Band newBand(bands[idx]);
+ newBand.top = std::max(bands[idx].top, aRect.Y());
+ newBand.bottom = std::min(bands[idx].bottom, aRect.YMost());
+
+ newBand.IntersectStrip(Strip(aRect.X(), aRect.XMost()));
+
+ if (newBand.mStrips.Length()) {
+ if (!mBands.IsEmpty() && newBand.top == mBands.LastElement().bottom &&
+ newBand.EqualStrips(mBands.LastElement())) {
+ mBands.LastElement().bottom = newBand.bottom;
+ } else {
+ mBands.AppendElement(std::move(newBand));
+ }
+ }
+ idx++;
+ }
+
+ if (mBands.Length()) {
+ mBounds = CalculateBounds();
+ } else {
+ mBounds.SetEmpty();
+ }
+
+ EnsureSimplified();
+ AssertState();
+ return *this;
+ }
+ nsRegion& And(const nsRegion& aRegion, const nsRect& aRect) {
+ return And(aRegion, nsRectAbsolute::FromRect(aRect));
+ }
+ nsRegion& And(const nsRect& aRect1, const nsRect& aRect2) {
+ nsRect tmpRect;
+
+ tmpRect.IntersectRect(aRect1, aRect2);
+ return Copy(tmpRect);
+ }
+
+ nsRegion& OrWith(const nsRegion& aOther) {
+ for (RectIterator idx(aOther); !idx.Done(); idx.Next()) {
+ AddRect(idx.GetAbsolute());
+ }
+ return *this;
+ }
+ nsRegion& OrWith(const nsRect& aOther) {
+ AddRect(nsRectAbsolute::FromRect(aOther));
+ return *this;
+ }
+ nsRegion& Or(const nsRegion& aRgn1, const nsRegion& aRgn2) {
+ if (&aRgn1 != this) {
+ *this = aRgn1;
+ }
+ for (RectIterator idx(aRgn2); !idx.Done(); idx.Next()) {
+ AddRect(idx.GetAbsolute());
+ }
+ return *this;
+ }
+ nsRegion& Or(const nsRegion& aRegion, const nsRect& aRect) {
+ if (&aRegion != this) {
+ *this = aRegion;
+ }
+ AddRect(nsRectAbsolute::FromRect(aRect));
+ return *this;
+ }
+ nsRegion& Or(const nsRect& aRect, const nsRegion& aRegion) {
+ return Or(aRegion, aRect);
+ }
+ nsRegion& Or(const nsRect& aRect1, const nsRect& aRect2) {
+ Copy(aRect1);
+ return Or(*this, aRect2);
+ }
+
+ nsRegion& XorWith(const nsRegion& aOther) { return Xor(*this, aOther); }
+ nsRegion& XorWith(const nsRect& aOther) { return Xor(*this, aOther); }
+ nsRegion& Xor(const nsRegion& aRgn1, const nsRegion& aRgn2) {
+ // this could be implemented better if pixman had direct
+ // support for xoring regions.
+ nsRegion p;
+ p.Sub(aRgn1, aRgn2);
+ nsRegion q;
+ q.Sub(aRgn2, aRgn1);
+ return Or(p, q);
+ }
+ nsRegion& Xor(const nsRegion& aRegion, const nsRect& aRect) {
+ return Xor(aRegion, nsRegion(aRect));
+ }
+ nsRegion& Xor(const nsRect& aRect, const nsRegion& aRegion) {
+ return Xor(nsRegion(aRect), aRegion);
+ }
+ nsRegion& Xor(const nsRect& aRect1, const nsRect& aRect2) {
+ return Xor(nsRegion(aRect1), nsRegion(aRect2));
+ }
+
+ nsRegion ToAppUnits(nscoord aAppUnitsPerPixel) const;
+
+ nsRegion& SubWith(const nsRegion& aOther) {
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorSubWith : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorSubWith(nsRegion& aRegion,
+ const nsRegion& aOtherRegion)
+ : mRegion(&aRegion),
+ mRegionCopy(aRegion),
+ mOtherRegion(aOtherRegion) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorSubWith() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionCopy.OutputToStream("r1", stream);
+ mOtherRegion.OutputToStream("r2", stream);
+ stream << "r1.SubWith(r2);\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionCopy;
+ nsRegion mOtherRegion;
+ };
+
+ OperationStringGeneratorSubWith opGenerator(*this, aOther);
+#endif
+
+ if (mBounds.IsEmpty()) {
+ return *this;
+ }
+
+ if (aOther.mBands.IsEmpty()) {
+ return SubWith(aOther.mBounds);
+ }
+
+ if (mBands.IsEmpty()) {
+ mBands.AppendElement(Band(mBounds));
+ }
+
+ size_t idx = 0;
+ size_t idxOther = 0;
+ while (idx < mBands.Length()) {
+ while (true) {
+ while (idx != mBands.Length() &&
+ mBands[idx].bottom <= aOther.mBands[idxOther].top) {
+ // Increment our current band until it ends beyond aOther's current
+ // band.
+ idx++;
+ }
+
+ if (idx == mBands.Length()) {
+ // This region is out of bands, the other region's future bands are
+ // ignored.
+ break;
+ }
+
+ while (idxOther != aOther.mBands.Length() &&
+ aOther.mBands[idxOther].bottom <= mBands[idx].top) {
+ // Increment aOther's current band until it ends beyond our current
+ // band.
+ idxOther++;
+ }
+
+ if (idxOther == aOther.mBands.Length()) {
+ // The other region's bands are all processed, all our future bands
+ // are ignored.
+ break;
+ }
+
+ if (aOther.mBands[idxOther].top < mBands[idx].bottom) {
+ // We know the other band's bottom lies beyond our band's top because
+ // otherwise we would've incremented above. Intersecting bands found.
+ break;
+ }
+ }
+
+ if (idx == mBands.Length() || idxOther == aOther.mBands.Length()) {
+ // The above loop executed a break because we're done.
+ break;
+ }
+
+ const Band& bandOther = aOther.mBands[idxOther];
+
+ if (!mBands[idx].Intersects(bandOther)) {
+ if (mBands[idx].bottom < bandOther.bottom) {
+ idx++;
+ } else {
+ idxOther++;
+ if (idxOther == aOther.mBands.Length()) {
+ break;
+ }
+ }
+ continue;
+ }
+
+ // These bands actually intersect.
+ if (mBands[idx].top < bandOther.top) {
+ mBands.InsertElementAt(idx + 1, Band(mBands[idx]));
+ mBands[idx].bottom = bandOther.top;
+ idx++;
+ mBands[idx].top = bandOther.top;
+ }
+
+ // mBands[idx].top >= bandOther.top;
+ if (mBands[idx].bottom <= bandOther.bottom) {
+ mBands[idx].SubStrips(bandOther);
+ if (mBands[idx].mStrips.IsEmpty()) {
+ mBands.RemoveElementAt(idx);
+ } else {
+ CompressBefore(idx);
+ idx++;
+ // The band before us just changed, it may be identical now.
+ CompressBefore(idx);
+ }
+ continue;
+ }
+
+ // mBands[idx].bottom > bandOther.bottom
+ Band newBand = mBands[idx];
+ newBand.SubStrips(bandOther);
+
+ if (!newBand.mStrips.IsEmpty()) {
+ mBands.InsertElementAt(idx, newBand);
+ mBands[idx].bottom = bandOther.bottom;
+ CompressBefore(idx);
+ idx++;
+ }
+
+ mBands[idx].top = bandOther.bottom;
+
+ idxOther++;
+ if (idxOther == aOther.mBands.Length()) {
+ break;
+ }
+ }
+
+ if (mBands.IsEmpty()) {
+ mBounds.SetEmpty();
+ } else {
+ mBounds = CalculateBounds();
+ }
+
+ AssertState();
+ EnsureSimplified();
+ return *this;
+ }
+ nsRegion& SubOut(const nsRegion& aOther) { return SubWith(aOther); }
+ nsRegion& SubOut(const nsRect& aOther) { return SubWith(aOther); }
+
+ private:
+ void AppendOrExtend(const Band& aNewBand) {
+ if (aNewBand.mStrips.IsEmpty()) {
+ return;
+ }
+ if (mBands.IsEmpty()) {
+ mBands.AppendElement(aNewBand);
+ return;
+ }
+
+ if (mBands.LastElement().bottom == aNewBand.top &&
+ mBands.LastElement().EqualStrips(aNewBand)) {
+ mBands.LastElement().bottom = aNewBand.bottom;
+ } else {
+ mBands.AppendElement(aNewBand);
+ }
+ }
+ void AppendOrExtend(const Band&& aNewBand) {
+ if (aNewBand.mStrips.IsEmpty()) {
+ return;
+ }
+ if (mBands.IsEmpty()) {
+ mBands.AppendElement(std::move(aNewBand));
+ return;
+ }
+
+ if (mBands.LastElement().bottom == aNewBand.top &&
+ mBands.LastElement().EqualStrips(aNewBand)) {
+ mBands.LastElement().bottom = aNewBand.bottom;
+ } else {
+ mBands.AppendElement(std::move(aNewBand));
+ }
+ }
+
+ public:
+ nsRegion& Sub(const nsRegion& aRgn1, const nsRegion& aRgn2) {
+ if (&aRgn1 == this) {
+ return SubWith(aRgn2);
+ }
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorSub : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorSub(nsRegion& aRegion, const nsRegion& aRgn1,
+ const nsRegion& aRgn2)
+ : mRegion(&aRegion), mRegion1(aRgn1), mRegion2(aRgn2) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorSub() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegion1.OutputToStream("r1", stream);
+ mRegion2.OutputToStream("r2", stream);
+ stream << "nsRegion r3;\nr3.Sub(r1, r2);\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegion1;
+ nsRegion mRegion2;
+ };
+
+ OperationStringGeneratorSub opGenerator(*this, aRgn1, aRgn2);
+#endif
+
+ mBands.Clear();
+
+ if (aRgn1.mBounds.IsEmpty()) {
+ mBounds.SetEmpty();
+ return *this;
+ }
+
+ if (aRgn2.mBounds.IsEmpty()) {
+ Copy(aRgn1);
+ return *this;
+ }
+
+ if (aRgn1.mBands.IsEmpty() && aRgn2.mBands.IsEmpty()) {
+ return Sub(aRgn1.mBounds, aRgn2.mBounds);
+ } else if (aRgn1.mBands.IsEmpty()) {
+ return Sub(aRgn1.mBounds, aRgn2);
+ } else if (aRgn2.mBands.IsEmpty()) {
+ return Sub(aRgn1, aRgn2.mBounds);
+ }
+
+ const BandArray& bands1 = aRgn1.mBands;
+ const BandArray& bands2 = aRgn2.mBands;
+
+ size_t idx = 0;
+ size_t idxOther = 0;
+
+ // We iterate the source region's bands, subtracting the other regions bands
+ // from them as we move them into ours.
+ while (idx < bands1.Length()) {
+ while (idxOther < bands2.Length() &&
+ bands2[idxOther].bottom <= bands1[idx].top) {
+ // These other bands are irrelevant as they don't intersect with the
+ // band we're currently processing.
+ idxOther++;
+ }
+ if (idxOther == bands2.Length()) {
+ break;
+ }
+
+ const Band& other = bands2[idxOther];
+
+ // bands2[idxOther].bottom >= bands1[idx].top
+ Band origBand(bands1[idx]);
+ if (other.top >= origBand.bottom) {
+ // No intersecting bands, append and continue.
+ AppendOrExtend(origBand);
+ idx++;
+ continue;
+ }
+
+ // Push a band for an uncovered region above our band.
+ if (origBand.top < other.top) {
+ Band newBand(origBand);
+ newBand.bottom = other.top;
+ AppendOrExtend(std::move(newBand));
+ }
+
+ int32_t lastBottom = std::max(other.top, origBand.top);
+ while (idxOther < bands2.Length() &&
+ bands2[idxOther].top < origBand.bottom) {
+ const Band& other = bands2[idxOther];
+ Band newBand(origBand);
+ newBand.top = std::max(origBand.top, other.top);
+ newBand.bottom = std::min(origBand.bottom, other.bottom);
+
+ // If there was a gap, we need to add the original band there.
+ if (newBand.top > lastBottom) {
+ Band betweenBand(newBand);
+ betweenBand.top = lastBottom;
+ betweenBand.bottom = newBand.top;
+ AppendOrExtend(std::move(betweenBand));
+ }
+
+ lastBottom = newBand.bottom;
+ newBand.SubStrips(other);
+ AppendOrExtend(std::move(newBand));
+ idxOther++;
+ }
+ // Decrement other here so we are back at the last band in region 2
+ // that intersected.
+ idxOther--;
+
+ if (bands2[idxOther].bottom < origBand.bottom) {
+ // The last band in region2 that intersected ended before this one,
+ // we can copy the rest.
+ Band newBand(origBand);
+ newBand.top = bands2[idxOther].bottom;
+ AppendOrExtend(std::move(newBand));
+ idxOther++;
+ }
+ idx++;
+ }
+
+ // Copy any remaining bands, the first one may have to be extended to fit
+ // the last one added before. The rest can be unconditionally appended.
+ if (idx < bands1.Length()) {
+ AppendOrExtend(bands1[idx]);
+ idx++;
+ }
+
+ while (idx < bands1.Length()) {
+ mBands.AppendElement(bands1[idx]);
+ idx++;
+ }
+
+ if (mBands.IsEmpty()) {
+ mBounds.SetEmpty();
+ } else {
+ mBounds = CalculateBounds();
+ }
+
+ AssertState();
+ EnsureSimplified();
+ return *this;
+ }
+
+ private:
+ // Internal helper for executing subtraction.
+ void RunSubtraction(const nsRectAbsolute& aRect) {
+ Strip rectStrip(aRect.X(), aRect.XMost());
+
+ size_t idx = 0;
+
+ while (idx < mBands.Length()) {
+ if (mBands[idx].top >= aRect.YMost()) {
+ return;
+ }
+
+ if (mBands[idx].bottom <= aRect.Y()) {
+ // This band is entirely before aRect, move on.
+ idx++;
+ continue;
+ }
+
+ if (!mBands[idx].Intersects(Strip(aRect.X(), aRect.XMost()))) {
+ // This band does not intersect aRect horizontally. Move on.
+ idx++;
+ continue;
+ }
+
+ // This band intersects with aRect.
+
+ if (mBands[idx].top < aRect.Y()) {
+ // This band starts above the start of aRect, split the band into two
+ // along the intersection, and continue to the next iteration to process
+ // the one that now intersects exactly.
+ auto above = mBands.InsertElementAt(idx, Band(mBands[idx]));
+ above->bottom = aRect.Y();
+ idx++;
+ mBands[idx].top = aRect.Y();
+ // Continue to run the loop for the next band.
+ continue;
+ }
+
+ if (mBands[idx].bottom <= aRect.YMost()) {
+ // This band ends before the end of aRect.
+ mBands[idx].SubStrip(rectStrip);
+ if (mBands[idx].mStrips.Length()) {
+ CompressAdjacentBands(idx);
+ } else {
+ mBands.RemoveElementAt(idx);
+ }
+ continue;
+ }
+
+ // This band extends beyond aRect.
+ Band newBand = mBands[idx];
+ newBand.SubStrip(rectStrip);
+ newBand.bottom = aRect.YMost();
+ mBands[idx].top = aRect.YMost();
+
+ if (newBand.mStrips.Length()) {
+ if (idx && mBands[idx - 1].bottom == newBand.top &&
+ newBand.EqualStrips(mBands[idx - 1])) {
+ mBands[idx - 1].bottom = aRect.YMost();
+ } else {
+ mBands.InsertElementAt(idx, std::move(newBand));
+ }
+ }
+
+ return;
+ }
+ }
+
+ public:
+ nsRegion& SubWith(const nsRectAbsolute& aRect) {
+ if (!mBounds.Intersects(aRect)) {
+ return *this;
+ }
+
+ if (aRect.Contains(mBounds)) {
+ SetEmpty();
+ return *this;
+ }
+
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorSubWith : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorSubWith(nsRegion& aRegion,
+ const nsRectAbsolute& aRect)
+ : mRegion(&aRegion), mRegionCopy(aRegion), mRect(aRect) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorSubWith() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionCopy.OutputToStream("r", stream);
+ stream << "r.SubWith(nsRect(" << mRect.X() << ", " << mRect.Y() << ", "
+ << mRect.Width() << ", " << mRect.Height() << "));\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionCopy;
+ nsRectAbsolute mRect;
+ };
+
+ OperationStringGeneratorSubWith opGenerator(*this, aRect);
+#endif
+
+ if (mBands.IsEmpty()) {
+ mBands.AppendElement(Band(mBounds));
+ }
+
+ RunSubtraction(aRect);
+
+ if (aRect.X() <= mBounds.X() || aRect.Y() <= mBounds.Y() ||
+ aRect.XMost() >= mBounds.XMost() || aRect.YMost() >= mBounds.YMost()) {
+ mBounds = CalculateBounds();
+ }
+ EnsureSimplified();
+ AssertState();
+ return *this;
+ }
+ nsRegion& Sub(const nsRegion& aRegion, const nsRectAbsolute& aRect) {
+ if (aRect.Contains(aRegion.mBounds)) {
+ SetEmpty();
+ return *this;
+ }
+ if (&aRegion == this) {
+ return SubWith(aRect);
+ }
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorSub : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorSub(nsRegion& aRegion,
+ const nsRegion& aRegionOther,
+ const nsRectAbsolute& aRect)
+ : mRegion(&aRegion), mRegionOther(aRegionOther), mRect(aRect) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorSub() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionOther.OutputToStream("r1", stream);
+ stream << "nsRegion r2;\nr2.Sub(r1, nsRect(" << mRect.X() << ", "
+ << mRect.Y() << ", " << mRect.Width() << ", " << mRect.Height()
+ << "));\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionOther;
+ nsRectAbsolute mRect;
+ };
+
+ OperationStringGeneratorSub opGenerator(*this, aRegion, aRect);
+#endif
+
+ mBands.Clear();
+
+ if (aRegion.mBounds.IsEmpty()) {
+ mBounds.SetEmpty();
+ return *this;
+ }
+
+ if (aRect.IsEmpty()) {
+ Copy(aRegion);
+ return *this;
+ }
+
+ if (aRegion.mBands.IsEmpty()) {
+ Copy(aRegion.mBounds);
+ return SubWith(aRect);
+ }
+
+ const BandArray& bands = aRegion.mBands;
+
+ size_t idx = 0;
+
+ Strip strip(aRect.X(), aRect.XMost());
+
+ mBands.SetCapacity(bands.Length() + 3);
+
+ // Process all bands that lie before aRect.
+ while (idx < bands.Length() && bands[idx].bottom <= aRect.Y()) {
+ mBands.AppendElement(bands[idx]);
+ idx++;
+ }
+
+ // This band's bottom lies beyond aRect.
+ if (idx < bands.Length() && bands[idx].top < aRect.Y()) {
+ Band newBand(bands[idx]);
+ if (bands[idx].Intersects(strip)) {
+ newBand.bottom = aRect.Y();
+ } else {
+ idx++;
+ }
+ mBands.AppendElement(std::move(newBand));
+ }
+
+ // This tracks whether the band when we -exit- the next loop intersected the
+ // rectangle.
+ bool didIntersect = false;
+
+ while (idx < bands.Length() && bands[idx].top < aRect.YMost()) {
+ // Process all bands intersecting with aRect.
+ if (!bands[idx].Intersects(strip)) {
+ AppendOrExtend(bands[idx]);
+ idx++;
+ didIntersect = false;
+ continue;
+ }
+
+ didIntersect = true;
+ Band newBand(bands[idx]);
+ newBand.top = std::max(newBand.top, aRect.Y());
+ newBand.bottom = std::min(newBand.bottom, aRect.YMost());
+ newBand.SubStrip(strip);
+ AppendOrExtend(std::move(newBand));
+ idx++;
+ }
+
+ if (didIntersect) {
+ if (aRect.YMost() < bands[idx - 1].bottom) {
+ // If this band does not intersect the loop above has already added the
+ // whole unmodified band.
+ Band newBand(bands[idx - 1]);
+ newBand.top = aRect.YMost();
+ AppendOrExtend(std::move(newBand));
+ }
+ }
+
+ // Now process all bands beyond aRect.
+ if (idx < bands.Length()) {
+ AppendOrExtend(bands[idx]);
+ idx++;
+ }
+
+ mBands.AppendElements(bands.Elements() + idx, bands.Length() - idx);
+
+ if (mBands.IsEmpty()) {
+ mBounds.SetEmpty();
+ } else {
+ mBounds = CalculateBounds();
+ }
+
+ AssertState();
+ EnsureSimplified();
+ return *this;
+ }
+ nsRegion& SubWith(const nsRect& aRect) {
+ return SubWith(nsRectAbsolute::FromRect(aRect));
+ }
+ nsRegion& Sub(const nsRect& aRect, const nsRegion& aRegion) {
+ Copy(aRect);
+ return SubWith(aRegion);
+ }
+ nsRegion& Sub(const nsRectAbsolute& aRect, const nsRegion& aRegion) {
+ Copy(aRect);
+ return SubWith(aRegion);
+ }
+ nsRegion& Sub(const nsRect& aRect1, const nsRect& aRect2) {
+ Copy(aRect1);
+ return SubWith(aRect2);
+ }
+ nsRegion& Sub(const nsRegion& aRegion, const nsRect& aRect) {
+ return Sub(aRegion, nsRectAbsolute::FromRect(aRect));
+ }
+ nsRegion& Sub(const nsRectAbsolute& aRect1, const nsRectAbsolute& aRect2) {
+ Copy(aRect1);
+ return SubWith(aRect2);
+ }
+
+ /**
+ * Returns true if the given point is inside the region. A region
+ * created from a rect (x=0, y=0, w=100, h=100) will NOT contain
+ * the point x=100, y=100.
+ */
+ bool Contains(int aX, int aY) const {
+ if (mBands.IsEmpty()) {
+ return mBounds.Contains(aX, aY);
+ }
+
+ auto iter = mBands.begin();
+
+ while (iter != mBands.end()) {
+ if (iter->bottom <= aY) {
+ iter++;
+ continue;
+ }
+
+ if (iter->top > aY) {
+ return false;
+ }
+
+ if (iter->ContainsStrip(Strip(aX, aX + 1))) {
+ return true;
+ }
+ return false;
+ }
+ return false;
+ }
+ bool Contains(const nsRectAbsolute& aRect) const {
+ if (aRect.IsEmpty()) {
+ return false;
+ }
+
+ if (mBands.IsEmpty()) {
+ return mBounds.Contains(aRect);
+ }
+
+ auto iter = mBands.begin();
+
+ while (iter != mBands.end()) {
+ if (iter->bottom <= aRect.Y()) {
+ iter++;
+ continue;
+ }
+
+ if (iter->top > aRect.Y()) {
+ return false;
+ }
+
+ // Now inside the rectangle.
+ if (!iter->ContainsStrip(Strip(aRect.X(), aRect.XMost()))) {
+ return false;
+ }
+
+ if (iter->bottom >= aRect.YMost()) {
+ return true;
+ }
+
+ int32_t lastY = iter->bottom;
+ iter++;
+ while (iter != mBands.end()) {
+ // Bands do not connect.
+ if (iter->top != lastY) {
+ return false;
+ }
+
+ if (!iter->ContainsStrip(Strip(aRect.X(), aRect.XMost()))) {
+ return false;
+ }
+
+ if (iter->bottom >= aRect.YMost()) {
+ return true;
+ }
+
+ lastY = iter->bottom;
+ iter++;
+ }
+ }
+ return false;
+ }
+ bool Contains(const nsRect& aRect) const {
+ return Contains(nsRectAbsolute::FromRect(aRect));
+ }
+
+ bool Contains(const nsRegion& aRgn) const;
+ bool Intersects(const nsRectAbsolute& aRect) const;
+ bool Intersects(const nsRect& aRect) const {
+ return Intersects(nsRectAbsolute::FromRect(aRect));
+ }
+
+ void MoveBy(int32_t aXOffset, int32_t aYOffset) {
+ MoveBy(nsPoint(aXOffset, aYOffset));
+ }
+ void MoveBy(nsPoint aPt) {
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorMoveBy : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorMoveBy(nsRegion& aRegion, const nsPoint& aPoint)
+ : mRegion(&aRegion), mRegionCopy(aRegion), mPoint(aPoint) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorMoveBy() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionCopy.OutputToStream("r", stream);
+ stream << "r.MoveBy(nsPoint(" << mPoint.x << ", " << mPoint.y
+ << "));\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionCopy;
+ nsPoint mPoint;
+ };
+
+ OperationStringGeneratorMoveBy opGenerator(*this, aPt);
+#endif
+
+ mBounds.MoveBy(aPt);
+ for (Band& band : mBands) {
+ band.top += aPt.Y();
+ band.bottom += aPt.Y();
+ for (Strip& strip : band.mStrips) {
+ strip.left += aPt.X();
+ strip.right += aPt.X();
+ }
+ }
+ AssertState();
+ }
+ void SetEmpty() {
+ mBands.Clear();
+ mBounds.SetEmpty();
+ }
+
+ nsRegion MovedBy(int32_t aXOffset, int32_t aYOffset) const {
+ return MovedBy(nsPoint(aXOffset, aYOffset));
+ }
+ nsRegion MovedBy(const nsPoint& aPt) const {
+ nsRegion copy(*this);
+ copy.MoveBy(aPt);
+ return copy;
+ }
+
+ nsRegion Intersect(const nsRegion& aOther) const {
+ nsRegion intersection;
+ intersection.And(*this, aOther);
+ return intersection;
+ }
+
+ void Inflate(const nsMargin& aMargin);
+
+ nsRegion Inflated(const nsMargin& aMargin) const {
+ nsRegion copy(*this);
+ copy.Inflate(aMargin);
+ return copy;
+ }
+
+ bool IsEmpty() const { return mBounds.IsEmpty(); }
+ bool IsComplex() const { return GetNumRects() > 1; }
+ bool IsEqual(const nsRegion& aRegion) const {
+ if (!mBounds.IsEqualInterior(aRegion.mBounds)) {
+ return false;
+ }
+
+ if (mBands.Length() != aRegion.mBands.Length()) {
+ return false;
+ }
+
+ for (auto iter1 = mBands.begin(), iter2 = aRegion.mBands.begin();
+ iter1 != mBands.end(); iter1++, iter2++) {
+ if (iter1->top != iter2->top || iter1->bottom != iter2->bottom ||
+ !iter1->EqualStrips(*iter2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ uint32_t GetNumRects() const {
+ if (mBands.IsEmpty()) {
+ return mBounds.IsEmpty() ? 0 : 1;
+ }
+
+ uint32_t rects = 0;
+
+ for (const Band& band : mBands) {
+ rects += band.mStrips.Length();
+ }
+
+ return rects;
+ }
+ const nsRect GetBounds() const { return mBounds.ToNSRect(); }
+ const nsRectAbsolute GetAbsoluteBounds() const { return mBounds; }
+ uint64_t Area() const;
+
+ /**
+ * Return this region scaled to a different appunits per pixel (APP) ratio.
+ * This applies nsRect::ScaleToOtherAppUnitsRoundOut/In to each rect of the
+ * region.
+ * @param aFromAPP the APP to scale from
+ * @param aToAPP the APP to scale to
+ * @note this can turn an empty region into a non-empty region
+ */
+ [[nodiscard]] nsRegion ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
+ int32_t aToAPP) const;
+ [[nodiscard]] nsRegion ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
+ int32_t aToAPP) const;
+ nsRegion& ScaleRoundOut(float aXScale, float aYScale);
+ nsRegion& ScaleInverseRoundOut(float aXScale, float aYScale);
+ nsRegion& Transform(const mozilla::gfx::Matrix4x4& aTransform);
+ nsIntRegion ScaleToOutsidePixels(float aXScale, float aYScale,
+ nscoord aAppUnitsPerPixel) const;
+ nsIntRegion ScaleToInsidePixels(float aXScale, float aYScale,
+ nscoord aAppUnitsPerPixel) const;
+ nsIntRegion ScaleToNearestPixels(float aXScale, float aYScale,
+ nscoord aAppUnitsPerPixel) const;
+ nsIntRegion ToOutsidePixels(nscoord aAppUnitsPerPixel) const;
+ nsIntRegion ToNearestPixels(nscoord aAppUnitsPerPixel) const;
+
+ /**
+ * Gets the largest rectangle contained in the region.
+ * @param aContainingRect if non-empty, we choose a rectangle that
+ * maximizes the area intersecting with aContainingRect (and break ties by
+ * then choosing the largest rectangle overall)
+ */
+ nsRect GetLargestRectangle(const nsRect& aContainingRect = nsRect()) const;
+
+ /**
+ * Make sure the region has at most aMaxRects by adding area to it
+ * if necessary. The simplified region will be a superset of the
+ * original region. The simplified region's bounding box will be
+ * the same as for the current region.
+ */
+ void SimplifyOutward(uint32_t aMaxRects);
+ /**
+ * Simplify the region by adding at most aThreshold area between spans of
+ * rects. The simplified region will be a superset of the original region.
+ * The simplified region's bounding box will be the same as for the current
+ * region.
+ */
+ void SimplifyOutwardByArea(uint32_t aThreshold);
+ /**
+ * Make sure the region has at most aMaxRects by removing area from
+ * it if necessary. The simplified region will be a subset of the
+ * original region.
+ */
+ void SimplifyInward(uint32_t aMaxRects);
+
+ /**
+ * VisitEdges is a weird kind of function that we use for padding
+ * out surfaces to prevent texture filtering artifacts.
+ * It calls the visitFn callback for each of the exterior edges of
+ * the regions. The top and bottom edges will be expanded 1 pixel
+ * to the left and right if there's an outside corner. The order
+ * the edges are visited is not guaranteed.
+ *
+ * visitFn has a side parameter that can be TOP,BOTTOM,LEFT,RIGHT
+ * and specifies which kind of edge is being visited. x1, y1, x2, y2
+ * are the coordinates of the line. (x1 == x2) || (y1 == y2)
+ */
+ typedef void (*visitFn)(void* closure, VisitSide side, int x1, int y1, int x2,
+ int y2);
+ void VisitEdges(visitFn, void* closure) const;
+
+ nsCString ToString() const;
+
+ static inline pixman_box32_t RectToBox(const nsRect& aRect) {
+ pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()};
+ return box;
+ }
+
+ static inline pixman_box32_t RectToBox(const mozilla::gfx::IntRect& aRect) {
+ pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()};
+ return box;
+ }
+
+ private:
+ nsIntRegion ToPixels(nscoord aAppUnitsPerPixel, bool aOutsidePixels) const;
+
+ nsRegion& Copy(const nsRegion& aRegion) {
+ mBounds = aRegion.mBounds;
+ mBands = aRegion.mBands.Clone();
+ return *this;
+ }
+
+ nsRegion& Copy(const nsRect& aRect) {
+ mBands.Clear();
+ mBounds = nsRectAbsolute::FromRect(aRect);
+ return *this;
+ }
+
+ nsRegion& Copy(const nsRectAbsolute& aRect) {
+ mBands.Clear();
+ mBounds = aRect;
+ return *this;
+ }
+
+ void EnsureSimplified() {
+ if (mBands.Length() == 1 && mBands.begin()->mStrips.Length() == 1) {
+ mBands.Clear();
+ }
+ }
+
+ static inline nsRectAbsolute BoxToRect(const pixman_box32_t& aBox) {
+ return nsRectAbsolute(aBox.x1, aBox.y1, aBox.x2, aBox.y2);
+ }
+
+ void AddRect(const nsRectAbsolute& aRect) {
+#ifdef DEBUG_REGIONS
+ class OperationStringGeneratorAddRect : public OperationStringGenerator {
+ public:
+ OperationStringGeneratorAddRect(nsRegion& aRegion,
+ const nsRectAbsolute& aRect)
+ : mRegion(&aRegion), mRegionCopy(aRegion), mRect(aRect) {
+ aRegion.mCurrentOpGenerator = this;
+ }
+ virtual ~OperationStringGeneratorAddRect() {
+ mRegion->mCurrentOpGenerator = nullptr;
+ }
+
+ virtual void OutputOp() override {
+ std::stringstream stream;
+ mRegionCopy.OutputToStream("r", stream);
+ stream << "r.OrWith(nsRect(" << mRect.X() << ", " << mRect.Y() << ", "
+ << mRect.Width() << ", " << mRect.Height() << "));\n";
+ gfxCriticalError() << stream.str();
+ }
+
+ private:
+ nsRegion* mRegion;
+ nsRegion mRegionCopy;
+ nsRectAbsolute mRect;
+ };
+
+ OperationStringGeneratorAddRect opGenerator(*this, aRect);
+#endif
+ if (aRect.IsEmpty()) {
+ return;
+ }
+
+ if (mBands.IsEmpty()) {
+ if (mBounds.IsEmpty()) {
+ mBounds = aRect;
+ return;
+ } else if (mBounds.Contains(aRect)) {
+ return;
+ }
+
+ mBands.AppendElement(Band(mBounds));
+ }
+
+ mBounds = aRect.UnsafeUnion(mBounds);
+
+ size_t idx = 0;
+
+ Strip strip(aRect.X(), aRect.XMost());
+ Band remaining(aRect);
+
+ while (idx != mBands.Length()) {
+ if (mBands[idx].bottom <= remaining.top) {
+ // This band lies wholly above aRect.
+ idx++;
+ continue;
+ }
+
+ if (remaining.top >= remaining.bottom) {
+ AssertState();
+ EnsureSimplified();
+ return;
+ }
+
+ if (mBands[idx].top >= remaining.bottom) {
+ // This band lies wholly below aRect.
+ break;
+ }
+
+ if (mBands[idx].EqualStrips(remaining)) {
+ mBands[idx].top = std::min(mBands[idx].top, remaining.top);
+ // Nothing to do for this band. Just expand.
+ remaining.top = mBands[idx].bottom;
+ CompressBefore(idx);
+ idx++;
+ continue;
+ }
+
+ if (mBands[idx].top > remaining.top) {
+ auto before = mBands.InsertElementAt(idx, remaining);
+ before->bottom = mBands[idx + 1].top;
+ remaining.top = before->bottom;
+ CompressBefore(idx);
+ idx++;
+ CompressBefore(idx);
+ continue;
+ }
+
+ if (mBands[idx].ContainsStrip(strip)) {
+ remaining.top = mBands[idx].bottom;
+ idx++;
+ continue;
+ }
+
+ // mBands[idx].top <= remaining.top.
+
+ if (mBands[idx].top < remaining.top) {
+ auto before = mBands.InsertElementAt(idx, Band(mBands[idx]));
+ before->bottom = remaining.top;
+ idx++;
+ mBands[idx].top = remaining.top;
+ continue;
+ }
+
+ // mBands[idx].top == remaining.top
+ if (mBands[idx].bottom > remaining.bottom) {
+ auto below = mBands.InsertElementAt(idx + 1, Band(mBands[idx]));
+ below->top = remaining.bottom;
+ mBands[idx].bottom = remaining.bottom;
+ }
+
+ mBands[idx].InsertStrip(strip);
+ CompressBefore(idx);
+ remaining.top = mBands[idx].bottom;
+ idx++;
+ CompressBefore(idx);
+ }
+
+ if (remaining.top < remaining.bottom) {
+ // We didn't find any bands that overlapped aRect.
+ if (idx) {
+ if (mBands[idx - 1].bottom == remaining.top &&
+ mBands[idx - 1].EqualStrips(remaining)) {
+ mBands[idx - 1].bottom = remaining.bottom;
+ CompressBefore(idx);
+ AssertState();
+ EnsureSimplified();
+ return;
+ }
+ }
+ mBands.InsertElementAt(idx, remaining);
+ idx++;
+ CompressBefore(idx);
+ } else {
+ CompressBefore(idx);
+ }
+
+ AssertState();
+ EnsureSimplified();
+ }
+
+ // Most callers could probably do this on the fly, if this ever shows up
+ // in profiles we could optimize this.
+ nsRectAbsolute CalculateBounds() const {
+ if (mBands.IsEmpty()) {
+ return mBounds;
+ }
+
+ int32_t top = mBands.begin()->top;
+ int32_t bottom = mBands.LastElement().bottom;
+
+ int32_t leftMost = mBands.begin()->mStrips.begin()->left;
+ int32_t rightMost = mBands.begin()->mStrips.LastElement().right;
+ for (const Band& band : mBands) {
+ leftMost = std::min(leftMost, band.mStrips.begin()->left);
+ rightMost = std::max(rightMost, band.mStrips.LastElement().right);
+ }
+
+ return nsRectAbsolute(leftMost, top, rightMost, bottom);
+ }
+
+ static uint32_t ComputeMergedAreaIncrease(const Band& aTopBand,
+ const Band& aBottomBand);
+
+ // Returns true if idx is now referring to the 'next' band
+ bool CompressAdjacentBands(size_t& aIdx) {
+ if ((aIdx + 1) < mBands.Length()) {
+ if (mBands[aIdx + 1].top == mBands[aIdx].bottom &&
+ mBands[aIdx + 1].EqualStrips(mBands[aIdx])) {
+ mBands[aIdx].bottom = mBands[aIdx + 1].bottom;
+ mBands.RemoveElementAt(aIdx + 1);
+ }
+ }
+ if (aIdx) {
+ if (mBands[aIdx - 1].bottom == mBands[aIdx].top &&
+ mBands[aIdx].EqualStrips(mBands[aIdx - 1])) {
+ mBands[aIdx - 1].bottom = mBands[aIdx].bottom;
+ mBands.RemoveElementAt(aIdx);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void CompressBefore(size_t& aIdx) {
+ if (aIdx && aIdx < mBands.Length()) {
+ if (mBands[aIdx - 1].bottom == mBands[aIdx].top &&
+ mBands[aIdx - 1].EqualStrips(mBands[aIdx])) {
+ mBands[aIdx].top = mBands[aIdx - 1].top;
+ mBands.RemoveElementAt(aIdx - 1);
+ aIdx--;
+ }
+ }
+ }
+
+ BandArray mBands;
+ // Considering we only ever OR with nsRects, the bounds should fit in an
+ // nsRect as well.
+ nsRectAbsolute mBounds;
+#ifdef DEBUG_REGIONS
+ friend class OperationStringGenerator;
+ OperationStringGenerator* mCurrentOpGenerator;
+#endif
+
+ public:
+ class RectIterator {
+ const nsRegion& mRegion;
+ typename BandArray::const_iterator mCurrentBand;
+ typename StripArray::const_iterator mCurrentStrip;
+
+ public:
+ explicit RectIterator(const nsRegion& aRegion)
+ : mRegion(aRegion),
+ mCurrentBand(aRegion.mBands.begin())
+#ifndef DEBUG
+ ,
+ mCurrentStrip(nullptr)
+#endif
+ {
+ mIsDone = mRegion.mBounds.IsEmpty();
+ if (mCurrentBand != aRegion.mBands.end()) {
+ mCurrentStrip = mCurrentBand->mStrips.begin();
+ }
+ }
+
+ bool Done() const { return mIsDone; }
+
+ const nsRect Get() const {
+ if (mRegion.mBands.IsEmpty()) {
+ return mRegion.GetBounds();
+ }
+ return nsRect(mCurrentStrip->left, mCurrentBand->top,
+ mCurrentStrip->right - mCurrentStrip->left,
+ mCurrentBand->bottom - mCurrentBand->top);
+ }
+
+ const nsRectAbsolute GetAbsolute() const {
+ if (mRegion.mBands.IsEmpty()) {
+ return mRegion.mBounds;
+ }
+ return nsRectAbsolute(mCurrentStrip->left, mCurrentBand->top,
+ mCurrentStrip->right, mCurrentBand->bottom);
+ }
+
+ void Next() {
+ if (mRegion.mBands.IsEmpty()) {
+ mIsDone = true;
+ return;
+ }
+
+ mCurrentStrip++;
+ if (mCurrentStrip == mCurrentBand->mStrips.end()) {
+ mCurrentBand++;
+ if (mCurrentBand != mRegion.mBands.end()) {
+ mCurrentStrip = mCurrentBand->mStrips.begin();
+ } else {
+ mIsDone = true;
+ }
+ }
+ }
+
+ bool mIsDone;
+ };
+
+ RectIterator RectIter() const { return RectIterator(*this); }
+};
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * BaseIntRegions use int32_t coordinates.
+ */
+template <typename Derived, typename Rect, typename Point, typename Margin>
+class BaseIntRegion {
+ friend class ::nsRegion;
+
+ // Give access to all specializations of IntRegionTyped, not just ones that
+ // derive from this specialization of BaseIntRegion.
+ template <typename units>
+ friend class IntRegionTyped;
+
+ public:
+ typedef Rect RectType;
+ typedef Point PointType;
+ typedef Margin MarginType;
+
+ BaseIntRegion() = default;
+ MOZ_IMPLICIT BaseIntRegion(const Rect& aRect) : mImpl(ToRect(aRect)) {}
+ explicit BaseIntRegion(mozilla::gfx::ArrayView<pixman_box32_t> aRects)
+ : mImpl(aRects) {}
+ BaseIntRegion(const BaseIntRegion& aRegion) : mImpl(aRegion.mImpl) {}
+ BaseIntRegion(BaseIntRegion&& aRegion) : mImpl(std::move(aRegion.mImpl)) {}
+ Derived& operator=(const Rect& aRect) {
+ mImpl = ToRect(aRect);
+ return This();
+ }
+ Derived& operator=(const Derived& aRegion) {
+ mImpl = aRegion.mImpl;
+ return This();
+ }
+ Derived& operator=(Derived&& aRegion) {
+ mImpl = std::move(aRegion.mImpl);
+ return This();
+ }
+
+ bool operator==(const Derived& aRgn) const { return IsEqual(aRgn); }
+ bool operator!=(const Derived& aRgn) const { return !(*this == aRgn); }
+
+ friend std::ostream& operator<<(std::ostream& stream, const Derived& m) {
+ return stream << m.mImpl;
+ }
+
+ void AndWith(const Derived& aOther) { And(This(), aOther); }
+ void AndWith(const Rect& aOther) { And(This(), aOther); }
+ Derived& And(const Derived& aRgn1, const Derived& aRgn2) {
+ mImpl.And(aRgn1.mImpl, aRgn2.mImpl);
+ return This();
+ }
+ Derived& And(const Derived& aRegion, const Rect& aRect) {
+ mImpl.And(aRegion.mImpl, ToRect(aRect));
+ return This();
+ }
+ Derived& And(const Rect& aRect, const Derived& aRegion) {
+ return And(aRegion, aRect);
+ }
+ Derived& And(const Rect& aRect1, const Rect& aRect2) {
+ Rect TmpRect;
+
+ TmpRect.IntersectRect(aRect1, aRect2);
+ mImpl = ToRect(TmpRect);
+ return This();
+ }
+
+ Derived& OrWith(const Derived& aOther) { return Or(This(), aOther); }
+ Derived& OrWith(const Rect& aOther) { return Or(This(), aOther); }
+ Derived& Or(const Derived& aRgn1, const Derived& aRgn2) {
+ mImpl.Or(aRgn1.mImpl, aRgn2.mImpl);
+ return This();
+ }
+ Derived& Or(const Derived& aRegion, const Rect& aRect) {
+ mImpl.Or(aRegion.mImpl, ToRect(aRect));
+ return This();
+ }
+ Derived& Or(const Rect& aRect, const Derived& aRegion) {
+ return Or(aRegion, aRect);
+ }
+ Derived& Or(const Rect& aRect1, const Rect& aRect2) {
+ mImpl = ToRect(aRect1);
+ return Or(This(), aRect2);
+ }
+
+ Derived& XorWith(const Derived& aOther) { return Xor(This(), aOther); }
+ Derived& XorWith(const Rect& aOther) { return Xor(This(), aOther); }
+ Derived& Xor(const Derived& aRgn1, const Derived& aRgn2) {
+ mImpl.Xor(aRgn1.mImpl, aRgn2.mImpl);
+ return This();
+ }
+ Derived& Xor(const Derived& aRegion, const Rect& aRect) {
+ mImpl.Xor(aRegion.mImpl, ToRect(aRect));
+ return This();
+ }
+ Derived& Xor(const Rect& aRect, const Derived& aRegion) {
+ return Xor(aRegion, aRect);
+ }
+ Derived& Xor(const Rect& aRect1, const Rect& aRect2) {
+ mImpl = ToRect(aRect1);
+ return Xor(This(), aRect2);
+ }
+
+ Derived& SubOut(const Derived& aOther) { return Sub(This(), aOther); }
+ Derived& SubOut(const Rect& aOther) { return Sub(This(), aOther); }
+ Derived& Sub(const Derived& aRgn1, const Derived& aRgn2) {
+ mImpl.Sub(aRgn1.mImpl, aRgn2.mImpl);
+ return This();
+ }
+ Derived& Sub(const Derived& aRegion, const Rect& aRect) {
+ mImpl.Sub(aRegion.mImpl, ToRect(aRect));
+ return This();
+ }
+ Derived& Sub(const Rect& aRect, const Derived& aRegion) {
+ return Sub(Derived(aRect), aRegion);
+ }
+ Derived& Sub(const Rect& aRect1, const Rect& aRect2) {
+ mImpl = ToRect(aRect1);
+ return Sub(This(), aRect2);
+ }
+
+ /**
+ * Returns true iff the given point is inside the region. A region
+ * created from a rect (x=0, y=0, w=100, h=100) will NOT contain
+ * the point x=100, y=100.
+ */
+ bool Contains(int aX, int aY) const { return mImpl.Contains(aX, aY); }
+ bool Contains(const Rect& aRect) const {
+ return mImpl.Contains(ToRect(aRect));
+ }
+ bool Contains(const Derived& aRgn) const {
+ return mImpl.Contains(aRgn.mImpl);
+ }
+ bool Intersects(const Rect& aRect) const {
+ return mImpl.Intersects(ToRect(aRect));
+ }
+
+ void MoveBy(int32_t aXOffset, int32_t aYOffset) {
+ MoveBy(Point(aXOffset, aYOffset));
+ }
+ void MoveBy(Point aPt) { mImpl.MoveBy(aPt.X(), aPt.Y()); }
+ Derived MovedBy(int32_t aXOffset, int32_t aYOffset) const {
+ return MovedBy(Point(aXOffset, aYOffset));
+ }
+ Derived MovedBy(const Point& aPt) const {
+ Derived copy(This());
+ copy.MoveBy(aPt);
+ return copy;
+ }
+
+ Derived Intersect(const Derived& aOther) const {
+ Derived intersection;
+ intersection.And(This(), aOther);
+ return intersection;
+ }
+
+ void Inflate(const Margin& aMargin) {
+ mImpl.Inflate(
+ nsMargin(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left));
+ }
+ Derived Inflated(const Margin& aMargin) const {
+ Derived copy(This());
+ copy.Inflate(aMargin);
+ return copy;
+ }
+
+ void SetEmpty() { mImpl.SetEmpty(); }
+
+ bool IsEmpty() const { return mImpl.IsEmpty(); }
+ bool IsComplex() const { return mImpl.IsComplex(); }
+ bool IsEqual(const Derived& aRegion) const {
+ return mImpl.IsEqual(aRegion.mImpl);
+ }
+ uint32_t GetNumRects() const { return mImpl.GetNumRects(); }
+ Rect GetBounds() const { return FromRect(mImpl.GetBounds()); }
+ uint64_t Area() const { return mImpl.Area(); }
+ nsRegion ToAppUnits(nscoord aAppUnitsPerPixel) const {
+ nsRegion result;
+ for (auto iter = RectIterator(*this); !iter.Done(); iter.Next()) {
+ nsRect appRect = ::ToAppUnits(iter.Get(), aAppUnitsPerPixel);
+ result.Or(result, appRect);
+ }
+ return result;
+ }
+ Rect GetLargestRectangle(const Rect& aContainingRect = Rect()) const {
+ return FromRect(mImpl.GetLargestRectangle(ToRect(aContainingRect)));
+ }
+
+ Derived& ScaleRoundOut(float aXScale, float aYScale) {
+ mImpl.ScaleRoundOut(aXScale, aYScale);
+ return This();
+ }
+
+ Derived& ScaleInverseRoundOut(float aXScale, float aYScale) {
+ mImpl.ScaleInverseRoundOut(aXScale, aYScale);
+ return This();
+ }
+
+ // Prefer using TransformBy(matrix, region) from UnitTransforms.h,
+ // as applying the transform should typically change the unit system.
+ // TODO(botond): Move this to IntRegionTyped and disable it for
+ // unit != UnknownUnits.
+ Derived& Transform(const mozilla::gfx::Matrix4x4& aTransform) {
+ mImpl.Transform(aTransform);
+ return This();
+ }
+
+ /**
+ * Make sure the region has at most aMaxRects by adding area to it
+ * if necessary. The simplified region will be a superset of the
+ * original region. The simplified region's bounding box will be
+ * the same as for the current region.
+ */
+ void SimplifyOutward(uint32_t aMaxRects) { mImpl.SimplifyOutward(aMaxRects); }
+ void SimplifyOutwardByArea(uint32_t aThreshold) {
+ mImpl.SimplifyOutwardByArea(aThreshold);
+ }
+ /**
+ * Make sure the region has at most aMaxRects by removing area from
+ * it if necessary. The simplified region will be a subset of the
+ * original region.
+ */
+ void SimplifyInward(uint32_t aMaxRects) { mImpl.SimplifyInward(aMaxRects); }
+
+ typedef void (*visitFn)(void* closure, VisitSide side, int x1, int y1, int x2,
+ int y2);
+ void VisitEdges(visitFn visit, void* closure) const {
+ mImpl.VisitEdges(visit, closure);
+ }
+
+ nsCString ToString() const { return mImpl.ToString(); }
+
+ class RectIterator {
+ nsRegion::RectIterator mImpl; // The underlying iterator.
+ mutable Rect mTmp; // The most recently gotten rectangle.
+
+ public:
+ explicit RectIterator(const BaseIntRegion& aRegion)
+ : mImpl(aRegion.mImpl) {}
+
+ bool Done() const { return mImpl.Done(); }
+
+ const Rect& Get() const {
+ mTmp = FromRect(mImpl.Get());
+ return mTmp;
+ }
+
+ void Next() { mImpl.Next(); }
+ };
+
+ RectIterator RectIter() const { return RectIterator(*this); }
+
+ protected:
+ // Expose enough to derived classes from them to define conversions
+ // between different types of BaseIntRegions.
+ explicit BaseIntRegion(const nsRegion& aImpl) : mImpl(aImpl) {}
+ const nsRegion& Impl() const { return mImpl; }
+
+ private:
+ nsRegion mImpl;
+
+ static nsRect ToRect(const Rect& aRect) {
+ return nsRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+ }
+ static Rect FromRect(const nsRect& aRect) {
+ return Rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+ }
+
+ Derived& This() { return *static_cast<Derived*>(this); }
+ const Derived& This() const { return *static_cast<const Derived*>(this); }
+};
+
+template <class units>
+class IntRegionTyped
+ : public BaseIntRegion<IntRegionTyped<units>, IntRectTyped<units>,
+ IntPointTyped<units>, IntMarginTyped<units>> {
+ typedef BaseIntRegion<IntRegionTyped<units>, IntRectTyped<units>,
+ IntPointTyped<units>, IntMarginTyped<units>>
+ Super;
+
+ // Make other specializations of IntRegionTyped friends.
+ template <typename OtherUnits>
+ friend class IntRegionTyped;
+
+ static_assert(IsPixel<units>::value,
+ "'units' must be a coordinate system tag");
+
+ public:
+ typedef IntRectTyped<units> RectType;
+ typedef IntPointTyped<units> PointType;
+ typedef IntMarginTyped<units> MarginType;
+
+ // Forward constructors.
+ IntRegionTyped() = default;
+ MOZ_IMPLICIT IntRegionTyped(const IntRectTyped<units>& aRect)
+ : Super(aRect) {}
+ IntRegionTyped(const IntRegionTyped& aRegion) : Super(aRegion) {}
+ explicit IntRegionTyped(mozilla::gfx::ArrayView<pixman_box32_t> aRects)
+ : Super(aRects) {}
+ IntRegionTyped(IntRegionTyped&& aRegion) : Super(std::move(aRegion)) {}
+
+ // Assignment operators need to be forwarded as well, otherwise the compiler
+ // will declare deleted ones.
+ IntRegionTyped& operator=(const IntRegionTyped& aRegion) {
+ return Super::operator=(aRegion);
+ }
+ IntRegionTyped& operator=(IntRegionTyped&& aRegion) {
+ return Super::operator=(std::move(aRegion));
+ }
+
+ static IntRegionTyped FromUnknownRegion(const IntRegion& aRegion) {
+ return IntRegionTyped(aRegion.Impl());
+ }
+ IntRegion ToUnknownRegion() const {
+ // Need |this->| because Impl() is defined in a dependent base class.
+ return IntRegion(this->Impl());
+ }
+
+ private:
+ // This is deliberately private, so calling code uses FromUnknownRegion().
+ explicit IntRegionTyped(const nsRegion& aRegion) : Super(aRegion) {}
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+typedef mozilla::gfx::IntRegion nsIntRegion;
+
+#endif
diff --git a/gfx/src/nsRegionFwd.h b/gfx/src/nsRegionFwd.h
new file mode 100644
index 0000000000..b029b35c25
--- /dev/null
+++ b/gfx/src/nsRegionFwd.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsRegionFwd_h__
+#define nsRegionFwd_h__
+
+// Forward declare enough things to define the typedef |nsIntRegion|.
+
+namespace mozilla {
+namespace gfx {
+
+struct UnknownUnits;
+
+template <class units>
+class IntRegionTyped;
+
+typedef IntRegionTyped<UnknownUnits> IntRegion;
+
+} // namespace gfx
+} // namespace mozilla
+
+typedef mozilla::gfx::IntRegion nsIntRegion;
+
+#endif
diff --git a/gfx/src/nsSize.h b/gfx/src/nsSize.h
new file mode 100644
index 0000000000..21aa654877
--- /dev/null
+++ b/gfx/src/nsSize.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef NSSIZE_H
+#define NSSIZE_H
+
+#include "nsCoord.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/Point.h"
+
+// Maximum allowable size
+#define NS_MAXSIZE nscoord_MAX
+
+typedef mozilla::gfx::IntSize nsIntSize;
+
+struct nsSize : public mozilla::gfx::BaseSize<nscoord, nsSize> {
+ typedef mozilla::gfx::BaseSize<nscoord, nsSize> Super;
+
+ constexpr nsSize() : Super() {}
+ constexpr nsSize(nscoord aWidth, nscoord aHeight) : Super(aWidth, aHeight) {}
+
+ inline mozilla::gfx::IntSize ScaleToNearestPixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const;
+ inline mozilla::gfx::IntSize ToNearestPixels(nscoord aAppUnitsPerPixel) const;
+
+ /**
+ * Return this size scaled to a different appunits per pixel (APP) ratio.
+ * @param aFromAPP the APP to scale from
+ * @param aToAPP the APP to scale to
+ */
+ [[nodiscard]] inline nsSize ScaleToOtherAppUnits(int32_t aFromAPP,
+ int32_t aToAPP) const;
+};
+
+inline mozilla::gfx::IntSize nsSize::ScaleToNearestPixels(
+ float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const {
+ return mozilla::gfx::IntSize(
+ NSToIntRoundUp(NSAppUnitsToDoublePixels(width, aAppUnitsPerPixel) *
+ aXScale),
+ NSToIntRoundUp(NSAppUnitsToDoublePixels(height, aAppUnitsPerPixel) *
+ aYScale));
+}
+
+inline mozilla::gfx::IntSize nsSize::ToNearestPixels(
+ nscoord aAppUnitsPerPixel) const {
+ return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel);
+}
+
+inline nsSize nsSize::ScaleToOtherAppUnits(int32_t aFromAPP,
+ int32_t aToAPP) const {
+ if (aFromAPP != aToAPP) {
+ nsSize size;
+ size.width = NSToCoordRound(NSCoordScale(width, aFromAPP, aToAPP));
+ size.height = NSToCoordRound(NSCoordScale(height, aFromAPP, aToAPP));
+ return size;
+ }
+ return *this;
+}
+
+inline nsSize IntSizeToAppUnits(mozilla::gfx::IntSize aSize,
+ nscoord aAppUnitsPerPixel) {
+ return nsSize(NSIntPixelsToAppUnits(aSize.width, aAppUnitsPerPixel),
+ NSIntPixelsToAppUnits(aSize.height, aAppUnitsPerPixel));
+}
+
+#endif /* NSSIZE_H */
diff --git a/gfx/src/nsThebesFontEnumerator.cpp b/gfx/src/nsThebesFontEnumerator.cpp
new file mode 100644
index 0000000000..f85beab0c7
--- /dev/null
+++ b/gfx/src/nsThebesFontEnumerator.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThebesFontEnumerator.h"
+#include <stdint.h> // for uint32_t
+#include "gfxPlatform.h" // for gfxPlatform
+#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Promise.h" // for mozilla::dom::Promise
+#include "nsCOMPtr.h" // for nsCOMPtr
+#include "nsDebug.h" // for NS_ENSURE_ARG_POINTER
+#include "nsError.h" // for NS_OK, NS_FAILED, nsresult
+#include "nsAtom.h" // for nsAtom, NS_Atomize
+#include "nsID.h"
+#include "nsMemory.h" // for nsMemory
+#include "nsString.h" // for nsAutoCString, nsAutoString, etc
+#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc
+#include "nscore.h" // for char16_t, NS_IMETHODIMP
+
+using mozilla::MakeUnique;
+using mozilla::Runnable;
+using mozilla::UniquePtr;
+
+NS_IMPL_ISUPPORTS(nsThebesFontEnumerator, nsIFontEnumerator)
+
+nsThebesFontEnumerator::nsThebesFontEnumerator() = default;
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::EnumerateAllFonts(nsTArray<nsString>& aResult) {
+ return EnumerateFonts(nullptr, nullptr, aResult);
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::EnumerateFonts(const char* aLangGroup,
+ const char* aGeneric,
+ nsTArray<nsString>& aResult) {
+ nsAutoCString generic;
+ if (aGeneric)
+ generic.Assign(aGeneric);
+ else
+ generic.SetIsVoid(true);
+
+ RefPtr<nsAtom> langGroupAtom;
+ if (aLangGroup) {
+ nsAutoCString lowered;
+ lowered.Assign(aLangGroup);
+ ToLowerCase(lowered);
+ langGroupAtom = NS_Atomize(lowered);
+ }
+
+ return gfxPlatform::GetPlatform()->GetFontList(langGroupAtom, generic,
+ aResult);
+}
+
+struct EnumerateFontsPromise final {
+ explicit EnumerateFontsPromise(mozilla::dom::Promise* aPromise)
+ : mPromise(aPromise) {
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ RefPtr<mozilla::dom::Promise> mPromise;
+};
+
+class EnumerateFontsResult final : public Runnable {
+ public:
+ EnumerateFontsResult(nsresult aRv,
+ UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise,
+ nsTArray<nsString> aFontList)
+ : Runnable("EnumerateFontsResult"),
+ mRv(aRv),
+ mEnumerateFontsPromise(std::move(aEnumerateFontsPromise)),
+ mFontList(std::move(aFontList)),
+ mWorkerThread(do_GetCurrentThread()) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(mRv)) {
+ mEnumerateFontsPromise->mPromise->MaybeReject(mRv);
+ } else {
+ mEnumerateFontsPromise->mPromise->MaybeResolve(mFontList);
+ }
+
+ mWorkerThread->Shutdown();
+
+ return NS_OK;
+ }
+
+ private:
+ nsresult mRv;
+ UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
+ nsTArray<nsString> mFontList;
+ nsCOMPtr<nsIThread> mWorkerThread;
+};
+
+class EnumerateFontsTask final : public Runnable {
+ public:
+ EnumerateFontsTask(nsAtom* aLangGroupAtom, const nsAutoCString& aGeneric,
+ UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise,
+ nsIEventTarget* aMainThreadTarget)
+ : Runnable("EnumerateFontsTask"),
+ mLangGroupAtom(aLangGroupAtom),
+ mGeneric(aGeneric),
+ mEnumerateFontsPromise(std::move(aEnumerateFontsPromise)),
+ mMainThreadTarget(aMainThreadTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsTArray<nsString> fontList;
+
+ nsresult rv = gfxPlatform::GetPlatform()->GetFontList(mLangGroupAtom,
+ mGeneric, fontList);
+ nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsResult(
+ rv, std::move(mEnumerateFontsPromise), std::move(fontList));
+ mMainThreadTarget->Dispatch(runnable.forget());
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsAtom> mLangGroupAtom;
+ nsAutoCStringN<16> mGeneric;
+ UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
+ RefPtr<nsIEventTarget> mMainThreadTarget;
+};
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::EnumerateAllFontsAsync(JSContext* aCx,
+ JS::MutableHandleValue aRval) {
+ return EnumerateFontsAsync(nullptr, nullptr, aCx, aRval);
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::EnumerateFontsAsync(const char* aLangGroup,
+ const char* aGeneric,
+ JSContext* aCx,
+ JS::MutableHandleValue aRval) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+
+ mozilla::ErrorResult errv;
+ RefPtr<mozilla::dom::Promise> promise =
+ mozilla::dom::Promise::Create(global, errv);
+ if (errv.Failed()) {
+ return errv.StealNSResult();
+ }
+
+ auto enumerateFontsPromise = MakeUnique<EnumerateFontsPromise>(promise);
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread("FontEnumThread", getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsAtom> langGroupAtom;
+ if (aLangGroup) {
+ nsAutoCStringN<16> lowered;
+ lowered.Assign(aLangGroup);
+ ToLowerCase(lowered);
+ langGroupAtom = NS_Atomize(lowered);
+ }
+
+ nsAutoCString generic;
+ if (aGeneric) {
+ generic.Assign(aGeneric);
+ } else {
+ generic.SetIsVoid(true);
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ global->EventTargetFor(mozilla::TaskCategory::Other);
+ nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsTask(
+ langGroupAtom, generic, std::move(enumerateFontsPromise), target);
+ thread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+
+ if (!ToJSValue(aCx, promise, aRval)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::HaveFontFor(const char* aLangGroup, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::GetDefaultFont(const char* aLangGroup,
+ const char* aGeneric,
+ char16_t** aResult) {
+ if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aLangGroup) ||
+ NS_WARN_IF(!aGeneric)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = nullptr;
+ nsAutoCString defaultFontName(gfxPlatform::GetPlatform()->GetDefaultFontName(
+ nsDependentCString(aLangGroup), nsDependentCString(aGeneric)));
+ if (!defaultFontName.IsEmpty()) {
+ *aResult = UTF8ToNewUnicode(defaultFontName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::UpdateFontList(bool* _retval) {
+ gfxPlatform::GetPlatform()->UpdateFontList();
+ *_retval = false; // always return false for now
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::GetStandardFamilyName(const char16_t* aName,
+ char16_t** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aName);
+
+ nsAutoString name(aName);
+ if (name.IsEmpty()) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ nsAutoCString family;
+ gfxPlatform::GetPlatform()->GetStandardFamilyName(
+ NS_ConvertUTF16toUTF8(aName), family);
+ if (family.IsEmpty()) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+ *aResult = UTF8ToNewUnicode(family);
+ return NS_OK;
+}
diff --git a/gfx/src/nsThebesFontEnumerator.h b/gfx/src/nsThebesFontEnumerator.h
new file mode 100644
index 0000000000..c0f2d79b68
--- /dev/null
+++ b/gfx/src/nsThebesFontEnumerator.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef _NSTHEBESFONTENUMERATOR_H_
+#define _NSTHEBESFONTENUMERATOR_H_
+
+#include "mozilla/Attributes.h" // for final
+#include "nsIFontEnumerator.h" // for NS_DECL_NSIFONTENUMERATOR, etc
+#include "nsISupports.h" // for NS_DECL_ISUPPORTS
+
+class nsThebesFontEnumerator final : public nsIFontEnumerator {
+ ~nsThebesFontEnumerator() = default;
+
+ public:
+ nsThebesFontEnumerator();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIFONTENUMERATOR
+};
+
+#endif /* _NSTHEBESFONTENUMERATOR_H_ */
diff --git a/gfx/src/nsTransform2D.cpp b/gfx/src/nsTransform2D.cpp
new file mode 100644
index 0000000000..655511a626
--- /dev/null
+++ b/gfx/src/nsTransform2D.cpp
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTransform2D.h"
+
+void nsTransform2D ::TransformCoord(nscoord* ptX, nscoord* ptY) const {
+ *ptX = NSToCoordRound(*ptX * m00 + m20);
+ *ptY = NSToCoordRound(*ptY * m11 + m21);
+}
+
+void nsTransform2D ::TransformCoord(nscoord* aX, nscoord* aY, nscoord* aWidth,
+ nscoord* aHeight) const {
+ nscoord x2 = *aX + *aWidth;
+ nscoord y2 = *aY + *aHeight;
+ TransformCoord(aX, aY);
+ TransformCoord(&x2, &y2);
+ *aWidth = x2 - *aX;
+ *aHeight = y2 - *aY;
+}
diff --git a/gfx/src/nsTransform2D.h b/gfx/src/nsTransform2D.h
new file mode 100644
index 0000000000..fb04f16a12
--- /dev/null
+++ b/gfx/src/nsTransform2D.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsTransform2D_h___
+#define nsTransform2D_h___
+
+#include "nsCoord.h"
+
+class nsTransform2D {
+ private:
+ /**
+ * This represents the following matrix (note that the order of row/column
+ * indices is opposite to usual notation)
+ *
+ * / m00 0 m20 \
+ * M = | 0 m11 m21 |
+ * \ 0 0 1 /
+ *
+ * Transformation of a coordinate (x, y) is obtained by setting
+ * v = (x, y, 1)^T and evaluating M . v
+ **/
+
+ float m00, m11, m20, m21;
+
+ public:
+ nsTransform2D(void) {
+ m20 = m21 = 0.0f;
+ m00 = m11 = 1.0f;
+ }
+
+ ~nsTransform2D(void) = default;
+
+ /**
+ * set this transform to a translation
+ *
+ * @param tx, x translation
+ * @param ty, y translation
+ **/
+
+ void SetToTranslate(float tx, float ty) {
+ m00 = m11 = 1.0f;
+ m20 = tx;
+ m21 = ty;
+ }
+
+ /**
+ * get the translation portion of this transform
+ *
+ * @param pt, Point to return translation values in
+ **/
+
+ void GetTranslationCoord(nscoord* ptX, nscoord* ptY) const {
+ *ptX = NSToCoordRound(m20);
+ *ptY = NSToCoordRound(m21);
+ }
+
+ /**
+ * apply matrix to vector
+ *
+ * @param pt Point to transform
+ **/
+
+ void TransformCoord(nscoord* ptX, nscoord* ptY) const;
+
+ /**
+ * apply matrix to rect
+ *
+ * @param rect Rect to transform
+ **/
+
+ void TransformCoord(nscoord* aX, nscoord* aY, nscoord* aWidth,
+ nscoord* aHeight) const;
+
+ /**
+ * add a scale to a Transform via x, y pair
+ *
+ * @param ptX x value to add as x scale
+ * @param ptY y value to add as y scale
+ **/
+
+ void AddScale(float ptX, float ptY) {
+ m00 *= ptX;
+ m11 *= ptY;
+ }
+
+ /**
+ * Set the scale (overriding any previous calls to AddScale, but leaving
+ * any existing translation).
+ *
+ * @param ptX x value to add as x scale
+ * @param ptY y value to add as y scale
+ **/
+
+ void SetScale(float ptX, float ptY) {
+ m00 = ptX;
+ m11 = ptY;
+ }
+};
+
+#endif