diff options
Diffstat (limited to 'gfx/src')
51 files changed, 12233 insertions, 0 deletions
diff --git a/gfx/src/AppUnits.h b/gfx/src/AppUnits.h new file mode 100644 index 000000000..e0ad6dc52 --- /dev/null +++ b/gfx/src/AppUnits.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 { +inline int32_t AppUnitsPerCSSPixel() { return 60; } +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 000000000..3c6704f43 --- /dev/null +++ b/gfx/src/ArrayView.h @@ -0,0 +1,50 @@ +/* -*- 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/DriverCrashGuard.cpp b/gfx/src/DriverCrashGuard.cpp new file mode 100644 index 000000000..36d08dcf3 --- /dev/null +++ b/gfx/src/DriverCrashGuard.cpp @@ -0,0 +1,620 @@ +/* -*- Mode: C++; 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 "DriverCrashGuard.h"
+#include "gfxEnv.h"
+#include "gfxPrefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.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",
+ "d3d9video",
+ "glcontext",
+ "d3d11video",
+};
+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.Assign("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()
+{
+ // Crash guard isn't supported in the GPU process since the entire + // process is basically a crash guard. + if (XRE_IsGPUProcess()) { + 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.
+ return gfxEnv::ForceCrashGuardNightly();
+#else
+ // Check to see if all guards have been disabled through the environment.
+ if (gfxEnv::DisableCrashGuard()) {
+ return false;
+ }
+ return true;
+#endif
+}
+
+void
+DriverCrashGuard::Initialize()
+{
+ if (!AreCrashGuardsEnabled()) {
+ 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));
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ // Remove the crash report annotation.
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
+ NS_LITERAL_CSTRING(""));
+#endif
+}
+
+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.Append(".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;
+
+#ifdef MOZ_CRASHREPORTER
+ // 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(NS_LITERAL_CSTRING("GraphicsStartupTest"),
+ NS_LITERAL_CSTRING("1"));
+ }
+#endif
+
+ // 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()
+{
+ CheckOrRefreshEnvironment();
+ 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.
+ static bool sBaseInfoChanged = false;
+ static bool sBaseInfoChecked = false;
+
+ if (!sBaseInfoChecked) {
+ // None of the prefs we care about, so we cache the result statically.
+ sBaseInfoChecked = true;
+ sBaseInfoChanged = UpdateBaseEnvironment();
+ }
+
+ // Always update the full environment, even if the base info didn't change.
+ return UpdateEnvironment() ||
+ sBaseInfoChanged ||
+ GetStatus() == DriverInitStatus::Unknown;
+}
+
+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(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);
+
+ nsAdoptingString oldValue = Preferences::GetString(pref.c_str());
+ 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()) {
+ prefService->SavePrefFile(nullptr);
+ }
+}
+
+void
+DriverCrashGuard::ForEachActiveCrashGuard(const CrashGuardCallback& aCallback)
+{
+ if (!AreCrashGuardsEnabled()) {
+ // Even if guards look active (via prefs), they can be ignored if globally
+ // disabled.
+ return;
+ }
+
+ for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) {
+ CrashGuardType type = static_cast<CrashGuardType>(i);
+
+ 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;
+ static bool changed = false;
+
+ if (checked) {
+ return changed;
+ }
+
+ checked = true;
+
+ // Feature status.
+#if defined(XP_WIN)
+ bool d2dEnabled = gfxPrefs::Direct2DForceEnabled() ||
+ (!gfxPrefs::Direct2DDisabled() && FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
+ changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled);
+
+ bool d3d11Enabled = !gfxPrefs::LayersPreferD3D9();
+ if (!FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS)) {
+ d3d11Enabled = false;
+ }
+ changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled);
+#endif
+
+ if (!changed) {
+ return false;
+ }
+
+ RecordTelemetry(TelemetryState::EnvironmentChanged);
+ return true;
+}
+
+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;
+}
+
+D3D9VideoCrashGuard::D3D9VideoCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::D3D9Video, aContentParent)
+{
+}
+
+bool
+D3D9VideoCrashGuard::UpdateEnvironment()
+{
+ // We don't care about any extra preferences here.
+ return false;
+}
+
+void
+D3D9VideoCrashGuard::LogCrashRecovery()
+{
+ gfxCriticalNote << "DXVA2D3D9 just crashed; hardware video will be disabled.";
+}
+
+void
+D3D9VideoCrashGuard::LogFeatureDisabled()
+{
+ gfxCriticalNote << "DXVA2D3D9 video decoding is disabled due to a previous crash.";
+}
+
+D3D11VideoCrashGuard::D3D11VideoCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::D3D11Video, aContentParent)
+{
+}
+
+bool
+D3D11VideoCrashGuard::UpdateEnvironment()
+{
+ // We don't care about any extra preferences here.
+ return false;
+}
+
+void
+D3D11VideoCrashGuard::LogCrashRecovery()
+{
+ gfxCriticalNote << "DXVA2D3D11 just crashed; hardware video will be disabled.";
+}
+
+void
+D3D11VideoCrashGuard::LogFeatureDisabled()
+{
+ gfxCriticalNote << "DXVA2D3D11 video decoding is disabled due to a previous crash.";
+}
+
+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;
+ static bool changed = false;
+
+ if (checked) {
+ return changed;
+ }
+
+ checked = true;
+
+#if defined(XP_WIN)
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11",
+ gfxPrefs::WebGLANGLEForceD3D11());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11",
+ gfxPrefs::WebGLANGLETryD3D11());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp",
+ gfxPrefs::WebGLANGLEForceWARP());
+ 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.";
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/src/DriverCrashGuard.h b/gfx/src/DriverCrashGuard.h new file mode 100644 index 000000000..9a0c5851a --- /dev/null +++ b/gfx/src/DriverCrashGuard.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; 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/. */
+#ifndef gfx_src_DriverCrashGuard_h__
+#define gfx_src_DriverCrashGuard_h__
+
+#include "nsCOMPtr.h"
+#include "nsIGfxInfo.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "mozilla/Function.h"
+#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,
+ D3D9Video,
+ GLContext,
+ D3D11Video,
+ // Add new entries above this line, update the name array in
+ // DriverCrashGuard.cpp, and make sure to add an entry in
+ // ContentParent.cpp.
+
+ 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 mozilla::function<void(const char* aName, const char* aPrefName)>
+ CrashGuardCallback;
+ static void ForEachActiveCrashGuard(const CrashGuardCallback& aCallback);
+
+protected:
+ virtual void Initialize();
+ virtual bool UpdateEnvironment() = 0;
+ 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 D3D9VideoCrashGuard final : public DriverCrashGuard
+{
+ public:
+ explicit D3D9VideoCrashGuard(dom::ContentParent* aContentParent = nullptr);
+
+ protected:
+ bool UpdateEnvironment() override;
+ void LogCrashRecovery() override;
+ void LogFeatureDisabled() override;
+};
+
+class D3D11VideoCrashGuard final : public DriverCrashGuard
+{
+ public:
+ explicit D3D11VideoCrashGuard(dom::ContentParent* aContentParent = nullptr);
+
+ protected:
+ bool UpdateEnvironment() override;
+ void LogCrashRecovery() override;
+ void LogFeatureDisabled() override;
+};
+
+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;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // gfx_src_DriverCrashGuard_h__
+
diff --git a/gfx/src/FilterSupport.cpp b/gfx/src/FilterSupport.cpp new file mode 100644 index 000000000..fed7b6879 --- /dev/null +++ b/gfx/src/FilterSupport.cpp @@ -0,0 +1,2185 @@ +/* -*- Mode: C++; 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 "FilterSupport.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Filters.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/PodOperations.h" + +#include "gfxContext.h" +#include "gfxPattern.h" +#include "gfxPlatform.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) +static 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; + } + + static already_AddRefed<FilterNode> + Clear(DrawTarget* aDT) + { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::FLOOD); + if (filter) { + filter->SetAttribute(ATT_FLOOD_COLOR, Color(0, 0, 0, 0)); + return filter.forget(); + } + return nullptr; + } + + static 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() {} +}; + +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. +static nsresult +ComputeColorMatrix(uint32_t aColorMatrixType, const nsTArray<float>& aValues, + 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 (aColorMatrixType) { + + case SVG_FECOLORMATRIX_TYPE_MATRIX: + { + if (aValues.Length() != 20) { + return NS_ERROR_FAILURE; + } + + PodCopy(aOutMatrix, aValues.Elements(), 20); + break; + } + + case SVG_FECOLORMATRIX_TYPE_SATURATE: + { + if (aValues.Length() != 1) + return NS_ERROR_FAILURE; + + float s = aValues[0]; + + if (s < 0) + return NS_ERROR_FAILURE; + + InterpolateFromIdentityMatrix(saturateMatrix, 1 - s, aOutMatrix); + break; + } + + case SVG_FECOLORMATRIX_TYPE_HUE_ROTATE: + { + if (aValues.Length() != 1) + return NS_ERROR_FAILURE; + + PodCopy(aOutMatrix, identityMatrix, 20); + + float hueRotateValue = aValues[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 (aValues.Length() != 1) + return NS_ERROR_FAILURE; + + float amount = aValues[0]; + + if (amount < 0 || amount > 1) + return NS_ERROR_FAILURE; + + InterpolateFromIdentityMatrix(sepiaMatrix, amount, aOutMatrix); + break; + } + + default: + return NS_ERROR_FAILURE; + + } + + return NS_OK; +} + +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 AttributeMap& aFunctionAttributes, + int32_t aChannel, + 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.GetUint(eComponentTransferFunctionType); + + switch (type) { + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: + { + const nsTArray<float>& tableValues = + aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues); + 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[aChannel], false); + filter->SetAttribute(tableAtt[aChannel], &tableValues[0], tableValues.Length()); + break; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: + { + const nsTArray<float>& tableValues = + aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues); + 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[aChannel], false); + filter->SetAttribute(tableAtt[aChannel], &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[aChannel], false); + float slope = aFunctionAttributes.GetFloat(eComponentTransferFunctionSlope); + float intercept = aFunctionAttributes.GetFloat(eComponentTransferFunctionIntercept); + filter->SetAttribute(slopeAtt[aChannel], slope); + filter->SetAttribute(interceptAtt[aChannel], 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[aChannel], false); + float amplitude = aFunctionAttributes.GetFloat(eComponentTransferFunctionAmplitude); + float exponent = aFunctionAttributes.GetFloat(eComponentTransferFunctionExponent); + float offset = aFunctionAttributes.GetFloat(eComponentTransferFunctionOffset); + filter->SetAttribute(amplitudeAtt[aChannel], amplitude); + filter->SetAttribute(exponentAtt[aChannel], exponent); + filter->SetAttribute(offsetAtt[aChannel], 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) +{ + const AttributeMap& atts = aDescription.Attributes(); + switch (aDescription.Type()) { + + case PrimitiveType::Empty: + return nullptr; + + case PrimitiveType::Blend: + { + uint32_t mode = atts.GetUint(eBlendBlendmode); + RefPtr<FilterNode> filter; + if (mode == SVG_FEBLEND_MODE_UNKNOWN) { + return nullptr; + } + if (mode == SVG_FEBLEND_MODE_NORMAL) { + filter = aDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + filter->SetInput(IN_COMPOSITE_IN_START, aSources[1]); + filter->SetInput(IN_COMPOSITE_IN_START + 1, aSources[0]); + } else { + filter = aDT->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, aSources[1]); + filter->SetInput(IN_BLEND_IN2, aSources[0]); + } + return filter.forget(); + } + + case PrimitiveType::ColorMatrix: + { + float colorMatrix[20]; + uint32_t type = atts.GetUint(eColorMatrixType); + const nsTArray<float>& values = atts.GetFloats(eColorMatrixValues); + if (NS_FAILED(ComputeColorMatrix(type, values, colorMatrix)) || + PodEqual(colorMatrix, identityMatrix)) { + RefPtr<FilterNode> filter(aSources[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 = aDT->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, aSources[0]); + return filter.forget(); + } + + case PrimitiveType::Morphology: + { + Size radii = atts.GetSize(eMorphologyRadii); + int32_t rx = radii.width; + int32_t ry = radii.height; + if (rx < 0 || ry < 0) { + // XXX SVGContentUtils::ReportToConsole() + return nullptr; + } + if (rx == 0 && ry == 0) { + return nullptr; + } + + // Clamp radii to prevent completely insane values: + rx = std::min(rx, kMorphologyMaxRadius); + ry = std::min(ry, kMorphologyMaxRadius); + + MorphologyOperator op = atts.GetUint(eMorphologyOperator) == SVG_OPERATOR_ERODE ? + MORPHOLOGY_OPERATOR_ERODE : MORPHOLOGY_OPERATOR_DILATE; + + RefPtr<FilterNode> filter = aDT->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, aSources[0]); + return filter.forget(); + } + + case PrimitiveType::Flood: + { + Color color = atts.GetColor(eFloodColor); + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::FLOOD); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_FLOOD_COLOR, color); + return filter.forget(); + } + + case PrimitiveType::Tile: + { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TILE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_TILE_SOURCE_RECT, aSourceRegions[0]); + filter->SetInput(IN_TILE_IN, aSources[0]); + return filter.forget(); + } + + case PrimitiveType::ComponentTransfer: + { + RefPtr<FilterNode> filters[4]; // one for each FILTER_*_TRANSFER type + static const AttributeName componentFunctionNames[4] = { + eComponentTransferFunctionR, + eComponentTransferFunctionG, + eComponentTransferFunctionB, + eComponentTransferFunctionA + }; + for (int32_t i = 0; i < 4; i++) { + AttributeMap functionAttributes = + atts.GetAttributeMap(componentFunctionNames[i]); + ConvertComponentTransferFunctionToFilter(functionAttributes, i, aDT, + filters[0], filters[1], filters[2], filters[3]); + } + + // Connect all used filters nodes. + RefPtr<FilterNode> lastFilter = aSources[0]; + for (int32_t i = 0; i < 4; i++) { + if (filters[i]) { + filters[i]->SetInput(0, lastFilter); + lastFilter = filters[i]; + } + } + + return lastFilter.forget(); + } + + case PrimitiveType::ConvolveMatrix: + { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::CONVOLVE_MATRIX); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_SIZE, atts.GetIntSize(eConvolveMatrixKernelSize)); + const nsTArray<float>& matrix = atts.GetFloats(eConvolveMatrixKernelMatrix); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_MATRIX, + matrix.Elements(), matrix.Length()); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_DIVISOR, + atts.GetFloat(eConvolveMatrixDivisor)); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_BIAS, + atts.GetFloat(eConvolveMatrixBias)); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_TARGET, + atts.GetIntPoint(eConvolveMatrixTarget)); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_SOURCE_RECT, + aSourceRegions[0]); + uint32_t edgeMode = atts.GetUint(eConvolveMatrixEdgeMode); + 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, + atts.GetSize(eConvolveMatrixKernelUnitLength)); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA, + atts.GetBool(eConvolveMatrixPreserveAlpha)); + filter->SetInput(IN_CONVOLVE_MATRIX_IN, aSources[0]); + return filter.forget(); + } + + case PrimitiveType::Offset: + { + return FilterWrappers::Offset(aDT, aSources[0], + atts.GetIntPoint(eOffsetOffset)); + } + + case PrimitiveType::DisplacementMap: + { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::DISPLACEMENT_MAP); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_DISPLACEMENT_MAP_SCALE, + atts.GetFloat(eDisplacementMapScale)); + 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[atts.GetUint(eDisplacementMapXChannel)]); + filter->SetAttribute(ATT_DISPLACEMENT_MAP_Y_CHANNEL, + (uint32_t)channel[atts.GetUint(eDisplacementMapYChannel)]); + filter->SetInput(IN_DISPLACEMENT_MAP_IN, aSources[0]); + filter->SetInput(IN_DISPLACEMENT_MAP_IN2, aSources[1]); + return filter.forget(); + } + + case PrimitiveType::Turbulence: + { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TURBULENCE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_TURBULENCE_BASE_FREQUENCY, + atts.GetSize(eTurbulenceBaseFrequency)); + filter->SetAttribute(ATT_TURBULENCE_NUM_OCTAVES, + atts.GetUint(eTurbulenceNumOctaves)); + filter->SetAttribute(ATT_TURBULENCE_STITCHABLE, + atts.GetBool(eTurbulenceStitchable)); + filter->SetAttribute(ATT_TURBULENCE_SEED, + (uint32_t)atts.GetFloat(eTurbulenceSeed)); + 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[atts.GetUint(eTurbulenceType)]); + filter->SetAttribute(ATT_TURBULENCE_RECT, + aDescription.PrimitiveSubregion() - atts.GetIntPoint(eTurbulenceOffset)); + return FilterWrappers::Offset(aDT, filter, atts.GetIntPoint(eTurbulenceOffset)); + } + + case PrimitiveType::Composite: + { + RefPtr<FilterNode> filter; + uint32_t op = atts.GetUint(eCompositeOperator); + if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + const nsTArray<float>& coefficients = atts.GetFloats(eCompositeCoefficients); + static const float allZero[4] = { 0, 0, 0, 0 }; + filter = aDT->CreateFilter(FilterType::ARITHMETIC_COMBINE); + // All-zero coefficients sometimes occur in junk filters. + if (!filter || + (coefficients.Length() == ArrayLength(allZero) && + PodEqual(coefficients.Elements(), allZero, ArrayLength(allZero)))) { + return nullptr; + } + filter->SetAttribute(ATT_ARITHMETIC_COMBINE_COEFFICIENTS, + coefficients.Elements(), coefficients.Length()); + filter->SetInput(IN_ARITHMETIC_COMBINE_IN, aSources[0]); + filter->SetInput(IN_ARITHMETIC_COMBINE_IN2, aSources[1]); + } else { + filter = aDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + static const uint8_t operators[SVG_FECOMPOSITE_OPERATOR_ARITHMETIC] = { + 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 + }; + filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)operators[op]); + filter->SetInput(IN_COMPOSITE_IN_START, aSources[1]); + filter->SetInput(IN_COMPOSITE_IN_START + 1, aSources[0]); + } + return filter.forget(); + } + + case PrimitiveType::Merge: + { + if (aSources.Length() == 0) { + return nullptr; + } + if (aSources.Length() == 1) { + RefPtr<FilterNode> filter(aSources[0]); + return filter.forget(); + } + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)COMPOSITE_OPERATOR_OVER); + for (size_t i = 0; i < aSources.Length(); i++) { + filter->SetInput(IN_COMPOSITE_IN_START + i, aSources[i]); + } + return filter.forget(); + } + + case PrimitiveType::GaussianBlur: + { + return FilterWrappers::GaussianBlur(aDT, aSources[0], + atts.GetSize(eGaussianBlurStdDeviation)); + } + + case PrimitiveType::DropShadow: + { + RefPtr<FilterNode> alpha = FilterWrappers::ToAlpha(aDT, aSources[0]); + RefPtr<FilterNode> blur = FilterWrappers::GaussianBlur(aDT, alpha, + atts.GetSize(eDropShadowStdDeviation)); + RefPtr<FilterNode> offsetBlur = FilterWrappers::Offset(aDT, blur, + atts.GetIntPoint(eDropShadowOffset)); + RefPtr<FilterNode> flood = aDT->CreateFilter(FilterType::FLOOD); + if (!flood) { + return nullptr; + } + Color color = atts.GetColor(eDropShadowColor); + if (aDescription.InputColorSpace(0) == ColorSpace::LinearRGB) { + color = Color(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, color); + + RefPtr<FilterNode> composite = aDT->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 = aDT->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, aSources[0]); + return filter.forget(); + } + + case PrimitiveType::DiffuseLighting: + case PrimitiveType::SpecularLighting: + { + bool isSpecular = + aDescription.Type() == PrimitiveType::SpecularLighting; + + AttributeMap lightAttributes = atts.GetAttributeMap(eLightingLight); + + if (lightAttributes.GetUint(eLightType) == eLightTypeNone) { + return nullptr; + } + + enum { POINT = 0, SPOT, DISTANT } lightType = POINT; + + switch (lightAttributes.GetUint(eLightType)) { + case eLightTypePoint: lightType = POINT; break; + case eLightTypeSpot: lightType = SPOT; break; + case eLightTypeDistant: lightType = DISTANT; 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 = + aDT->CreateFilter(filterType[isSpecular][lightType]); + if (!filter) { + return nullptr; + } + + filter->SetAttribute(ATT_LIGHTING_COLOR, + atts.GetColor(eLightingColor)); + filter->SetAttribute(ATT_LIGHTING_SURFACE_SCALE, + atts.GetFloat(eLightingSurfaceScale)); + filter->SetAttribute(ATT_LIGHTING_KERNEL_UNIT_LENGTH, + atts.GetSize(eLightingKernelUnitLength)); + + if (isSpecular) { + filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + atts.GetFloat(eSpecularLightingSpecularConstant)); + filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT, + atts.GetFloat(eSpecularLightingSpecularExponent)); + } else { + filter->SetAttribute(ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT, + atts.GetFloat(eDiffuseLightingDiffuseConstant)); + } + + switch (lightType) { + case POINT: + filter->SetAttribute(ATT_POINT_LIGHT_POSITION, + lightAttributes.GetPoint3D(ePointLightPosition)); + break; + case SPOT: + filter->SetAttribute(ATT_SPOT_LIGHT_POSITION, + lightAttributes.GetPoint3D(eSpotLightPosition)); + filter->SetAttribute(ATT_SPOT_LIGHT_POINTS_AT, + lightAttributes.GetPoint3D(eSpotLightPointsAt)); + filter->SetAttribute(ATT_SPOT_LIGHT_FOCUS, + lightAttributes.GetFloat(eSpotLightFocus)); + filter->SetAttribute(ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, + lightAttributes.GetFloat(eSpotLightLimitingConeAngle)); + break; + case DISTANT: + filter->SetAttribute(ATT_DISTANT_LIGHT_AZIMUTH, + lightAttributes.GetFloat(eDistantLightAzimuth)); + filter->SetAttribute(ATT_DISTANT_LIGHT_ELEVATION, + lightAttributes.GetFloat(eDistantLightElevation)); + break; + } + + filter->SetInput(IN_LIGHTING_IN, aSources[0]); + + return filter.forget(); + } + + case PrimitiveType::Image: + { + Matrix TM = atts.GetMatrix(eImageTransform); + 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 = + aInputImages[atts.GetUint(eImageInputIndex)]; + + RefPtr<FilterNode> transform = aDT->CreateFilter(FilterType::TRANSFORM); + if (!transform) { + return nullptr; + } + transform->SetInput(IN_TRANSFORM_IN, inputImage); + transform->SetAttribute(ATT_TRANSFORM_MATRIX, TM); + transform->SetAttribute(ATT_TRANSFORM_FILTER, atts.GetUint(eImageFilter)); + return transform.forget(); + } + + case PrimitiveType::ToAlpha: + { + return FilterWrappers::ToAlpha(aDT, aSources[0]); + } + + default: + return nullptr; + } +} + +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) +{ + switch (aDescr.Type()) { + case PrimitiveType::Tile: + case PrimitiveType::Offset: + case PrimitiveType::ToAlpha: + return aOriginalAlphaModel; + + case PrimitiveType::ColorMatrix: + case PrimitiveType::ComponentTransfer: + return AlphaModel::Unpremultiplied; + + case PrimitiveType::DisplacementMap: + return aInputIndex == 0 ? + AlphaModel::Premultiplied : AlphaModel::Unpremultiplied; + + case PrimitiveType::ConvolveMatrix: + return aDescr.Attributes().GetBool(eConvolveMatrixPreserveAlpha) ? + AlphaModel::Unpremultiplied : AlphaModel::Premultiplied; + + default: + 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. +static already_AddRefed<FilterNode> +FilterNodeGraphFromDescription(DrawTarget* aDT, + const FilterDescription& aFilter, + const Rect& aResultNeededRect, + SourceSurface* aSourceGraphic, + const IntRect& aSourceGraphicRect, + SourceSurface* aFillPaint, + const IntRect& aFillPaintRect, + SourceSurface* aStrokePaint, + const IntRect& aStrokePaintRect, + 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<SourceSurface*> primitiveSurfaces; + nsTArray<IntRect> primitiveSurfaceRects; + RefPtr<SourceSurface> surf = + ElementForIndex(inputIndex, primitiveSurfaces, + aSourceGraphic, aFillPaint, aStrokePaint); + IntRect surfaceRect = + ElementForIndex(inputIndex, primitiveSurfaceRects, + aSourceGraphicRect, aFillPaintRect, aStrokePaintRect); + if (surf) { + IntPoint offset = surfaceRect.TopLeft(); + sourceFilterNode = FilterWrappers::ForSurface(aDT, surf, offset); + + // 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> resultFilter = + FilterNodeGraphFromDescription(aDT, aFilter, aRenderRect, + aSourceGraphic, aSourceGraphicRect, aFillPaint, aFillPaintRect, + aStrokePaint, aStrokePaintRect, 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) +{ + const AttributeMap& atts = aDescription.Attributes(); + switch (aDescription.Type()) { + + case PrimitiveType::Empty: + case PrimitiveType::Flood: + case PrimitiveType::Turbulence: + case PrimitiveType::Image: + return nsIntRegion(); + + case PrimitiveType::Blend: + case PrimitiveType::Composite: + case PrimitiveType::Merge: + return UnionOfRegions(aInputChangeRegions); + + case PrimitiveType::ColorMatrix: + case PrimitiveType::ComponentTransfer: + case PrimitiveType::ToAlpha: + return aInputChangeRegions[0]; + + case PrimitiveType::Morphology: + { + Size radii = atts.GetSize(eMorphologyRadii); + int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius); + int32_t ry = clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius); + return aInputChangeRegions[0].Inflated(nsIntMargin(ry, rx, ry, rx)); + } + + case PrimitiveType::Tile: + return aDescription.PrimitiveSubregion(); + + case PrimitiveType::ConvolveMatrix: + { + if (atts.GetUint(eConvolveMatrixEdgeMode) != EDGE_MODE_NONE) { + return aDescription.PrimitiveSubregion(); + } + Size kernelUnitLength = atts.GetSize(eConvolveMatrixKernelUnitLength); + IntSize kernelSize = atts.GetIntSize(eConvolveMatrixKernelSize); + IntPoint target = atts.GetIntPoint(eConvolveMatrixTarget); + 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 aInputChangeRegions[0].Inflated(m); + } + + case PrimitiveType::Offset: + { + IntPoint offset = atts.GetIntPoint(eOffsetOffset); + return aInputChangeRegions[0].MovedBy(offset.x, offset.y); + } + + case PrimitiveType::DisplacementMap: + { + int32_t scale = ceil(std::abs(atts.GetFloat(eDisplacementMapScale))); + return aInputChangeRegions[0].Inflated(nsIntMargin(scale, scale, scale, scale)); + } + + case PrimitiveType::GaussianBlur: + { + Size stdDeviation = atts.GetSize(eGaussianBlurStdDeviation); + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + return aInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + case PrimitiveType::DropShadow: + { + IntPoint offset = atts.GetIntPoint(eDropShadowOffset); + nsIntRegion offsetRegion = aInputChangeRegions[0].MovedBy(offset.x, offset.y); + Size stdDeviation = atts.GetSize(eDropShadowStdDeviation); + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + nsIntRegion blurRegion = offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + blurRegion.Or(blurRegion, aInputChangeRegions[0]); + return blurRegion; + } + + case PrimitiveType::DiffuseLighting: + case PrimitiveType::SpecularLighting: + { + Size kernelUnitLength = atts.GetSize(eLightingKernelUnitLength); + int32_t dx = ceil(kernelUnitLength.width); + int32_t dy = ceil(kernelUnitLength.height); + return aInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + default: + return nsIntRegion(); + } +} + +/* 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 AttributeMap& aFunctionAttributes) +{ + switch (aFunctionAttributes.GetUint(eComponentTransferFunctionType)) { + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: + { + const nsTArray<float>& tableValues = + aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues); + if (tableValues.Length() < 2) { + return 0.0f; + } + return tableValues[0]; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: + { + const nsTArray<float>& tableValues = + aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues); + if (tableValues.Length() < 1) { + return 0.0f; + } + return tableValues[0]; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: + return aFunctionAttributes.GetFloat(eComponentTransferFunctionIntercept); + + case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: + return aFunctionAttributes.GetFloat(eComponentTransferFunctionOffset); + + case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY: + default: + return 0.0f; + } +} + +nsIntRegion +FilterSupport::PostFilterExtentsForPrimitive(const FilterPrimitiveDescription& aDescription, + const nsTArray<nsIntRegion>& aInputExtents) +{ + const AttributeMap& atts = aDescription.Attributes(); + switch (aDescription.Type()) { + + case PrimitiveType::Empty: + return IntRect(); + + case PrimitiveType::Composite: + { + uint32_t op = atts.GetUint(eCompositeOperator); + if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + // The arithmetic composite primitive can draw outside the bounding + // box of its source images. + const nsTArray<float>& coefficients = atts.GetFloats(eCompositeCoefficients); + 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 = aInputExtents[0].Intersect(aInputExtents[1]); + } + if (coefficients[1] > 0.0f) { + region.Or(region, aInputExtents[0]); + } + if (coefficients[2] > 0.0f) { + region.Or(region, aInputExtents[1]); + } + if (coefficients[3] > 0.0f) { + region = aDescription.PrimitiveSubregion(); + } + return region; + } + if (op == SVG_FECOMPOSITE_OPERATOR_IN) { + return aInputExtents[0].Intersect(aInputExtents[1]); + } + return ResultChangeRegionForPrimitive(aDescription, aInputExtents); + } + + case PrimitiveType::Flood: + { + if (atts.GetColor(eFloodColor).a == 0.0f) { + return IntRect(); + } + return aDescription.PrimitiveSubregion(); + } + + case PrimitiveType::ColorMatrix: + { + if (atts.GetUint(eColorMatrixType) == (uint32_t)SVG_FECOLORMATRIX_TYPE_MATRIX) { + const nsTArray<float>& values = atts.GetFloats(eColorMatrixValues); + if (values.Length() == 20 && values[19] > 0.0f) { + return aDescription.PrimitiveSubregion(); + } + } + return aInputExtents[0]; + } + + case PrimitiveType::ComponentTransfer: + { + AttributeMap functionAttributes = + atts.GetAttributeMap(eComponentTransferFunctionA); + if (ResultOfZeroUnderTransferFunction(functionAttributes) > 0.0f) { + return aDescription.PrimitiveSubregion(); + } + return aInputExtents[0]; + } + + case PrimitiveType::Turbulence: + case PrimitiveType::Image: + case PrimitiveType::DiffuseLighting: + case PrimitiveType::SpecularLighting: + { + return aDescription.PrimitiveSubregion(); + } + + case PrimitiveType::Morphology: + { + uint32_t op = atts.GetUint(eMorphologyOperator); + if (op == SVG_OPERATOR_ERODE) { + return aInputExtents[0]; + } + Size radii = atts.GetSize(eMorphologyRadii); + int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius); + int32_t ry = clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius); + return aInputExtents[0].Inflated(nsIntMargin(ry, rx, ry, rx)); + } + + default: + return ResultChangeRegionForPrimitive(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) +{ + const AttributeMap& atts = aDescription.Attributes(); + switch (aDescription.Type()) { + + case PrimitiveType::Flood: + case PrimitiveType::Turbulence: + case PrimitiveType::Image: + MOZ_CRASH("GFX: this shouldn't be called for filters without inputs"); + return nsIntRegion(); + + case PrimitiveType::Empty: + return nsIntRegion(); + + case PrimitiveType::Blend: + case PrimitiveType::Composite: + case PrimitiveType::Merge: + case PrimitiveType::ColorMatrix: + case PrimitiveType::ComponentTransfer: + case PrimitiveType::ToAlpha: + return aResultNeededRegion; + + case PrimitiveType::Morphology: + { + Size radii = atts.GetSize(eMorphologyRadii); + int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius); + int32_t ry = clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius); + return aResultNeededRegion.Inflated(nsIntMargin(ry, rx, ry, rx)); + } + + case PrimitiveType::Tile: + return IntRect(INT32_MIN/2, INT32_MIN/2, INT32_MAX, INT32_MAX); + + case PrimitiveType::ConvolveMatrix: + { + Size kernelUnitLength = atts.GetSize(eConvolveMatrixKernelUnitLength); + IntSize kernelSize = atts.GetIntSize(eConvolveMatrixKernelSize); + IntPoint target = atts.GetIntPoint(eConvolveMatrixTarget); + 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 aResultNeededRegion.Inflated(m); + } + + case PrimitiveType::Offset: + { + IntPoint offset = atts.GetIntPoint(eOffsetOffset); + return aResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y)); + } + + case PrimitiveType::DisplacementMap: + { + if (aInputIndex == 1) { + return aResultNeededRegion; + } + int32_t scale = ceil(std::abs(atts.GetFloat(eDisplacementMapScale))); + return aResultNeededRegion.Inflated(nsIntMargin(scale, scale, scale, scale)); + } + + case PrimitiveType::GaussianBlur: + { + Size stdDeviation = atts.GetSize(eGaussianBlurStdDeviation); + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + return aResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + case PrimitiveType::DropShadow: + { + IntPoint offset = atts.GetIntPoint(eDropShadowOffset); + nsIntRegion offsetRegion = + aResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y)); + Size stdDeviation = atts.GetSize(eDropShadowStdDeviation); + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + nsIntRegion blurRegion = offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + blurRegion.Or(blurRegion, aResultNeededRegion); + return blurRegion; + } + + case PrimitiveType::DiffuseLighting: + case PrimitiveType::SpecularLighting: + { + Size kernelUnitLength = atts.GetSize(eLightingKernelUnitLength); + int32_t dx = ceil(kernelUnitLength.width); + int32_t dy = ceil(kernelUnitLength.height); + return aResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + default: + return nsIntRegion(); + } + +} + +/* 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() + : mType(PrimitiveType::Empty) + , mOutputColorSpace(ColorSpace::SRGB) + , mIsTainted(false) +{ +} + +FilterPrimitiveDescription::FilterPrimitiveDescription(PrimitiveType aType) + : mType(aType) + , mOutputColorSpace(ColorSpace::SRGB) + , mIsTainted(false) +{ +} + +FilterPrimitiveDescription::FilterPrimitiveDescription(const FilterPrimitiveDescription& aOther) + : mType(aOther.mType) + , mAttributes(aOther.mAttributes) + , mInputPrimitives(aOther.mInputPrimitives) + , mFilterPrimitiveSubregion(aOther.mFilterPrimitiveSubregion) + , mFilterSpaceBounds(aOther.mFilterSpaceBounds) + , mInputColorSpaces(aOther.mInputColorSpaces) + , mOutputColorSpace(aOther.mOutputColorSpace) + , mIsTainted(aOther.mIsTainted) +{ +} + +FilterPrimitiveDescription& +FilterPrimitiveDescription::operator=(const FilterPrimitiveDescription& aOther) +{ + if (this != &aOther) { + mType = aOther.mType; + mAttributes = aOther.mAttributes; + mInputPrimitives = aOther.mInputPrimitives; + mFilterPrimitiveSubregion = aOther.mFilterPrimitiveSubregion; + mFilterSpaceBounds = aOther.mFilterSpaceBounds; + mInputColorSpaces = aOther.mInputColorSpaces; + mOutputColorSpace = aOther.mOutputColorSpace; + mIsTainted = aOther.mIsTainted; + } + return *this; +} + +bool +FilterPrimitiveDescription::operator==(const FilterPrimitiveDescription& aOther) const +{ + return mType == aOther.mType && + 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; +} + +// AttributeMap + +// A class that wraps different types for easy storage in a hashtable. Only +// used by AttributeMap. +struct FilterAttribute { + FilterAttribute(const FilterAttribute& aOther); + ~FilterAttribute(); + + bool operator==(const FilterAttribute& aOther) const; + bool operator!=(const FilterAttribute& aOther) const + { + return !(*this == aOther); + } + + AttributeType Type() const { return mType; } + +#define MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(type, typeLabel) \ + explicit FilterAttribute(type aValue) \ + : mType(AttributeType::e##typeLabel), m##typeLabel(aValue) \ + {} \ + type As##typeLabel() { \ + MOZ_ASSERT(mType == AttributeType::e##typeLabel); \ + return m##typeLabel; \ + } + +#define MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(className) \ + explicit FilterAttribute(const className& aValue) \ + : mType(AttributeType::e##className), m##className(new className(aValue)) \ + {} \ + className As##className() { \ + MOZ_ASSERT(mType == AttributeType::e##className); \ + return *m##className; \ + } + + MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(bool, Bool) + MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(uint32_t, Uint) + MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(float, Float) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Size) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(IntSize) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(IntPoint) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Matrix) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Matrix5x4) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Point3D) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Color) + MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(AttributeMap) + +#undef MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC +#undef MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS + + FilterAttribute(const float* aValue, uint32_t aLength) + : mType(AttributeType::eFloats) + { + mFloats = new nsTArray<float>(); + mFloats->AppendElements(aValue, aLength); + } + + const nsTArray<float>& AsFloats() const { + MOZ_ASSERT(mType == AttributeType::eFloats); + return *mFloats; + } + +private: + const AttributeType mType; + + union { + bool mBool; + uint32_t mUint; + float mFloat; + Size* mSize; + IntSize* mIntSize; + IntPoint* mIntPoint; + Matrix* mMatrix; + Matrix5x4* mMatrix5x4; + Point3D* mPoint3D; + Color* mColor; + AttributeMap* mAttributeMap; + nsTArray<float>* mFloats; + }; +}; + +FilterAttribute::FilterAttribute(const FilterAttribute& aOther) + : mType(aOther.mType) +{ + switch (mType) { + case AttributeType::eBool: + mBool = aOther.mBool; + break; + case AttributeType::eUint: + mUint = aOther.mUint; + break; + case AttributeType::eFloat: + mFloat = aOther.mFloat; + break; + +#define HANDLE_CLASS(className) \ + case AttributeType::e##className: \ + m##className = new className(*aOther.m##className); \ + break; + + HANDLE_CLASS(Size) + HANDLE_CLASS(IntSize) + HANDLE_CLASS(IntPoint) + HANDLE_CLASS(Matrix) + HANDLE_CLASS(Matrix5x4) + HANDLE_CLASS(Point3D) + HANDLE_CLASS(Color) + HANDLE_CLASS(AttributeMap) + +#undef HANDLE_CLASS + + case AttributeType::eFloats: + mFloats = new nsTArray<float>(*aOther.mFloats); + break; + case AttributeType::Max: + break; + } +} + +FilterAttribute::~FilterAttribute() { + switch (mType) { + case AttributeType::Max: + case AttributeType::eBool: + case AttributeType::eUint: + case AttributeType::eFloat: + break; + +#define HANDLE_CLASS(className) \ + case AttributeType::e##className: \ + delete m##className; \ + break; + + HANDLE_CLASS(Size) + HANDLE_CLASS(IntSize) + HANDLE_CLASS(IntPoint) + HANDLE_CLASS(Matrix) + HANDLE_CLASS(Matrix5x4) + HANDLE_CLASS(Point3D) + HANDLE_CLASS(Color) + HANDLE_CLASS(AttributeMap) + +#undef HANDLE_CLASS + + case AttributeType::eFloats: + delete mFloats; + break; + } +} + +bool +FilterAttribute::operator==(const FilterAttribute& aOther) const +{ + if (mType != aOther.mType) { + return false; + } + + switch (mType) { + +#define HANDLE_TYPE(typeName) \ + case AttributeType::e##typeName: \ + return m##typeName == aOther.m##typeName; + + HANDLE_TYPE(Bool) + HANDLE_TYPE(Uint) + HANDLE_TYPE(Float) + HANDLE_TYPE(Size) + HANDLE_TYPE(IntSize) + HANDLE_TYPE(IntPoint) + HANDLE_TYPE(Matrix) + HANDLE_TYPE(Matrix5x4) + HANDLE_TYPE(Point3D) + HANDLE_TYPE(Color) + HANDLE_TYPE(AttributeMap) + HANDLE_TYPE(Floats) + +#undef HANDLE_TYPE + + default: + return false; + } +} + +typedef FilterAttribute Attribute; + +AttributeMap::AttributeMap() +{ +} + +AttributeMap::~AttributeMap() +{ +} + +AttributeMap::AttributeMap(const AttributeMap& aOther) +{ + for (auto iter = aOther.mMap.Iter(); !iter.Done(); iter.Next()) { + const uint32_t& attributeName = iter.Key(); + Attribute* attribute = iter.UserData(); + mMap.Put(attributeName, new Attribute(*attribute)); + } +} + +AttributeMap& +AttributeMap::operator=(const AttributeMap& aOther) +{ + if (this != &aOther) { + mMap.Clear(); + for (auto iter = aOther.mMap.Iter(); !iter.Done(); iter.Next()) { + const uint32_t& attributeName = iter.Key(); + Attribute* attribute = iter.UserData(); + mMap.Put(attributeName, new Attribute(*attribute)); + } + } + return *this; +} + +bool +AttributeMap::operator==(const AttributeMap& aOther) const +{ + if (mMap.Count() != aOther.mMap.Count()) { + return false; + } + + for (auto iter = aOther.mMap.Iter(); !iter.Done(); iter.Next()) { + const uint32_t& attributeName = iter.Key(); + Attribute* attribute = iter.UserData(); + Attribute* matchingAttribute = mMap.Get(attributeName); + if (!matchingAttribute || *matchingAttribute != *attribute) { + return false; + } + } + + return true; +} + +uint32_t +AttributeMap::Count() const +{ + return mMap.Count(); +} + +nsClassHashtable<nsUint32HashKey, FilterAttribute>::Iterator +AttributeMap::ConstIter() const +{ + return mMap.ConstIter(); +} + +/* static */ AttributeType +AttributeMap::GetType(FilterAttribute* aAttribute) +{ + return aAttribute->Type(); +} + +#define MAKE_ATTRIBUTE_HANDLERS_BASIC(type, typeLabel, defaultValue) \ + type \ + AttributeMap::Get##typeLabel(AttributeName aName) const { \ + Attribute* value = mMap.Get(aName); \ + return value ? value->As##typeLabel() : defaultValue; \ + } \ + void \ + AttributeMap::Set(AttributeName aName, type aValue) { \ + mMap.Remove(aName); \ + mMap.Put(aName, new Attribute(aValue)); \ + } + +#define MAKE_ATTRIBUTE_HANDLERS_CLASS(className) \ + className \ + AttributeMap::Get##className(AttributeName aName) const { \ + Attribute* value = mMap.Get(aName); \ + return value ? value->As##className() : className(); \ + } \ + void \ + AttributeMap::Set(AttributeName aName, const className& aValue) { \ + mMap.Remove(aName); \ + mMap.Put(aName, new Attribute(aValue)); \ + } + +MAKE_ATTRIBUTE_HANDLERS_BASIC(bool, Bool, false) +MAKE_ATTRIBUTE_HANDLERS_BASIC(uint32_t, Uint, 0) +MAKE_ATTRIBUTE_HANDLERS_BASIC(float, Float, 0) +MAKE_ATTRIBUTE_HANDLERS_CLASS(Size) +MAKE_ATTRIBUTE_HANDLERS_CLASS(IntSize) +MAKE_ATTRIBUTE_HANDLERS_CLASS(IntPoint) +MAKE_ATTRIBUTE_HANDLERS_CLASS(Matrix) +MAKE_ATTRIBUTE_HANDLERS_CLASS(Matrix5x4) +MAKE_ATTRIBUTE_HANDLERS_CLASS(Point3D) +MAKE_ATTRIBUTE_HANDLERS_CLASS(Color) +MAKE_ATTRIBUTE_HANDLERS_CLASS(AttributeMap) + +#undef MAKE_ATTRIBUTE_HANDLERS_BASIC +#undef MAKE_ATTRIBUTE_HANDLERS_CLASS + +const nsTArray<float>& +AttributeMap::GetFloats(AttributeName aName) const +{ + Attribute* value = mMap.Get(aName); + if (!value) { + value = new Attribute(nullptr, 0); + mMap.Put(aName, value); + } + return value->AsFloats(); +} + +void +AttributeMap::Set(AttributeName aName, const float* aValues, int32_t aLength) +{ + mMap.Remove(aName); + mMap.Put(aName, new Attribute(aValues, aLength)); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/src/FilterSupport.h b/gfx/src/FilterSupport.h new file mode 100644 index 000000000..96a43d7cb --- /dev/null +++ b/gfx/src/FilterSupport.h @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/2D.h" +#include "nsClassHashtable.h" +#include "nsTArray.h" +#include "nsRegion.h" + +namespace mozilla { +namespace gfx { + +// 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; + +// 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; + +enum AttributeName { + eBlendBlendmode = 0, + eMorphologyRadii, + eMorphologyOperator, + eColorMatrixType, + eColorMatrixValues, + eFloodColor, + eTileSourceRect, + eComponentTransferFunctionR, + eComponentTransferFunctionG, + eComponentTransferFunctionB, + eComponentTransferFunctionA, + eComponentTransferFunctionType, + eComponentTransferFunctionTableValues, + eComponentTransferFunctionSlope, + eComponentTransferFunctionIntercept, + eComponentTransferFunctionAmplitude, + eComponentTransferFunctionExponent, + eComponentTransferFunctionOffset, + eConvolveMatrixKernelSize, + eConvolveMatrixKernelMatrix, + eConvolveMatrixDivisor, + eConvolveMatrixBias, + eConvolveMatrixTarget, + eConvolveMatrixEdgeMode, + eConvolveMatrixKernelUnitLength, + eConvolveMatrixPreserveAlpha, + eOffsetOffset, + eDropShadowStdDeviation, + eDropShadowOffset, + eDropShadowColor, + eDisplacementMapScale, + eDisplacementMapXChannel, + eDisplacementMapYChannel, + eTurbulenceOffset, + eTurbulenceBaseFrequency, + eTurbulenceNumOctaves, + eTurbulenceSeed, + eTurbulenceStitchable, + eTurbulenceType, + eCompositeOperator, + eCompositeCoefficients, + eGaussianBlurStdDeviation, + eLightingLight, + eLightingSurfaceScale, + eLightingKernelUnitLength, + eLightingColor, + eDiffuseLightingDiffuseConstant, + eSpecularLightingSpecularConstant, + eSpecularLightingSpecularExponent, + eLightType, + eLightTypeNone, + eLightTypePoint, + eLightTypeSpot, + eLightTypeDistant, + ePointLightPosition, + eSpotLightPosition, + eSpotLightPointsAt, + eSpotLightFocus, + eSpotLightLimitingConeAngle, + eDistantLightAzimuth, + eDistantLightElevation, + eImageInputIndex, + eImageFilter, + eImageNativeSize, + eImageSubregion, + eImageTransform, + eLastAttributeName +}; + +class DrawTarget; +class SourceSurface; +struct FilterAttribute; + +enum class AttributeType { + eBool, + eUint, + eFloat, + eSize, + eIntSize, + eIntPoint, + eMatrix, + eMatrix5x4, + ePoint3D, + eColor, + eAttributeMap, + eFloats, + Max +}; + +// Limits +const float kMaxStdDeviation = 500; + +// A class that stores values of different types, keyed by an attribute name. +// The Get*() methods assert that they're called for the same type that the +// attribute was Set() with. +// AttributeMaps can be nested because AttributeMap is a valid attribute type. +class AttributeMap final { +public: + AttributeMap(); + AttributeMap(const AttributeMap& aOther); + AttributeMap& operator=(const AttributeMap& aOther); + bool operator==(const AttributeMap& aOther) const; + bool operator!=(const AttributeMap& aOther) const + { + return !(*this == aOther); + } + ~AttributeMap(); + + void Set(AttributeName aName, bool aValue); + void Set(AttributeName aName, uint32_t aValue); + void Set(AttributeName aName, float aValue); + void Set(AttributeName aName, const Size& aValue); + void Set(AttributeName aName, const IntSize& aValue); + void Set(AttributeName aName, const IntPoint& aValue); + void Set(AttributeName aName, const Matrix& aValue); + void Set(AttributeName aName, const Matrix5x4& aValue); + void Set(AttributeName aName, const Point3D& aValue); + void Set(AttributeName aName, const Color& aValue); + void Set(AttributeName aName, const AttributeMap& aValue); + void Set(AttributeName aName, const float* aValues, int32_t aLength); + + bool GetBool(AttributeName aName) const; + uint32_t GetUint(AttributeName aName) const; + float GetFloat(AttributeName aName) const; + Size GetSize(AttributeName aName) const; + IntSize GetIntSize(AttributeName aName) const; + IntPoint GetIntPoint(AttributeName aName) const; + Matrix GetMatrix(AttributeName aName) const; + Matrix5x4 GetMatrix5x4(AttributeName aName) const; + Point3D GetPoint3D(AttributeName aName) const; + Color GetColor(AttributeName aName) const; + AttributeMap GetAttributeMap(AttributeName aName) const; + const nsTArray<float>& GetFloats(AttributeName aName) const; + + uint32_t Count() const; + + nsClassHashtable<nsUint32HashKey, FilterAttribute>::Iterator ConstIter() const; + + static AttributeType GetType(FilterAttribute* aAttribute); + +private: + mutable nsClassHashtable<nsUint32HashKey, FilterAttribute> mMap; +}; + +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 (uint8_t(mColorSpace) << 1) + uint8_t(mAlphaModel); + } + + ColorSpace mColorSpace; + AlphaModel mAlphaModel; +}; + +enum class PrimitiveType { + Empty = 0, + Blend, + Morphology, + ColorMatrix, + Flood, + Tile, + ComponentTransfer, + ConvolveMatrix, + Offset, + DisplacementMap, + Turbulence, + Composite, + Merge, + Image, + GaussianBlur, + DropShadow, + DiffuseLighting, + SpecularLighting, + ToAlpha, + Max +}; + +/** + * 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(PrimitiveType aType); + FilterPrimitiveDescription(const FilterPrimitiveDescription& aOther); + FilterPrimitiveDescription& operator=(const FilterPrimitiveDescription& aOther); + + PrimitiveType Type() const { return mType; } + void SetType(PrimitiveType aType) { mType = aType; } + const AttributeMap& Attributes() const { return mAttributes; } + AttributeMap& 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: + PrimitiveType mType; + AttributeMap mAttributes; + nsTArray<int32_t> mInputPrimitives; + IntRect mFilterPrimitiveSubregion; + IntRect mFilterSpaceBounds; + nsTArray<ColorSpace> 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() {} + explicit FilterDescription(const nsTArray<FilterPrimitiveDescription>& aPrimitives) + : mPrimitives(aPrimitives) + {} + + bool operator==(const FilterDescription& aOther) const; + bool operator!=(const FilterDescription& aOther) const + { + return !(*this == aOther); + } + + nsTArray<FilterPrimitiveDescription> mPrimitives; +}; + +/** + * 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); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // __FilterSupport_h diff --git a/gfx/src/PingPongRegion.h b/gfx/src/PingPongRegion.h new file mode 100644 index 000000000..d3bdcae1b --- /dev/null +++ b/gfx/src/PingPongRegion.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef PingPongRegion_h__ +#define PingPongRegion_h__ + +/* This class uses a pair of regions and swaps between them while + * accumulating to avoid the heap allocations associated with + * modifying a region in place. + * + * It is sizeof(T)*2 + sizeof(T*) and can use end up using + * approximately double the amount of memory as using single + * region so use it sparingly. + */ + +template <typename T> +class PingPongRegion +{ + typedef typename T::RectType RectType; +public: + PingPongRegion() + { + rgn = &rgn1; + } + + void SubOut(const RectType& aOther) + { + T* nextRgn = nextRegion(); + nextRgn->Sub(*rgn, aOther); + rgn = nextRgn; + } + + void OrWith(const RectType& aOther) + { + T* nextRgn = nextRegion(); + nextRgn->Or(*rgn, aOther); + rgn = nextRgn; + } + + T& Region() + { + return *rgn; + } + +private: + + T* nextRegion() + { + if (rgn == &rgn1) { + return &rgn2; + } else { + return &rgn1; + } + } + + T* rgn; + T rgn1; + T rgn2; +}; + +#endif diff --git a/gfx/src/RegionBuilder.h b/gfx/src/RegionBuilder.h new file mode 100644 index 000000000..c8bcd21b1 --- /dev/null +++ b/gfx/src/RegionBuilder.h @@ -0,0 +1,32 @@ +/* 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() + {} + + 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/TiledRegion.cpp b/gfx/src/TiledRegion.cpp new file mode 100644 index 000000000..efaa2346b --- /dev/null +++ b/gfx/src/TiledRegion.cpp @@ -0,0 +1,370 @@ +/* -*- 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); + // The total may be larger than what fits in a size_t, so clamp it to + // SIZE_MAX in that case. + return ((uint64_t)total > (uint64_t)SIZE_MAX) ? SIZE_MAX : (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 000000000..051773241 --- /dev/null +++ b/gfx/src/TiledRegion.h @@ -0,0 +1,208 @@ +/* -*- 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 "mozilla/ArrayView.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/Move.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(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 000000000..638bd0419 --- /dev/null +++ b/gfx/src/X11UndefineNone.h @@ -0,0 +1,23 @@ +/* -*- 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 + diff --git a/gfx/src/X11Util.cpp b/gfx/src/X11Util.cpp new file mode 100644 index 000000000..7dd02d2fd --- /dev/null +++ b/gfx/src/X11Util.cpp @@ -0,0 +1,94 @@ +/* -*- 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; + return; +} + +void +FinishX(Display* aDisplay) +{ + unsigned long lastRequest = NextRequest(aDisplay) - 1; + if (lastRequest == LastKnownRequestProcessed(aDisplay)) + return; + + XSync(aDisplay, False); +} + +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 000000000..e04913342 --- /dev/null +++ b/gfx/src/X11Util.h @@ -0,0 +1,148 @@ +/* -*- 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/. */ + +#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) + return GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); +#endif +} + +/** + * 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/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp new file mode 100644 index 000000000..42647ccc6 --- /dev/null +++ b/gfx/src/gfxCrashReporterUtils.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; 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 "gfxCrashReporterUtils.h" + +#if defined(MOZ_CRASHREPORTER) +#define MOZ_GFXFEATUREREPORTER 1 +#endif + +#ifdef MOZ_GFXFEATUREREPORTER +#include "gfxCrashReporterUtils.h" +#include <string.h> // for strcmp +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#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 "nsCOMPtr.h" // for nsCOMPtr +#include "nsError.h" // for NS_OK, NS_FAILED, nsresult +#include "nsExceptionHandler.h" // for AppendAppNotesToCrashReport +#include "nsID.h" +#include "nsIEventTarget.h" // for NS_DISPATCH_NORMAL +#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 "nsThreadUtils.h" // for NS_DispatchToMainThread, etc +#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() {} +private: + virtual ~ObserverToDestroyFeaturesAlreadyReported() {} +}; + +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: + 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 CancelableRunnable { +public: + explicit AppendAppNotesRunnable(const nsACString& aFeatureStr) + : mFeatureString(aFeatureStr) + { + } + + NS_IMETHOD Run() override { + CrashReporter::AppendAppNotesToCrashReport(mFeatureString); + return NS_OK; + } + +private: + nsAutoCString mFeatureString; +}; + +void +ScopedGfxFeatureReporter::WriteAppNote(char statusChar) +{ + StaticMutexAutoLock al(gFeaturesAlreadyReportedMutex); + + if (!gFeaturesAlreadyReported) { + gFeaturesAlreadyReported = new nsTArray<nsCString>; + nsCOMPtr<nsIRunnable> r = new RegisterObserverRunnable(); + NS_DispatchToMainThread(r); + } + + nsAutoCString featureString; + featureString.AppendPrintf("%s%c ", + mFeature, + statusChar); + + 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); + NS_DispatchToMainThread(r); + } +} + +} // end namespace mozilla + +#else + +namespace mozilla { +void ScopedGfxFeatureReporter::WriteAppNote(char) {} +void ScopedGfxFeatureReporter::AppNote(const nsACString&) {} + +} // namespace mozilla + +#endif diff --git a/gfx/src/gfxCrashReporterUtils.h b/gfx/src/gfxCrashReporterUtils.h new file mode 100644 index 000000000..efc5ab1ab --- /dev/null +++ b/gfx/src/gfxCrashReporterUtils.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; 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/. */ + +#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('-') + { + WriteAppNote(aForce ? '!' : '?'); + } + ~ScopedGfxFeatureReporter() { + WriteAppNote(mStatusChar); + } + void SetSuccessful() { mStatusChar = '+'; } + + static void AppNote(const nsACString& aMessage); + + class AppNoteWritingRunnable; + +protected: + const char *mFeature; + char mStatusChar; + +private: + void WriteAppNote(char statusChar); +}; + +} // end namespace mozilla + +#endif // gfxCrashReporterUtils_h__ diff --git a/gfx/src/gfxTelemetry.cpp b/gfx/src/gfxTelemetry.cpp new file mode 100644 index 000000000..9ea15dcbd --- /dev/null +++ b/gfx/src/gfxTelemetry.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "gfxTelemetry.h"
+
+namespace mozilla {
+namespace gfx {
+
+const char*
+FeatureStatusToString(FeatureStatus aStatus)
+{
+ switch (aStatus) {
+ case FeatureStatus::Unused:
+ return "unused";
+ case FeatureStatus::Unavailable:
+ return "unavailable";
+ case FeatureStatus::CrashedInHandler:
+ return "crashed";
+ case FeatureStatus::Blocked:
+ return "blocked";
+ case FeatureStatus::Blacklisted:
+ return "blacklisted";
+ 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 000000000..9f0f618f0 --- /dev/null +++ b/gfx/src/gfxTelemetry.h @@ -0,0 +1,70 @@ +/* -*- 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/. */
+#ifndef gfx_src_gfxTelemetry_h__
+#define gfx_src_gfxTelemetry_h__
+
+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,
+
+ // 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 blacklist, such as a
+ // runtime test failing.
+ Blocked,
+
+ // This feature has been blocked by the graphics blacklist.
+ Blacklisted,
+
+ // 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 000000000..3678eee7a --- /dev/null +++ b/gfx/src/moz.build @@ -0,0 +1,94 @@ +# -*- 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', + 'nsIScriptableRegion.idl', +] + +XPIDL_MODULE = 'gfx' + +DEFINES['MOZ_APP_VERSION'] = '"%s"' % CONFIG['MOZ_APP_VERSION'] + +EXPORTS += [ + 'DriverCrashGuard.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', + 'nsRegion.h', + 'nsRegionFwd.h', + 'nsRenderingContext.h', + 'nsSize.h', + 'nsThemeConstants.h', + 'nsTransform2D.h', + 'PingPongRegion.h', + 'RegionBuilder.h', + 'X11UndefineNone.h' +] + +EXPORTS.mozilla += [ + 'AppUnits.h', + 'ArrayView.h', +] + +EXPORTS.mozilla.gfx += [ + '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', + 'nsScriptableRegion.cpp', + 'nsThebesFontEnumerator.cpp', + 'nsThebesGfxFactory.cpp', + 'nsTransform2D.cpp', + 'TiledRegion.cpp', +] + +# nsDeviceContext.cpp cannot be built in unified mode because it pulls in OS X system headers. +SOURCES += [ + 'nsDeviceContext.cpp', +] + +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 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + CXXFLAGS += CONFIG['MOZ_PANGO_CFLAGS'] diff --git a/gfx/src/nsBoundingMetrics.h b/gfx/src/nsBoundingMetrics.h new file mode 100644 index 000000000..4665db24f --- /dev/null +++ b/gfx/src/nsBoundingMetrics.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..359f9fde4 --- /dev/null +++ b/gfx/src/nsColor.cpp @@ -0,0 +1,361 @@ +/* -*- Mode: C++; 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 "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 + +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; +} + +// Returns kColorNames, an array of all possible color names, and sets +// *aSizeArray to the size of that array. Do NOT call free() on this array. +const char * const * NS_AllColorNames(size_t *aSizeArray) +{ + *aSizeArray = ArrayLength(kColorNames); + return kColorNames; +} + +// 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); +} + +namespace mozilla { + +static uint32_t +BlendColorComponent(uint32_t aBg, uint32_t aFg, uint32_t aFgAlpha) +{ + return RoundingDivideBy255(aBg * (255 - aFgAlpha) + aFg * aFgAlpha); +} + +nscolor +LinearBlendColors(nscolor aBg, nscolor aFg, uint_fast8_t aFgRatio) +{ + // Common case that either pure background or pure foreground + if (aFgRatio == 0) { + return aBg; + } + if (aFgRatio == 255) { + return aFg; + } + // Common case that alpha channel is equal (usually both are opaque) + if (NS_GET_A(aBg) == NS_GET_A(aFg)) { + auto r = BlendColorComponent(NS_GET_R(aBg), NS_GET_R(aFg), aFgRatio); + auto g = BlendColorComponent(NS_GET_G(aBg), NS_GET_G(aFg), aFgRatio); + auto b = BlendColorComponent(NS_GET_B(aBg), NS_GET_B(aFg), aFgRatio); + return NS_RGBA(r, g, b, NS_GET_A(aFg)); + } + + constexpr float kFactor = 1.0f / 255.0f; + + float p1 = kFactor * (255 - aFgRatio); + float a1 = kFactor * NS_GET_A(aBg); + float r1 = a1 * NS_GET_R(aBg); + float g1 = a1 * NS_GET_G(aBg); + float b1 = a1 * NS_GET_B(aBg); + + float p2 = 1.0f - p1; + float a2 = kFactor * NS_GET_A(aFg); + float r2 = a2 * NS_GET_R(aFg); + float g2 = a2 * NS_GET_G(aFg); + float b2 = a2 * NS_GET_B(aFg); + + float a = p1 * a1 + p2 * a2; + if (a == 0.0) { + return NS_RGBA(0, 0, 0, 0); + } + + auto r = ClampColor((p1 * r1 + p2 * r2) / a); + auto g = ClampColor((p1 * g1 + p2 * g2) / a); + auto b = ClampColor((p1 * b1 + p2 * b2) / a); + return NS_RGBA(r, g, b, NSToIntRound(a * 255)); +} + +} // namespace mozilla + +// Functions to convert from HSL color space to RGB color space. +// This is the algorithm described in the CSS3 specification + +// helper +static float +HSL_HueToRGB(float m1, float m2, float h) +{ + if (h < 0.0f) + h += 1.0f; + if (h > 1.0f) + h -= 1.0f; + if (h < (float)(1.0/6.0)) + return m1 + (m2 - m1)*h*6.0f; + if (h < (float)(1.0/2.0)) + return m2; + if (h < (float)(2.0/3.0)) + return m1 + (m2 - m1)*((float)(2.0/3.0) - h)*6.0f; + return m1; +} + +// The float parameters are all expected to be in the range 0-1 +nscolor +NS_HSL2RGB(float h, float s, float l) +{ + uint8_t r, g, b; + float m1, m2; + if (l <= 0.5f) { + m2 = l*(s+1); + } else { + m2 = l + s - l*s; + } + m1 = l*2 - m2; + r = uint8_t(255 * HSL_HueToRGB(m1, m2, h + 1.0f/3.0f)); + g = uint8_t(255 * HSL_HueToRGB(m1, m2, h)); + b = uint8_t(255 * HSL_HueToRGB(m1, m2, h - 1.0f/3.0f)); + return NS_RGB(r, g, b); +} + +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 000000000..2f21c91bf --- /dev/null +++ b/gfx/src/nsColor.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef nsColor_h___ +#define nsColor_h___ + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint8_t, uint32_t +#include "nscore.h" // for nsAString +#include "nsCoord.h" // for NSToIntRound + +class nsAString; +class nsString; + +// 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 + +// 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 + +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; +} + +// Blend one RGBA color with another based on a given ratio. +// It is a linear interpolation on each channel with alpha premultipled. +nscolor LinearBlendColors(nscolor aBg, nscolor aFg, uint_fast8_t aFgRatio); + +} // 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); + +// Returns an array of all possible color names, and sets +// *aSizeArray to the size of that array. Do NOT call |free()| on this array. +const char * const * NS_AllColorNames(size_t *aSizeArray); + +// function to convert from HSL color space to RGB color space +// the float parameters are all expected to be in the range 0-1 +nscolor NS_HSL2RGB(float h, float s, float l); + +// 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 000000000..b679b981d --- /dev/null +++ b/gfx/src/nsColorNameList.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; 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/. */ + +/****** + + 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 000000000..388789710 --- /dev/null +++ b/gfx/src/nsColorNames.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; 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/. */ + +#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 000000000..bb53611cf --- /dev/null +++ b/gfx/src/nsCoord.h @@ -0,0 +1,443 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef NSCOORD_H +#define NSCOORD_H + +#include "nsAlgorithm.h" +#include "nscore.h" +#include "nsMathUtils.h" +#include <math.h> +#include <float.h> +#include <stdlib.h> + +#include "nsDebug.h" +#include <algorithm> + +/* + * 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 + +inline float NS_IEEEPositiveInfinity() { + union { uint32_t mPRUint32; float mFloat; } pun; + pun.mPRUint32 = 0x7F800000; + return pun.mFloat; +} +inline bool NS_IEEEIsNan(float aF) { + union { uint32_t mBits; float mFloat; } pun; + pun.mFloat = aF; + return (pun.mBits & 0x7F800000) == 0x7F800000 && + (pun.mBits & 0x007FFFFF) != 0; +} + +#ifdef NS_COORD_IS_FLOAT +typedef float nscoord; +#define nscoord_MAX NS_IEEEPositiveInfinity() +#else +typedef int32_t nscoord; +#define nscoord_MAX nscoord(1 << 30) +#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_WIN32) && defined(_M_IX86) && !defined(__GNUC__) && !defined(__clang__) + return NS_lroundup30(aValue); +#else + return nscoord(floorf(aValue + 0.5f)); +#endif /* XP_WIN32 && _M_IX86 && !__GNUC__ */ +} + +inline nscoord NSToCoordRound(double aValue) +{ +#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__) && !defined(__clang__) + return NS_lroundup30((float)aValue); +#else + return nscoord(floor(aValue + 0.5f)); +#endif /* XP_WIN32 && _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 >= nscoord_MAX) { + return nscoord_MAX; + } + if (aValue <= 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>(nscoord_MAX, product)); + return NSToCoordRoundWithClamp(std::max<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) + NS_NOTREACHED("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(!NS_IEEEIsNan(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 >= nscoord_MAX) { + return nscoord_MAX; + } + if (aValue <= 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 >= nscoord_MAX) { + return nscoord_MAX; + } + if (aValue <= 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 >= nscoord_MAX) { + return nscoord_MAX; + } + if (aValue <= 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 000000000..8a5c16973 --- /dev/null +++ b/gfx/src/nsDeviceContext.cpp @@ -0,0 +1,725 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 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 "gfxFont.h" // for gfxFontGroup +#include "gfxImageSurface.h" // for gfxImageSurface +#include "gfxPoint.h" // for gfxSize +#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_NOTREACHED, NS_ASSERTION, etc +#include "nsFont.h" // for nsFont +#include "nsFontMetrics.h" // for nsFontMetrics +#include "nsIAtom.h" // for nsIAtom, NS_Atomize +#include "nsID.h" +#include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec +#include "nsILanguageAtomService.h" // for nsILanguageAtomService, etc +#include "nsIObserver.h" // for nsIObserver, etc +#include "nsIObserverService.h" // for nsIObserverService +#include "nsIScreen.h" // for nsIScreen +#include "nsIScreenManager.h" // for nsIScreenManager +#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" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::services::GetObserverService; + +class nsFontCache final : public nsIObserver +{ +public: + nsFontCache() { MOZ_COUNT_CTOR(nsFontCache); } + + NS_DECL_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(); + void Flush(); + +protected: + ~nsFontCache() { MOZ_COUNT_DTOR(nsFontCache); } + + nsDeviceContext* mContext; // owner + nsCOMPtr<nsIAtom> mLocaleLanguage; + nsTArray<nsFontMetrics*> mFontMetrics; +}; + +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); + + nsCOMPtr<nsILanguageAtomService> langService; + langService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID); + if (langService) { + mLocaleLanguage = langService->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) +{ + nsIAtom* language = aParams.language ? aParams.language + : mLocaleLanguage.get(); + + // First check our cache + // start from the end, which is where we put the most-recent-used element + + 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) { + 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. + + 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.get()).take()); + return fm.forget(); +} + +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); + } + } +} + +void +nsFontCache::Flush() +{ + for (int32_t i = mFontMetrics.Length()-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.Clear(); +} + +nsDeviceContext::nsDeviceContext() + : mWidth(0), mHeight(0), mDepth(0), + mAppUnitsPerDevPixel(-1), mAppUnitsPerDevPixelAtUnitFullZoom(-1), + mAppUnitsPerPhysicalInch(-1), + mFullZoom(1.0f), mPrintingScale(1.0f) +#ifdef DEBUG + , mIsInitialized(false) +#endif +{ + MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread"); +} + +nsDeviceContext::~nsDeviceContext() +{ + if (mFontCache) { + mFontCache->Destroy(); + } +} + +already_AddRefed<nsFontMetrics> +nsDeviceContext::GetMetricsFor(const nsFont& aFont, + const nsFontMetrics::Params& aParams) +{ + if (!mFontCache) { + mFontCache = new nsFontCache(); + mFontCache->Init(this); + } + + 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 +#ifdef XP_MACOSX + || mCachedPrintTarget != nullptr +#endif + ; +} + +void +nsDeviceContext::SetDPI(double* aScale) +{ + float dpi = -1.0f; + + // Use the printing DC to determine DPI values, if we have one. + if (mDeviceContextSpec) { + dpi = mDeviceContextSpec->GetDPI(); + mPrintingScale = mDeviceContextSpec->GetPrintingScale(); + mAppUnitsPerDevPixelAtUnitFullZoom = + NS_lround((AppUnitsPerCSSPixel() * 96) / dpi); + } else { + // 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) { + dpi = mWidget->GetDPI(); + + 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; + 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<PrintTarget> printingTarget = mPrintTarget; +#ifdef XP_MACOSX + // CreateRenderingContext() can be called (on reflow) after EndPage() + // but before BeginPage(). On OS X (and only there) mPrintTarget + // will in this case be null, because OS X printing surfaces are + // per-page, and therefore only truly valid between calls to BeginPage() + // and EndPage(). But we can get away with fudging things here, if need + // be, by using a cached copy. + if (!printingTarget) { + printingTarget = mCachedPrintTarget; + } +#endif + + // This will usually be null, depending on the pref print.print_via_parent. + RefPtr<DrawEventRecorder> recorder; + mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder)); + + RefPtr<gfx::DrawTarget> dt; + if (aWantReferenceContext) { + dt = printingTarget->GetReferenceDrawTarget(recorder); + } else { + dt = printingTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote + << "Failed to create draw target in device context sized " + << mWidth << "x" << mHeight << " and pointers " + << hexa(mPrintTarget) << " and " << hexa(printingTarget); + return nullptr; + } + +#ifdef XP_MACOSX + 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; + if (printingTarget->RotateNeededForLandscape()) { + // Rotate page 90 degrees to draw landscape page on portrait paper + IntSize size = printingTarget->GetSize(); + transform.Translate(gfxPoint(0, size.width)); + gfxMatrix rotate(0, -1, + 1, 0, + 0, 0); + transform = rotate * transform; + } + transform.Scale(mPrintingScale, mPrintingScale); + + pContext->SetMatrix(transform); + return pContext.forget(); +} + +nsresult +nsDeviceContext::GetDepth(uint32_t& aDepth) +{ + if (mDepth == 0 && mScreenManager) { + nsCOMPtr<nsIScreen> primaryScreen; + mScreenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen)); + primaryScreen->GetColorDepth(reinterpret_cast<int32_t *>(&mDepth)); + } + + aDepth = mDepth; + 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.x = 0; + aRect.y = 0; + aRect.width = mWidth; + aRect.height = mHeight; + } else + ComputeFullAreaUsingScreen ( &aRect ); + + return NS_OK; +} + +nsresult +nsDeviceContext::GetClientRect(nsRect &aRect) +{ + if (IsPrinterContext()) { + aRect.x = 0; + aRect.y = 0; + aRect.width = mWidth; + aRect.height = 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) +{ + nsresult rv = mPrintTarget->BeginPrinting(aTitle, aPrintToFileName); + + if (NS_SUCCEEDED(rv) && mDeviceContextSpec) { + rv = mDeviceContextSpec->BeginDocument(aTitle, aPrintToFileName, + aStartPage, aEndPage); + } + + return rv; +} + + +nsresult +nsDeviceContext::EndDocument(void) +{ + nsresult rv = NS_OK; + + if (mPrintTarget) { + rv = mPrintTarget->EndPrinting(); + if (NS_SUCCEEDED(rv)) + mPrintTarget->Finish(); + } + + if (mDeviceContextSpec) + mDeviceContextSpec->EndDocument(); + + return rv; +} + + +nsresult +nsDeviceContext::AbortDocument(void) +{ + nsresult rv = mPrintTarget->AbortPrinting(); + + if (mDeviceContextSpec) + mDeviceContextSpec->EndDocument(); + + return rv; +} + + +nsresult +nsDeviceContext::BeginPage(void) +{ + nsresult rv = NS_OK; + + if (mDeviceContextSpec) + rv = mDeviceContextSpec->BeginPage(); + + if (NS_FAILED(rv)) return rv; + +#ifdef XP_MACOSX + // We need to get a new surface for each page on the Mac, as the + // CGContextRefs are only good for one page. + mPrintTarget = mDeviceContextSpec->MakePrintTarget(); +#endif + + rv = mPrintTarget->BeginPage(); + + return rv; +} + +nsresult +nsDeviceContext::EndPage(void) +{ + nsresult rv = mPrintTarget->EndPage(); + +#ifdef XP_MACOSX + // We need to release the CGContextRef in the surface here, plus it's + // not something you would want anyway, as these CGContextRefs are only + // good for one page. But we need to keep a cached reference to it, since + // CreateRenderingContext() may try to access it when mPrintTarget + // would normally be null. See bug 665218. If we just stop nulling out + // mPrintTarget here (and thereby make that our cached copy), we'll + // break all our null checks on mPrintTarget. See bug 684622. + mCachedPrintTarget = mPrintTarget; + mPrintTarget = nullptr; +#endif + + 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->y = NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()); + outRect->x = NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()); + outRect->width = NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()); + outRect->height = 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->y = NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()); + outRect->x = NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()); + outRect->width = NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()); + outRect->height = 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(); + + if (mWidget->GetOwningTabChild()) { + mScreenManager->ScreenForNativeWidget((void *)mWidget->GetOwningTabChild(), + outScreen); + } + else if (mWidget->GetNativeData(NS_NATIVE_WINDOW)) { + mScreenManager->ScreenForNativeWidget(mWidget->GetNativeData(NS_NATIVE_WINDOW), + outScreen); + } + +#ifdef MOZ_WIDGET_ANDROID + if (!(*outScreen)) { + nsCOMPtr<nsIScreen> screen = mWidget->GetWidgetScreen(); + screen.forget(outScreen); + } +#endif + + if (!(*outScreen)) { + mScreenManager->GetPrimaryScreen(outScreen); + } +} + +bool +nsDeviceContext::CalcPrintingSize() +{ + if (!mPrintTarget) { + return (mWidth > 0 && mHeight > 0); + } + + 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) { + NS_NOTREACHED("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); +} diff --git a/gfx/src/nsDeviceContext.h b/gfx/src/nsDeviceContext.h new file mode 100644 index 000000000..1115757eb --- /dev/null +++ b/gfx/src/nsDeviceContext.h @@ -0,0 +1,311 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 + +class gfxContext; +class gfxTextPerfMetrics; +class gfxUserFontSet; +struct nsFont; +class nsFontCache; +class nsIAtom; +class nsIDeviceContextSpec; +class nsIScreen; +class nsIScreenManager; +class nsIWidget; +struct nsRect; + +namespace mozilla { +namespace gfx { +class PrintTarget; +} +} + +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 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 CSS pixel; this number is global, + * not unique to each device context. + */ + static int32_t AppUnitsPerCSSPixel() { return mozilla::AppUnitsPerCSSPixel(); } + + /** + * 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; } + + /** + * Gets the number of app units in one CSS inch; this is + * 96 times AppUnitsPerCSSPixel. + */ + static int32_t AppUnitsPerCSSInch() { return mozilla::AppUnitsPerCSSInch(); } + + /** + * 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); + + /** + * 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(); + +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; + uint32_t mDepth; + int32_t mAppUnitsPerDevPixel; + int32_t mAppUnitsPerDevPixelAtUnitFullZoom; + int32_t mAppUnitsPerPhysicalInch; + float mFullZoom; + float mPrintingScale; + + RefPtr<nsFontCache> mFontCache; + nsCOMPtr<nsIWidget> mWidget; + nsCOMPtr<nsIScreenManager> mScreenManager; + nsCOMPtr<nsIDeviceContextSpec> mDeviceContextSpec; + RefPtr<PrintTarget> mPrintTarget; +#ifdef XP_MACOSX + RefPtr<PrintTarget> mCachedPrintTarget; +#endif +#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 000000000..c5b1f09f0 --- /dev/null +++ b/gfx/src/nsFont.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; 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 "nsFont.h" +#include "gfxFont.h" // for gfxFontStyle +#include "gfxFontConstants.h" // for NS_FONT_KERNING_AUTO, etc +#include "gfxFontFeatures.h" // for gfxFontFeature, etc +#include "gfxFontUtils.h" // for TRUETYPE_TAG +#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, nscoord aSize) + : fontlist(aFontlist) +{ + Init(); + size = aSize; +} + +nsFont::nsFont(FontFamilyType aGenericType, nscoord aSize) + : fontlist(aGenericType) +{ + Init(); + size = aSize; +} + +void +nsFont::Init() +{ + style = NS_FONT_STYLE_NORMAL; + weight = NS_FONT_WEIGHT_NORMAL; + stretch = NS_FONT_STRETCH_NORMAL; + systemFont = false; + smoothing = NS_FONT_SMOOTHING_AUTO; + sizeAdjust = -1.0f; + kerning = NS_FONT_KERNING_AUTO; + synthesis = NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE; + + variantAlternates = 0; + variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; + variantEastAsian = 0; + variantLigatures = 0; + variantNumeric = 0; + variantPosition = NS_FONT_VARIANT_POSITION_NORMAL; + variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL; +} + +nsFont::nsFont(const nsFont& aOther) = default; + +nsFont::nsFont() +{ +} + +nsFont::~nsFont() +{ +} + +bool nsFont::Equals(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) && + (synthesis == aOther.synthesis) && + (fontFeatureSettings == aOther.fontFeatureSettings) && + (languageOverride == aOther.languageOverride) && + (variantAlternates == aOther.variantAlternates) && + (variantCaps == aOther.variantCaps) && + (variantEastAsian == aOther.variantEastAsian) && + (variantLigatures == aOther.variantLigatures) && + (variantNumeric == aOther.variantNumeric) && + (variantPosition == aOther.variantPosition) && + (variantWidth == aOther.variantWidth) && + (alternateValues == aOther.alternateValues) && + (featureValueLookup == aOther.featureValueLookup) && + (smoothing == aOther.smoothing)) { + return true; + } + return false; +} + +nsFont& nsFont::operator=(const nsFont& aOther) = default; + +void +nsFont::CopyAlternates(const nsFont& aOther) +{ + variantAlternates = aOther.variantAlternates; + alternateValues = aOther.alternateValues; + featureValueLookup = aOther.featureValueLookup; +} + +// 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) == + eFeatureEastAsian_numFeatures, + "eFeatureEastAsian_numFeatures 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) == + eFeatureLigatures_numFeatures, + "eFeatureLigatures_numFeatures 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) == + eFeatureNumeric_numFeatures, + "eFeatureNumeric_numFeatures 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) const +{ + // add in font-variant features + gfxFontFeature setting; + + // -- kerning + setting.mTag = 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 + if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_HISTORICAL) { + setting.mValue = 1; + setting.mTag = TRUETYPE_TAG('h','i','s','t'); + aStyle->featureSettings.AppendElement(setting); + } + + // -- copy font-specific alternate info into style + // (this will be resolved after font-matching occurs) + aStyle->alternateValues.AppendElements(alternateValues); + aStyle->featureValueLookup = featureValueLookup; + + // -- 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); + + // 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; + } +} diff --git a/gfx/src/nsFont.h b/gfx/src/nsFont.h new file mode 100644 index 000000000..d21ce2593 --- /dev/null +++ b/gfx/src/nsFont.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef nsFont_h___ +#define nsFont_h___ + +#include <stdint.h> // for uint8_t, uint16_t +#include <sys/types.h> // for int16_t +#include "gfxFontFamilyList.h" +#include "gfxFontFeatures.h" +#include "mozilla/RefPtr.h" // for RefPtr +#include "nsCoord.h" // for nscoord +#include "nsStringFwd.h" // for nsSubstring +#include "nsString.h" // for nsString +#include "nsTArray.h" // for nsTArray + +struct gfxFontStyle; + +// XXX we need a method to enumerate all of the possible fonts on the +// system across family, weight, style, size, etc. But not here! + +// Enumerator callback function. Return false to stop +typedef bool (*nsFontFamilyEnumFunc)(const nsString& aFamily, bool aGeneric, void *aData); + +// IDs for generic fonts +// NOTE: 0, 1 are reserved for the special IDs of the default variable +// and fixed fonts in the presentation context, see nsPresContext.h +const uint8_t kGenericFont_NONE = 0x00; +// Special +const uint8_t kGenericFont_moz_variable = 0x00; // for the default variable width font +const uint8_t kGenericFont_moz_fixed = 0x01; // our special "use the user's fixed font" +// CSS +const uint8_t kGenericFont_serif = 0x02; +const uint8_t kGenericFont_sans_serif = 0x04; +const uint8_t kGenericFont_monospace = 0x08; +const uint8_t kGenericFont_cursive = 0x10; +const uint8_t kGenericFont_fantasy = 0x20; + +// Font structure. +struct nsFont { + + // list of font families, either named or generic + mozilla::FontFamilyList fontlist; + + // The style of font (normal, italic, oblique; see gfxFontConstants.h) + uint8_t 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; + + // Variant subproperties + uint8_t variantCaps; + uint8_t variantNumeric; + uint8_t variantPosition; + uint8_t variantWidth; + + uint16_t variantLigatures; + uint16_t variantEastAsian; + + // Some font-variant-alternates property values require + // font-specific settings defined via @font-feature-values rules. + // These are resolved *after* font matching occurs. + + // -- bitmask for both enumerated and functional propvals + uint16_t variantAlternates; + + // Smoothing - controls subpixel-antialiasing (currently OSX only) + uint8_t smoothing; + + // The weight of the font; see gfxFontConstants.h. + uint16_t weight; + + // The stretch of the font (the sum of various NS_FONT_STRETCH_* + // constants; see gfxFontConstants.h). + int16_t stretch; + + // Kerning + uint8_t kerning; + + // Synthesis setting, controls use of fake bolding/italics + uint8_t synthesis; + + // The logical size of the font, in nscoord units + nscoord size; + + // 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; + + // -- list of value tags for font-specific alternate features + nsTArray<gfxAlternateValue> alternateValues; + + // -- object used to look these up once the font is matched + RefPtr<gfxFontFeatureValueSet> featureValueLookup; + + // Font features from CSS font-feature-settings + nsTArray<gfxFontFeature> fontFeatureSettings; + + // 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). + nsString languageOverride; + + // initialize the font with a fontlist + nsFont(const mozilla::FontFamilyList& aFontlist, nscoord aSize); + + // initialize the font with a single generic + nsFont(mozilla::FontFamilyType aGenericType, nscoord aSize); + + // Make a copy of the given font + nsFont(const nsFont& aFont); + + // leave members uninitialized + nsFont(); + + ~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); + + void CopyAlternates(const nsFont& aOther); + + // Add featureSettings into style + void AddFontFeaturesToStyle(gfxFontStyle *aStyle) const; + +protected: + void Init(); // helper method for initialization +}; + +#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 000000000..062bc73a2 --- /dev/null +++ b/gfx/src/nsFontMetrics.cpp @@ -0,0 +1,445 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "gfxFontConstants.h" // for NS_FONT_SYNTHESIS_* +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxPoint.h" // for gfxPoint +#include "gfxRect.h" // for gfxRect +#include "gfxTypes.h" // for gfxFloat +#include "nsBoundingMetrics.h" // for nsBoundingMetrics +#include "nsDebug.h" // for NS_ERROR +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsIAtom.h" // for nsIAtom +#include "nsMathUtils.h" // for NS_round +#include "nsRenderingContext.h" // for nsRenderingContext +#include "nsString.h" // for nsString +#include "nsStyleConsts.h" // for NS_STYLE_HYPHENS_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), + nullptr); + } + + AutoTextRun(nsFontMetrics* aMetrics, DrawTarget* aDrawTarget, + const char16_t* aString, int32_t aLength) + { + mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( + aString, aLength, + aDrawTarget, + aMetrics->AppUnitsPerDevPixel(), + ComputeFlags(aMetrics), + nullptr); + } + + gfxTextRun *get() { return mTextRun.get(); } + gfxTextRun *operator->() { return mTextRun.get(); } + +private: + static uint32_t ComputeFlags(nsFontMetrics* aMetrics) { + uint32_t flags = 0; + if (aMetrics->GetTextRunRTL()) { + flags |= gfxTextRunFactory::TEXT_IS_RTL; + } + if (aMetrics->GetVertical()) { + switch (aMetrics->GetTextOrientation()) { + case NS_STYLE_TEXT_ORIENTATION_MIXED: + flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED; + break; + case NS_STYLE_TEXT_ORIENTATION_UPRIGHT: + flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT; + break; + case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS: + flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + } + return flags; + } + + RefPtr<gfxTextRun> mTextRun; +}; + +class StubPropertyProvider : public gfxTextRun::PropertyProvider { +public: + virtual void GetHyphenationBreaks(gfxTextRun::Range aRange, + bool* aBreakBefore) { + NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText"); + } + virtual int8_t GetHyphensOption() { + NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText"); + return NS_STYLE_HYPHENS_NONE; + } + virtual gfxFloat GetHyphenWidth() { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return 0; + } + virtual already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return nullptr; + } + virtual uint32_t GetAppUnitsPerDevUnit() { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return 60; + } + virtual void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) { + 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) + , mTextRunRTL(false) + , mVertical(false) + , mTextOrientation(0) +{ + gfxFontStyle style(aFont.style, + aFont.weight, + aFont.stretch, + gfxFloat(aFont.size) / mP2A, + aParams.language, + aParams.explicitLanguage, + aFont.sizeAdjust, + aFont.systemFont, + mDeviceContext->IsPrinterContext(), + aFont.synthesis & NS_FONT_SYNTHESIS_WEIGHT, + aFont.synthesis & NS_FONT_SYNTHESIS_STYLE, + aFont.languageOverride); + + aFont.AddFontFeaturesToStyle(&style); + + gfxFloat devToCssSize = gfxFloat(mP2A) / + gfxFloat(mDeviceContext->AppUnitsPerCSSPixel()); + mFontGroup = gfxPlatform::GetPlatform()-> + CreateFontGroup(aFont.fontlist, &style, aParams.textPerf, + aParams.userFontSet, devToCssSize); +} + +nsFontMetrics::~nsFontMetrics() +{ + 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) + +const gfxFont::Metrics& +nsFontMetrics::GetMetrics(gfxFont::Orientation aOrientation) const +{ + return mFontGroup->GetFirstValidFont()->GetMetrics(aOrientation); +} + +nscoord +nsFontMetrics::XHeight() +{ + return ROUND_TO_TWIPS(GetMetrics().xHeight); +} + +nscoord +nsFontMetrics::CapHeight() +{ + return ROUND_TO_TWIPS(GetMetrics().capHeight); +} + +nscoord +nsFontMetrics::SuperscriptOffset() +{ + return ROUND_TO_TWIPS(GetMetrics().emHeight * + NS_FONT_SUPERSCRIPT_OFFSET_RATIO); +} + +nscoord +nsFontMetrics::SubscriptOffset() +{ + return ROUND_TO_TWIPS(GetMetrics().emHeight * + NS_FONT_SUBSCRIPT_OFFSET_RATIO); +} + +void +nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) +{ + aOffset = ROUND_TO_TWIPS(GetMetrics().strikeoutOffset); + aSize = ROUND_TO_TWIPS(GetMetrics().strikeoutSize); +} + +void +nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) +{ + aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset()); + aSize = ROUND_TO_TWIPS(GetMetrics().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().internalLeading); +} + +nscoord +nsFontMetrics::ExternalLeading() +{ + return ROUND_TO_TWIPS(GetMetrics().externalLeading); +} + +nscoord +nsFontMetrics::EmHeight() +{ + return ROUND_TO_TWIPS(GetMetrics().emHeight); +} + +nscoord +nsFontMetrics::EmAscent() +{ + return ROUND_TO_TWIPS(GetMetrics().emAscent); +} + +nscoord +nsFontMetrics::EmDescent() +{ + return ROUND_TO_TWIPS(GetMetrics().emDescent); +} + +nscoord +nsFontMetrics::MaxHeight() +{ + return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics())) + + CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(), mFontGroup)); +} + +nscoord +nsFontMetrics::MaxAscent() +{ + return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics())); +} + +nscoord +nsFontMetrics::MaxDescent() +{ + return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(), mFontGroup)); +} + +nscoord +nsFontMetrics::MaxAdvance() +{ + return CEIL_TO_TWIPS(GetMetrics().maxAdvance); +} + +nscoord +nsFontMetrics::AveCharWidth() +{ + // Use CEIL instead of ROUND for consistency with GetMaxAdvance + return CEIL_TO_TWIPS(GetMetrics().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(mVertical && + mTextOrientation == NS_STYLE_TEXT_ORIENTATION_UPRIGHT + ? gfxFont::eVertical + : gfxFont::eHorizontal).spaceWidth); +} + +int32_t +nsFontMetrics::GetMaxStringLength() +{ + const gfxFont::Metrics& m = GetMetrics(); + const double x = 32767.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(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(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, + nsRenderingContext *aContext) +{ + if (aLength == 0) + return; + + StubPropertyProvider provider; + AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength); + if (!textRun.get()) { + return; + } + gfxPoint pt(aX, aY); + 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->ThebesContext()); + params.provider = &provider; + textRun->Draw(range, pt, params); +} + +void +nsFontMetrics::DrawString(const char16_t* aString, uint32_t aLength, + nscoord aX, nscoord aY, + nsRenderingContext *aContext, + DrawTarget* aTextRunConstructionDrawTarget) +{ + if (aLength == 0) + return; + + StubPropertyProvider provider; + AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength); + if (!textRun.get()) { + return; + } + gfxPoint pt(aX, aY); + 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->ThebesContext()); + 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::GetInkBoundsForVisualOverflow(const char16_t *aString, uint32_t aLength, + DrawTarget* aDrawTarget) +{ + return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget, + gfxFont::LOOSE_INK_EXTENTS); +} + diff --git a/gfx/src/nsFontMetrics.h b/gfx/src/nsFontMetrics.h new file mode 100644 index 000000000..f8a2c5b93 --- /dev/null +++ b/gfx/src/nsFontMetrics.h @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "gfxTextRun.h" // for gfxFont, gfxFontGroup +#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 "nscore.h" // for char16_t + +class gfxUserFontSet; +class gfxTextPerfMetrics; +class nsDeviceContext; +class nsIAtom; +class nsRenderingContext; +struct nsBoundingMetrics; + +/** + * 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 gfxTextRun::Range Range; + typedef mozilla::gfx::DrawTarget DrawTarget; + + struct Params + { + nsIAtom* language = nullptr; + bool explicitLanguage = false; + gfxFont::Orientation orientation = gfxFont::eHorizontal; + gfxUserFontSet* userFontSet = nullptr; + gfxTextPerfMetrics* textPerf = nullptr; + }; + + nsFontMetrics(const nsFont& aFont, const Params& aParams, + nsDeviceContext *aContext); + + NS_INLINE_DECL_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 + */ + nsIAtom* Language() const { return mLanguage; } + + /** + * Returns the orientation (horizontal/vertical) of these metrics. + */ + gfxFont::Orientation 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, + nsRenderingContext *aContext); + void DrawString(const char16_t* aString, uint32_t aLength, + nscoord aX, nscoord aY, + nsRenderingContext *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 GetInkBoundsForVisualOverflow(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(uint8_t aTextOrientation) + { + mTextOrientation = aTextOrientation; + } + uint8_t GetTextOrientation() const { return mTextOrientation; } + + gfxFontGroup* GetThebesFontGroup() const { return mFontGroup; } + gfxUserFontSet* GetUserFontSet() const + { + return mFontGroup->GetUserFontSet(); + } + + int32_t AppUnitsPerDevPixel() const { return mP2A; } + +private: + // Private destructor, to discourage deletion outside of Release(): + ~nsFontMetrics(); + + const gfxFont::Metrics& GetMetrics() const { + return GetMetrics(mOrientation); + } + + const gfxFont::Metrics& + GetMetrics(const gfxFont::Orientation aFontOrientation) const; + + nsFont mFont; + RefPtr<gfxFontGroup> mFontGroup; + nsCOMPtr<nsIAtom> mLanguage; + nsDeviceContext* 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. + gfxFont::Orientation mOrientation; + + // 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; + uint8_t mTextOrientation; +}; + +#endif /* NSFONTMETRICS__H__ */ diff --git a/gfx/src/nsGfxCIID.h b/gfx/src/nsGfxCIID.h new file mode 100644 index 000000000..37cc27d0d --- /dev/null +++ b/gfx/src/nsGfxCIID.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; 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/. */ + +#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 000000000..f5f4c6468 --- /dev/null +++ b/gfx/src/nsIFontEnumerator.idl @@ -0,0 +1,61 @@ +/* -*- 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. + * + * @param aCount returns number of names returned + * @param aResult returns array of names + * @return void + */ + void EnumerateAllFonts(out uint32_t aCount, + [retval, array, size_is(aCount)] out wstring aResult); + + /** + * 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 + * @param aCount returns number of names returned + * @param aResult returns array of names + * @return void + */ + void EnumerateFonts(in string aLangGroup, in string aGeneric, + out uint32_t aCount, [retval, array, size_is(aCount)] out wstring aResult); + + /** + @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/nsIScriptableRegion.idl b/gfx/src/nsIScriptableRegion.idl new file mode 100644 index 000000000..1e32fdd1b --- /dev/null +++ b/gfx/src/nsIScriptableRegion.idl @@ -0,0 +1,176 @@ +/* -*- 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" + +%{C++ +#include "nsRegionFwd.h" +%} + +native nsIntRegion(nsIntRegion); + + +[scriptable, uuid(a5f44cc7-2820-489b-b817-ae8a08506ff6)] +interface nsIScriptableRegion : nsISupports +{ + void init ( ) ; + + /** + * copy operator equivalent that takes another region + * + * @param region to copy + * @return void + * + **/ + + void setToRegion ( in nsIScriptableRegion aRegion ); + + /** + * copy operator equivalent that takes a rect + * + * @param aX xoffset of rect to set region to + * @param aY yoffset of rect to set region to + * @param aWidth width of rect to set region to + * @param aHeight height of rect to set region to + * @return void + * + **/ + + void setToRect ( in long aX, in long aY, in long aWidth, in long aHeight ); + + /** + * destructively intersect another region with this one + * + * @param region to intersect + * @return void + * + **/ + + void intersectRegion ( in nsIScriptableRegion aRegion ) ; + + /** + * destructively intersect a rect with this region + * + * @param aX xoffset of rect to intersect with region + * @param aY yoffset of rect to intersect with region + * @param aWidth width of rect to intersect with region + * @param aHeight height of rect to intersect with region + * @return void + * + **/ + + void intersectRect ( in long aX, in long aY, in long aWidth, in long aHeight ) ; + + /** + * destructively union another region with this one + * + * @param region to union + * @return void + * + **/ + + void unionRegion ( in nsIScriptableRegion aRegion ) ; + + /** + * destructively union a rect with this region + * + * @param aX xoffset of rect to union with region + * @param aY yoffset of rect to union with region + * @param aWidth width of rect to union with region + * @param aHeight height of rect to union with region + * @return void + * + **/ + + void unionRect ( in long aX, in long aY, in long aWidth, in long aHeight ) ; + + /** + * destructively subtract another region with this one + * + * @param region to subtract + * @return void + * + **/ + + void subtractRegion ( in nsIScriptableRegion aRegion ) ; + + /** + * destructively subtract a rect from this region + * + * @param aX xoffset of rect to subtract with region + * @param aY yoffset of rect to subtract with region + * @param aWidth width of rect to subtract with region + * @param aHeight height of rect to subtract with region + * @return void + * + **/ + + void subtractRect ( in long aX, in long aY, in long aWidth, in long aHeight ) ; + + /** + * is this region empty? i.e. does it contain any pixels + * + * @param none + * @return returns whether the region is empty + * + **/ + + boolean isEmpty ( ) ; + + /** + * == operator equivalent i.e. do the regions contain exactly + * the same pixels + * + * @param region to compare + * @return whether the regions are identical + * + **/ + + boolean isEqualRegion ( in nsIScriptableRegion aRegion ) ; + + /** + * returns the bounding box of the region i.e. the smallest + * rectangle that completely contains the region. + * + * @param aX out parameter for xoffset of bounding rect for region + * @param aY out parameter for yoffset of bounding rect for region + * @param aWidth out parameter for width of bounding rect for region + * @param aHeight out parameter for height of bounding rect for region + * @return void + * + **/ + void getBoundingBox ( out long aX, out long aY, out long aWidth, out long aHeight ) ; + + /** + * offsets the region in x and y + * + * @param xoffset pixel offset in x + * @param yoffset pixel offset in y + * @return void + * + **/ + void offset ( in long aXOffset, in long aYOffset ) ; + + /** + * @return null if there are no rects, + * @return flat array of rects,ie [x1,y1,width1,height1,x2...]. + * The result will contain bogus data if values don't fit in 31 bit + **/ + [implicit_jscontext] jsval getRects(); + + /** + * does the region intersect the rectangle? + * + * @param rect to check for containment + * @return true if the region intersects the rect + * + **/ + + boolean containsRect ( in long aX, in long aY, in long aWidth, in long aHeight ) ; + + [noscript] readonly attribute nsIntRegion region; + +}; diff --git a/gfx/src/nsITheme.h b/gfx/src/nsITheme.h new file mode 100644 index 000000000..9456a7394 --- /dev/null +++ b/gfx/src/nsITheme.h @@ -0,0 +1,206 @@ +/* -*- Mode: C++; 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/. */ + +/* service providing platform-specific native rendering for widgets */ + +#ifndef nsITheme_h_ +#define nsITheme_h_ + +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsColor.h" +#include "Units.h" + +struct nsRect; +class nsAttrValue; +class nsPresContext; +class nsRenderingContext; +class nsDeviceContext; +class nsIFrame; +class nsIAtom; +class nsIWidget; + +// 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 } } +// {0ae05515-cf7a-45a8-9e02-6556de7685b1} +#define NS_THEMERENDERER_CID \ +{ 0x0ae05515, 0xcf7a, 0x45a8, \ + { 0x9e, 0x02, 0x65, 0x56, 0xde, 0x76, 0x85, 0xb1 } } + +/** + * 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 { +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(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) = 0; + + /** + * Get the computed CSS border for the widget, in pixels. + */ + NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult)=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, + uint8_t aWidgetType, + nsIntMargin* 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 visual overflow but not + * scrollable overflow.) + */ + virtual bool GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t 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, + uint8_t 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, uint8_t aWidgetType) + { return eUnknownTransparency; } + + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + nsIAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue)=0; + + NS_IMETHOD ThemeChanged()=0; + + virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) + { return false; } + + virtual bool NeedToClearBackgroundBehindWidget(nsIFrame* aFrame, + uint8_t aWidgetType) + { return false; } + + virtual bool WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame, + uint8_t aWidgetType, nscolor* aColor) + { 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, + uint8_t aWidgetType) + { return eThemeGeometryTypeUnknown; } + + /** + * Can the nsITheme implementation handle this widget? + */ + virtual bool ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + uint8_t aWidgetType)=0; + + virtual bool WidgetIsContainer(uint8_t aWidgetType)=0; + + /** + * Does the nsITheme implementation draw its own focus ring for this widget? + */ + virtual bool ThemeDrawsFocusForWidget(uint8_t aWidgetType)=0; + + /** + * Should we insert a dropmarker inside of combobox button? + */ + virtual bool ThemeNeedsComboboxDropmarker()=0; + + /** + * Should we hide scrollbars? + */ + virtual bool ShouldHideScrollbars() + { return false; } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITheme, NS_ITHEME_IID) + +// Creator function +extern nsresult NS_NewNativeTheme(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +#endif diff --git a/gfx/src/nsMargin.h b/gfx/src/nsMargin.h new file mode 100644 index 000000000..d9b0b8bd9 --- /dev/null +++ b/gfx/src/nsMargin.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef NSMARGIN_H +#define NSMARGIN_H + +#include "nsCoord.h" +#include "nsPoint.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() : Super() {} + nsMargin(const nsMargin& aMargin) : Super(aMargin) {} + 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 000000000..b377eb5a5 --- /dev/null +++ b/gfx/src/nsPoint.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef NSPOINT_H +#define NSPOINT_H + +#include "nsCoord.h" +#include "mozilla/gfx/BaseSize.h" +#include "mozilla/gfx/BasePoint.h" +#include "nsSize.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() : Super() {} + nsPoint(const nsPoint& aPoint) : Super(aPoint) {} + 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 + */ + MOZ_MUST_USE inline nsPoint + ScaleToOtherAppUnits(int32_t aFromAPP, int32_t aToAPP) const; + + MOZ_MUST_USE inline nsPoint + RemoveResolution(const float resolution) const; + MOZ_MUST_USE 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 000000000..c17c249b2 --- /dev/null +++ b/gfx/src/nsRect.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; 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 "nsRect.h" +#include "mozilla/gfx/Types.h" // for NS_SIDE_BOTTOM, 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(NS_SIDE_TOP) == 0) && + (int(NS_SIDE_RIGHT) == 1) && + (int(NS_SIDE_BOTTOM) == 2) && + (int(NS_SIDE_LEFT) == 3), + "The mozilla::css::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 +} + +#ifdef DEBUG +// Diagnostics + +FILE* operator<<(FILE* out, const nsRect& rect) +{ + nsAutoString tmp; + + // Output the coordinates in fractional pixels so they're easier to read + tmp.Append('{'); + tmp.AppendFloat(NSAppUnitsToFloatPixels(rect.x, + nsDeviceContext::AppUnitsPerCSSPixel())); + tmp.AppendLiteral(", "); + tmp.AppendFloat(NSAppUnitsToFloatPixels(rect.y, + nsDeviceContext::AppUnitsPerCSSPixel())); + tmp.AppendLiteral(", "); + tmp.AppendFloat(NSAppUnitsToFloatPixels(rect.width, + nsDeviceContext::AppUnitsPerCSSPixel())); + tmp.AppendLiteral(", "); + tmp.AppendFloat(NSAppUnitsToFloatPixels(rect.height, + nsDeviceContext::AppUnitsPerCSSPixel())); + tmp.Append('}'); + fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out); + return out; +} + +#endif // DEBUG diff --git a/gfx/src/nsRect.h b/gfx/src/nsRect.h new file mode 100644 index 000000000..267f5849c --- /dev/null +++ b/gfx/src/nsRect.h @@ -0,0 +1,324 @@ +/* -*- Mode: C++; 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/. */ + + +#ifndef NSRECT_H +#define NSRECT_H + +#include <stdio.h> // for FILE +#include <stdint.h> // for int32_t, int64_t +#include <algorithm> // for min/max +#include "mozilla/Likely.h" // for MOZ_UNLIKELY +#include "mozilla/gfx/Rect.h" +#include "nsCoord.h" // for nscoord, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsPoint.h" // for nsIntPoint, nsPoint +#include "nsMargin.h" // for nsIntMargin, nsMargin +#include "nsSize.h" // for IntSize, nsSize +#include "nscore.h" // for NS_BUILD_REFCNT_LOGGING + +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); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + ~nsRect() { + MOZ_COUNT_DTOR(nsRect); + } +#endif + + // 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. + + MOZ_MUST_USE nsRect SaturatingUnion(const nsRect& aRect) const + { + if (IsEmpty()) { + return aRect; + } else if (aRect.IsEmpty()) { + return *static_cast<const nsRect*>(this); + } else { + return SaturatingUnionEdges(aRect); + } + } + + MOZ_MUST_USE nsRect SaturatingUnionEdges(const nsRect& aRect) const + { +#ifdef NS_COORD_IS_FLOAT + return UnionEdges(aRect); +#else + nsRect result; + result.x = std::min(aRect.x, x); + int64_t w = std::max(int64_t(aRect.x) + aRect.width, int64_t(x) + width) - result.x; + if (MOZ_UNLIKELY(w > nscoord_MAX)) { + // Clamp huge negative x to nscoord_MIN / 2 and try again. + result.x = std::max(result.x, nscoord_MIN / 2); + w = std::max(int64_t(aRect.x) + aRect.width, int64_t(x) + width) - result.x; + if (MOZ_UNLIKELY(w > nscoord_MAX)) { + w = nscoord_MAX; + } + } + result.width = nscoord(w); + + result.y = std::min(aRect.y, y); + int64_t h = std::max(int64_t(aRect.y) + aRect.height, int64_t(y) + height) - result.y; + if (MOZ_UNLIKELY(h > nscoord_MAX)) { + // Clamp huge negative y to nscoord_MIN / 2 and try again. + result.y = std::max(result.y, nscoord_MIN / 2); + h = std::max(int64_t(aRect.y) + aRect.height, int64_t(y) + height) - result.y; + if (MOZ_UNLIKELY(h > nscoord_MAX)) { + h = nscoord_MAX; + } + } + result.height = nscoord(h); + return result; +#endif + } + +#ifndef NS_COORD_IS_FLOAT + // Make all nsRect Union methods be saturating. + MOZ_MUST_USE nsRect UnionEdges(const nsRect& aRect) const + { + return SaturatingUnionEdges(aRect); + } + void UnionRectEdges(const nsRect& aRect1, const nsRect& aRect2) + { + *this = aRect1.UnionEdges(aRect2); + } + MOZ_MUST_USE nsRect Union(const nsRect& aRect) const + { + return SaturatingUnion(aRect); + } + void UnionRect(const nsRect& aRect1, const nsRect& aRect2) + { + *this = aRect1.Union(aRect2); + } +#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 + */ + MOZ_MUST_USE inline nsRect + ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, int32_t aToAPP) const; + MOZ_MUST_USE inline nsRect + ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, int32_t aToAPP) const; + + MOZ_MUST_USE inline mozilla::gfx::IntRect + ScaleToNearestPixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + + MOZ_MUST_USE inline mozilla::gfx::IntRect + ToNearestPixels(nscoord aAppUnitsPerPixel) const; + + // Note: this can turn an empty rectangle into a non-empty rectangle + MOZ_MUST_USE inline mozilla::gfx::IntRect + ScaleToOutsidePixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + + // Note: this can turn an empty rectangle into a non-empty rectangle + MOZ_MUST_USE inline mozilla::gfx::IntRect + ToOutsidePixels(nscoord aAppUnitsPerPixel) const; + + MOZ_MUST_USE inline mozilla::gfx::IntRect + ScaleToInsidePixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + + MOZ_MUST_USE 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); + } + + MOZ_MUST_USE 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; + nscoord right = NSToCoordCeil(NSCoordScale(XMost(), aFromAPP, aToAPP)); + nscoord bottom = NSToCoordCeil(NSCoordScale(YMost(), aFromAPP, aToAPP)); + rect.x = NSToCoordFloor(NSCoordScale(x, aFromAPP, aToAPP)); + rect.y = NSToCoordFloor(NSCoordScale(y, aFromAPP, aToAPP)); + rect.width = (right - rect.x); + rect.height = (bottom - rect.y); + + return rect; +} + +inline nsRect +nsRect::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, int32_t aToAPP) const +{ + if (aFromAPP == aToAPP) { + return *this; + } + + nsRect rect; + nscoord right = NSToCoordFloor(NSCoordScale(XMost(), aFromAPP, aToAPP)); + nscoord bottom = NSToCoordFloor(NSCoordScale(YMost(), aFromAPP, aToAPP)); + rect.x = NSToCoordCeil(NSCoordScale(x, aFromAPP, aToAPP)); + rect.y = NSToCoordCeil(NSCoordScale(y, aFromAPP, aToAPP)); + rect.width = (right - rect.x); + rect.height = (bottom - rect.y); + + return rect; +} + +// 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; + rect.x = NSToIntRoundUp(NSAppUnitsToDoublePixels(x, aAppUnitsPerPixel) * aXScale); + rect.y = NSToIntRoundUp(NSAppUnitsToDoublePixels(y, aAppUnitsPerPixel) * aYScale); + // Avoid negative widths and heights due to overflow + rect.width = std::max(0, NSToIntRoundUp(NSAppUnitsToDoublePixels(XMost(), + aAppUnitsPerPixel) * aXScale) - rect.x); + rect.height = std::max(0, NSToIntRoundUp(NSAppUnitsToDoublePixels(YMost(), + aAppUnitsPerPixel) * aYScale) - rect.y); + 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; + rect.x = NSToIntFloor(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) * aXScale); + rect.y = NSToIntFloor(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) * aYScale); + // Avoid negative widths and heights due to overflow + rect.width = std::max(0, NSToIntCeil(NSAppUnitsToFloatPixels(XMost(), + float(aAppUnitsPerPixel)) * aXScale) - rect.x); + rect.height = std::max(0, NSToIntCeil(NSAppUnitsToFloatPixels(YMost(), + float(aAppUnitsPerPixel)) * aYScale) - rect.y); + 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.x = NSToIntCeil(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) * aXScale); + rect.y = NSToIntCeil(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) * aYScale); + // Avoid negative widths and heights due to overflow + rect.width = std::max(0, NSToIntFloor(NSAppUnitsToFloatPixels(XMost(), + float(aAppUnitsPerPixel)) * aXScale) - rect.x); + rect.height = std::max(0, NSToIntFloor(NSAppUnitsToFloatPixels(YMost(), + float(aAppUnitsPerPixel)) * aYScale) - rect.y); + 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.x = NSToCoordRound(NSCoordToFloat(x) / aResolution); + rect.y = 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.width = rect.height = 1; + } else { + rect.width = NSToCoordCeil(NSCoordToFloat(width) / aResolution); + rect.height = 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)); +} + +#ifdef DEBUG +// Diagnostics +extern FILE* operator<<(FILE* out, const nsRect& rect); +#endif // DEBUG + +#endif /* NSRECT_H */ diff --git a/gfx/src/nsRegion.cpp b/gfx/src/nsRegion.cpp new file mode 100644 index 000000000..3b0bec1e3 --- /dev/null +++ b/gfx/src/nsRegion.cpp @@ -0,0 +1,1146 @@ +/* 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 "mozilla/ToString.h" + +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 nsRect& aRect) const +{ + // XXX this could be made faster by using pixman_region32_contains_rect + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + if (iter.Get().Intersects(aRect)) { + return true; + } + } + return false; +} + +void nsRegion::Inflate(const nsMargin& aMargin) +{ + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + rect.Inflate(aMargin); + boxes[i] = RectToBox(rect); + } + + pixman_region32_t region; + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(®ion, boxes, n); + + pixman_region32_fini(&mImpl); + mImpl = region; +} + +void nsRegion::SimplifyOutward (uint32_t aMaxRects) +{ + MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count"); + + if (GetNumRects() <= aMaxRects) + return; + + pixman_box32_t *boxes; + int n; + boxes = pixman_region32_rectangles(&mImpl, &n); + + // Try combining rects in horizontal bands into a single rect + int dest = 0; + for (int src = 1; src < n; src++) + { + // 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 + while ((src < (n)) && boxes[dest].y1 == boxes[src].y1) { + // merge box[i] and box[i+1] + boxes[dest].x2 = boxes[src].x2; + src++; + } + if (src < n) { + dest++; + boxes[dest] = boxes[src]; + } + } + + uint32_t reducedCount = dest+1; + // pixman has a special representation for + // regions of 1 rectangle. So just use the + // bounds in that case + if (reducedCount > 1 && reducedCount <= aMaxRects) { + // reach into pixman and lower the number + // of rects stored in data. + mImpl.data->numRects = reducedCount; + } else { + *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 +static uint32_t ComputeMergedAreaIncrease(pixman_box32_t *topRects, + pixman_box32_t *topRectsEnd, + pixman_box32_t *bottomRects, + pixman_box32_t *bottomRectsEnd) +{ + uint32_t totalArea = 0; + struct pt { + int32_t x, y; + }; + + + pt *i = (pt*)topRects; + pt *end_i = (pt*)topRectsEnd; + pt *j = (pt*)bottomRects; + pt *end_j = (pt*)bottomRectsEnd; + bool top = false; + bool bottom = false; + + int cur_x = i->x; + bool top_next = top; + bool bottom_next = bottom; + //XXX: we could probably simplify this condition and perhaps move it into the loop below + if (j->x < cur_x) { + cur_x = j->x; + j++; + bottom_next = !bottom; + } else if (j->x == cur_x) { + i++; + top_next = !top; + bottom_next = !bottom; + j++; + } else { + top_next = !top; + i++; + } + + int topRectsHeight = topRects->y2 - topRects->y1; + int bottomRectsHeight = bottomRects->y2 - bottomRects->y1; + int inbetweenHeight = bottomRects->y1 - topRects->y2; + int width = cur_x; + // top and bottom are the in-status to the left of cur_x + do { + if (top && !bottom) { + totalArea += (inbetweenHeight+bottomRectsHeight)*width; + } else if (bottom && !top) { + totalArea += (inbetweenHeight+topRectsHeight)*width; + } else if (bottom && top) { + totalArea += (inbetweenHeight)*width; + } + top = top_next; + bottom = bottom_next; + // find the next edge + if (i->x < j->x) { + top_next = !top; + width = i->x - cur_x; + cur_x = i->x; + i++; + } else if (j->x < i->x) { + bottom_next = !bottom; + width = j->x - cur_x; + cur_x = j->x; + j++; + } else { // i->x == j->x + top_next = !top; + bottom_next = !bottom; + width = i->x - cur_x; + cur_x = i->x; + i++; + j++; + } + } while (i < end_i && j < end_j); + + // handle any remaining rects + while (i < end_i) { + width = i->x - cur_x; + cur_x = i->x; + i++; + if (top) + totalArea += (inbetweenHeight+bottomRectsHeight)*width; + top = !top; + } + + while (j < end_j) { + width = j->x - cur_x; + cur_x = j->x; + j++; + if (bottom) + totalArea += (inbetweenHeight+topRectsHeight)*width; + bottom = !bottom; + } + return totalArea; +} + +static pixman_box32_t * +CopyRow(pixman_box32_t *dest_it, pixman_box32_t *src_start, pixman_box32_t *src_end) +{ + // XXX: std::copy + pixman_box32_t *src_it = src_start; + while (src_it < src_end) { + *dest_it++ = *src_it++; + } + return dest_it; +} + + +#define WRITE_RECT(x1, x2, y1, y2) \ + do { \ + tmpRect->x1 = x1; \ + tmpRect->x2 = x2; \ + tmpRect->y1 = y1; \ + tmpRect->y2 = y2; \ + tmpRect++; \ + } while (0) + +/* If 'r' overlaps the current rect, then expand the current rect to include + * it. Otherwise write the current rect out to tmpRect, and set r as the + * updated current rect. */ +#define MERGE_RECT(r) \ + do { \ + if (r->x1 <= x2) { \ + if (x2 < r->x2) \ + x2 = r->x2; \ + } else { \ + WRITE_RECT(x1, x2, y1, y2); \ + x1 = r->x1; \ + x2 = r->x2; \ + } \ + r++; \ + } while (0) + + +/* Can we merge two sets of rects without extra space? + * Yes, but not easily. We can even do it stably + * but we don't need that property. + * + * This is written in the style of pixman_region_union_o */ +static pixman_box32_t * +MergeRects(pixman_box32_t *r1, + pixman_box32_t *r1_end, + pixman_box32_t *r2, + pixman_box32_t *r2_end, + pixman_box32_t *tmpRect) +{ + /* This routine works by maintaining the current + * rectangle in x1,x2,y1,y2 and either merging + * in the left most rectangle if it overlaps or + * outputing the current rectangle and setting + * it to the the left most one */ + const int y1 = r1->y1; + const int y2 = r2->y2; + int x1; + int x2; + + /* Find the left-most edge */ + if (r1->x1 < r2->x1) { + x1 = r1->x1; + x2 = r1->x2; + r1++; + } else { + x1 = r2->x1; + x2 = r2->x2; + r2++; + } + + while (r1 != r1_end && r2 != r2_end) { + /* Find and merge the left-most rectangle */ + if (r1->x1 < r2->x1) + MERGE_RECT (r1); + else + MERGE_RECT (r2); + } + + /* Finish up any left overs */ + if (r1 != r1_end) { + do { + MERGE_RECT (r1); + } while (r1 != r1_end); + } else if (r2 != r2_end) { + do { + MERGE_RECT(r2); + } while (r2 != r2_end); + } + + /* Finish up the last rectangle */ + WRITE_RECT(x1, x2, y1, y2); + + return tmpRect; +} + +void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) +{ + + pixman_box32_t *boxes; + int n; + boxes = pixman_region32_rectangles(&mImpl, &n); + + // if we have no rectangles then we're done + if (!n) + return; + + pixman_box32_t *end = boxes + n; + pixman_box32_t *topRectsEnd = boxes+1; + pixman_box32_t *topRects = boxes; + + // we need some temporary storage for merging both rows of rectangles + AutoTArray<pixman_box32_t, 10> tmpStorage; + tmpStorage.SetCapacity(n); + pixman_box32_t *tmpRect = tmpStorage.Elements(); + + pixman_box32_t *destRect = boxes; + pixman_box32_t *rect = tmpRect; + // find the end of the first span of rectangles + while (topRectsEnd < end && topRectsEnd->y1 == topRects->y1) { + topRectsEnd++; + } + + // if we only have one row we are done + if (topRectsEnd == end) + return; + + pixman_box32_t *bottomRects = topRectsEnd; + pixman_box32_t *bottomRectsEnd = bottomRects+1; + do { + // find the end of the bottom span of rectangles + while (bottomRectsEnd < end && bottomRectsEnd->y1 == bottomRects->y1) { + bottomRectsEnd++; + } + uint32_t totalArea = ComputeMergedAreaIncrease(topRects, topRectsEnd, + bottomRects, bottomRectsEnd); + + if (totalArea <= aThreshold) { + // merge the rects into tmpRect + rect = MergeRects(topRects, topRectsEnd, bottomRects, bottomRectsEnd, tmpRect); + + // set topRects to where the newly merged rects will be so that we use them + // as our next set of topRects + topRects = destRect; + // copy the merged rects back into the destination + topRectsEnd = CopyRow(destRect, tmpRect, rect); + } else { + // copy the unmerged rects + destRect = CopyRow(destRect, topRects, topRectsEnd); + + topRects = bottomRects; + topRectsEnd = bottomRectsEnd; + if (bottomRectsEnd == end) { + // copy the last row when we are done + topRectsEnd = CopyRow(destRect, topRects, topRectsEnd); + } + } + bottomRects = bottomRectsEnd; + } while (bottomRectsEnd != end); + + + uint32_t reducedCount = topRectsEnd - pixman_region32_rectangles(&this->mImpl, &n); + // pixman has a special representation for + // regions of 1 rectangle. So just use the + // bounds in that case + if (reducedCount > 1) { + // reach into pixman and lower the number + // of rects stored in data. + this->mImpl.data->numRects = reducedCount; + } else { + *this = GetBounds(); + } +} + + +typedef void (*visit_fn)(void *closure, VisitSide side, int x1, int y1, int x2, int y2); + +static bool VisitNextEdgeBetweenRect(visit_fn visit, void *closure, VisitSide side, + pixman_box32_t *&r1, pixman_box32_t *&r2, const int y, int &x1) +{ + // check for overlap + if (r1->x2 >= r2->x1) { + MOZ_ASSERT(r2->x1 >= x1); + visit(closure, side, x1, y, r2->x1, y); + + // find the rect that ends first or always drop the one that comes first? + if (r1->x2 < r2->x2) { + x1 = r1->x2; + r1++; + } else { + x1 = r2->x2; + r2++; + } + return true; + } else { + MOZ_ASSERT(r1->x2 < r2->x2); + // we handle the corners by just extending the top and bottom edges + visit(closure, side, x1, y, r1->x2+1, y); + r1++; + // we assign x1 because we can assume that x1 <= r2->x1 - 1 + // However the caller may know better and if so, may update + // x1 to r1->x1 + x1 = r2->x1 - 1; + return false; + } +} + +//XXX: if we need to this can compute the end of the row +static void +VisitSides(visit_fn visit, void *closure, pixman_box32_t *r, pixman_box32_t *r_end) +{ + // XXX: we can drop LEFT/RIGHT and just use the orientation + // of the line if it makes sense + while (r != r_end) { + visit(closure, VisitSide::LEFT, r->x1, r->y1, r->x1, r->y2); + visit(closure, VisitSide::RIGHT, r->x2, r->y1, r->x2, r->y2); + r++; + } +} + +static void +VisitAbove(visit_fn visit, void *closure, pixman_box32_t *r, pixman_box32_t *r_end) +{ + while (r != r_end) { + visit(closure, VisitSide::TOP, r->x1-1, r->y1, r->x2+1, r->y1); + r++; + } +} + +static void +VisitBelow(visit_fn visit, void *closure, pixman_box32_t *r, pixman_box32_t *r_end) +{ + while (r != r_end) { + visit(closure, VisitSide::BOTTOM, r->x1-1, r->y2, r->x2+1, r->y2); + r++; + } +} + +static pixman_box32_t * +VisitInbetween(visit_fn visit, void *closure, pixman_box32_t *r1, + pixman_box32_t *r1_end, + pixman_box32_t *r2, + pixman_box32_t *r2_end) +{ + const int y = r1->y2; + int x1; + + bool overlap = false; + while (r1 != r1_end && r2 != r2_end) { + if (!overlap) { + /* Find the left-most edge */ + if (r1->x1 < r2->x1) { + x1 = r1->x1 - 1; + } else { + x1 = r2->x1 - 1; + } + } + + MOZ_ASSERT((x1 >= (r1->x1 - 1)) || (x1 >= (r2->x1 - 1))); + if (r1->x1 < r2->x1) { + overlap = VisitNextEdgeBetweenRect(visit, closure, VisitSide::BOTTOM, r1, r2, y, x1); + } else { + overlap = VisitNextEdgeBetweenRect(visit, closure, VisitSide::TOP, r2, r1, y, x1); + } + } + + /* Finish up which ever row has remaining rects*/ + if (r1 != r1_end) { + // top row + do { + visit(closure, VisitSide::BOTTOM, x1, y, r1->x2 + 1, y); + r1++; + if (r1 == r1_end) + break; + x1 = r1->x1 - 1; + } while (true); + } else if (r2 != r2_end) { + // bottom row + do { + visit(closure, VisitSide::TOP, x1, y, r2->x2 + 1, y); + r2++; + if (r2 == r2_end) + break; + x1 = r2->x1 - 1; + } while (true); + } + + return 0; +} + +void nsRegion::VisitEdges (visit_fn visit, void *closure) +{ + pixman_box32_t *boxes; + int n; + boxes = pixman_region32_rectangles(&mImpl, &n); + + // if we have no rectangles then we're done + if (!n) + return; + + pixman_box32_t *end = boxes + n; + pixman_box32_t *topRectsEnd = boxes + 1; + pixman_box32_t *topRects = boxes; + + // find the end of the first span of rectangles + while (topRectsEnd < end && topRectsEnd->y1 == topRects->y1) { + topRectsEnd++; + } + + // In order to properly handle convex corners we always visit the sides first + // that way when we visit the corners we can pad using the value from the sides + VisitSides(visit, closure, topRects, topRectsEnd); + + VisitAbove(visit, closure, topRects, topRectsEnd); + + pixman_box32_t *bottomRects = topRects; + pixman_box32_t *bottomRectsEnd = topRectsEnd; + if (topRectsEnd != end) { + do { + // find the next row of rects + bottomRects = topRectsEnd; + bottomRectsEnd = topRectsEnd + 1; + while (bottomRectsEnd < end && bottomRectsEnd->y1 == bottomRects->y1) { + bottomRectsEnd++; + } + + VisitSides(visit, closure, bottomRects, bottomRectsEnd); + + if (topRects->y2 == bottomRects->y1) { + VisitInbetween(visit, closure, topRects, topRectsEnd, + bottomRects, bottomRectsEnd); + } else { + VisitBelow(visit, closure, topRects, topRectsEnd); + VisitAbove(visit, closure, bottomRects, bottomRectsEnd); + } + + topRects = bottomRects; + topRectsEnd = bottomRectsEnd; + } while (bottomRectsEnd != end); + } + + // the bottom of the region doesn't touch anything else so we + // can always visit it at the end + VisitBelow(visit, closure, bottomRects, bottomRectsEnd); +} + + +void nsRegion::SimplifyInward (uint32_t aMaxRects) +{ + NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count"); + + if (GetNumRects() <= aMaxRects) + return; + + SetEmpty(); +} + +uint64_t nsRegion::Area () const +{ + uint64_t area = 0; + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + const nsRect& rect = iter.Get(); + area += uint64_t(rect.width) * rect.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; + } + + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + rect.ScaleRoundOut(aXScale, aYScale); + boxes[i] = RectToBox(rect); + } + + pixman_region32_t region; + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(®ion, boxes, n); + + pixman_region32_fini(&mImpl); + mImpl = region; + return *this; +} + +nsRegion& nsRegion::ScaleInverseRoundOut (float aXScale, float aYScale) +{ + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + rect.ScaleInverseRoundOut(aXScale, aYScale); + boxes[i] = RectToBox(rect); + } + + pixman_region32_t region; + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(®ion, boxes, n); + + pixman_region32_fini(&mImpl); + mImpl = region; + 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) +{ + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + boxes[i] = RectToBox(nsIntRegion::ToRect(TransformRect(nsIntRegion::FromRect(rect), aTransform))); + } + + pixman_region32_t region; + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(®ion, boxes, n); + + pixman_region32_fini(&mImpl); + mImpl = region; + return *this; +} + + +nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut (int32_t aFromAPP, int32_t aToAPP) const +{ + if (aFromAPP == aToAPP) { + return *this; + } + + nsRegion region = *this; + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(®ion.mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP); + boxes[i] = RectToBox(rect); + } + + pixman_region32_t pixmanRegion; + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(&pixmanRegion, boxes, n); + + pixman_region32_fini(®ion.mImpl); + region.mImpl = pixmanRegion; + return region; +} + +nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn (int32_t aFromAPP, int32_t aToAPP) const +{ + if (aFromAPP == aToAPP) { + return *this; + } + + nsRegion region = *this; + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(®ion.mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP); + boxes[i] = RectToBox(rect); + } + + pixman_region32_t pixmanRegion; + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(&pixmanRegion, boxes, n); + + pixman_region32_fini(®ion.mImpl); + region.mImpl = pixmanRegion; + return region; +} + +nsIntRegion nsRegion::ToPixels (nscoord aAppUnitsPerPixel, bool aOutsidePixels) const +{ + nsRegion region = *this; + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(®ion.mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + mozilla::gfx::IntRect deviceRect; + if (aOutsidePixels) + deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel); + else + deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel); + + boxes[i] = RectToBox(deviceRect); + } + + nsIntRegion intRegion; + pixman_region32_fini(&intRegion.mImpl.mImpl); + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(&intRegion.mImpl.mImpl, boxes, n); + + 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 + nsRegion region = *this; + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(®ion.mImpl, &n); + boxes = pixman_region32_rectangles(®ion.mImpl, &n); + for (int i=0; i<n; i++) { + nsRect rect = BoxToRect(boxes[i]); + mozilla::gfx::IntRect irect = rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); + boxes[i] = RectToBox(irect); + } + + nsIntRegion iRegion; + // clear out the initial pixman_region so that we can replace it below + pixman_region32_fini(&iRegion.mImpl.mImpl); + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(&iRegion.mImpl.mImpl, boxes, n); + + return iRegion; +} + +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. + */ + + // make a copy of this region so that we can mutate it in place + nsRegion region = *this; + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(®ion.mImpl, &n); + + nsIntRegion intRegion; + if (n) { + nsRect first = BoxToRect(boxes[0]); + mozilla::gfx::IntRect firstDeviceRect = + first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); + + for (int i=1; i<n; i++) { + nsRect rect = nsRect(boxes[i].x1, boxes[i].y1, + boxes[i].x2 - boxes[i].x1, + boxes[i].y2 - boxes[i].y1); + 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()); + } + } + } + + boxes[i] = RectToBox(deviceRect); + } + + boxes[0] = RectToBox(firstDeviceRect); + + pixman_region32_fini(&intRegion.mImpl.mImpl); + // This will union all of the rectangles and runs in about O(n lg(n)) + pixman_region32_init_rects(&intRegion.mImpl.mImpl, boxes, n); + } + 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 << "["; + + int n; + pixman_box32_t *boxes = pixman_region32_rectangles(const_cast<pixman_region32_t*>(&m.mImpl), &n); + for (int i=0; i<n; i++) { + if (i != 0) { + stream << "; "; + } + stream << boxes[i].x1 << "," << boxes[i].y1 << "," << boxes[i].x2 << "," << boxes[i].y2; + } + + stream << "]"; + return stream; +} + +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 000000000..6f9f7fc8e --- /dev/null +++ b/gfx/src/nsRegion.h @@ -0,0 +1,868 @@ +/* -*- 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 <sys/types.h> // for int32_t +#include <ostream> // for std::ostream +#include "nsCoord.h" // for nscoord +#include "nsError.h" // for nsresult +#include "nsPoint.h" // for nsIntPoint, nsPoint +#include "nsRect.h" // for mozilla::gfx::IntRect, nsRect +#include "nsMargin.h" // for nsIntMargin +#include "nsRegionFwd.h" // for nsIntRegion +#include "nsStringGlue.h" // for nsCString +#include "xpcom-config.h" // for CPP_THROW_NEW +#include "mozilla/ArrayView.h" // for ArrayView +#include "mozilla/Move.h" // for mozilla::Move +#include "mozilla/gfx/MatrixFwd.h" // for mozilla::gfx::Matrix4x4 + +#include "pixman.h" + +/* 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 +}; + +class nsRegion +{ +public: + typedef nsRect RectType; + typedef nsPoint PointType; + typedef nsMargin MarginType; + + nsRegion () { pixman_region32_init(&mImpl); } + MOZ_IMPLICIT nsRegion (const nsRect& aRect) { pixman_region32_init_rect(&mImpl, + aRect.x, + aRect.y, + aRect.width, + aRect.height); } + explicit nsRegion (mozilla::gfx::ArrayView<pixman_box32_t> aRects) + { + pixman_region32_init_rects(&mImpl, aRects.Data(), aRects.Length()); + } + nsRegion (const nsRegion& aRegion) { pixman_region32_init(&mImpl); pixman_region32_copy(&mImpl,aRegion.Impl()); } + nsRegion (nsRegion&& aRegion) { mImpl = aRegion.mImpl; pixman_region32_init(&aRegion.mImpl); } + nsRegion& operator = (nsRegion&& aRegion) { + pixman_region32_fini(&mImpl); + mImpl = aRegion.mImpl; + pixman_region32_init(&aRegion.mImpl); + return *this; + } + ~nsRegion () { pixman_region32_fini(&mImpl); } + 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 Swap(nsRegion* aOther) + { + pixman_region32_t tmp = mImpl; + mImpl = aOther->mImpl; + aOther->mImpl = tmp; + } + + static + nsresult InitStatic() + { + return NS_OK; + } + + static + void ShutdownStatic() {} + + void AndWith(const nsRegion& aOther) + { + And(*this, aOther); + } + void AndWith(const nsRect& aOther) + { + And(*this, aOther); + } + nsRegion& And(const nsRegion& aRgn1, const nsRegion& aRgn2) + { + pixman_region32_intersect(&mImpl, aRgn1.Impl(), aRgn2.Impl()); + return *this; + } + nsRegion& And(const nsRect& aRect, const nsRegion& aRegion) + { + return And(aRegion, aRect); + } + nsRegion& And(const nsRegion& aRegion, const nsRect& aRect) + { + pixman_region32_intersect_rect(&mImpl, aRegion.Impl(), aRect.x, aRect.y, aRect.width, aRect.height); + return *this; + } + nsRegion& And(const nsRect& aRect1, const nsRect& aRect2) + { + nsRect TmpRect; + + TmpRect.IntersectRect(aRect1, aRect2); + return Copy(TmpRect); + } + + nsRegion& OrWith(const nsRegion& aOther) + { + return Or(*this, aOther); + } + nsRegion& OrWith(const nsRect& aOther) + { + return Or(*this, aOther); + } + nsRegion& Or(const nsRegion& aRgn1, const nsRegion& aRgn2) + { + pixman_region32_union(&mImpl, aRgn1.Impl(), aRgn2.Impl()); + return *this; + } + nsRegion& Or(const nsRegion& aRegion, const nsRect& aRect) + { + pixman_region32_union_rect(&mImpl, aRegion.Impl(), aRect.x, aRect.y, aRect.width, aRect.height); + 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& SubOut(const nsRegion& aOther) + { + return Sub(*this, aOther); + } + nsRegion& SubOut(const nsRect& aOther) + { + return Sub(*this, aOther); + } + nsRegion& Sub(const nsRegion& aRgn1, const nsRegion& aRgn2) + { + pixman_region32_subtract(&mImpl, aRgn1.Impl(), aRgn2.Impl()); + return *this; + } + nsRegion& Sub(const nsRegion& aRegion, const nsRect& aRect) + { + return Sub(aRegion, nsRegion(aRect)); + } + nsRegion& Sub(const nsRect& aRect, const nsRegion& aRegion) + { + return Sub(nsRegion(aRect), aRegion); + } + nsRegion& Sub(const nsRect& aRect1, const nsRect& aRect2) + { + Copy(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 pixman_region32_contains_point(Impl(), aX, aY, nullptr); + } + bool Contains (const nsRect& aRect) const + { + pixman_box32_t box = RectToBox(aRect); + return pixman_region32_contains_rectangle(Impl(), &box) == PIXMAN_REGION_IN; + } + bool Contains (const nsRegion& aRgn) const; + bool Intersects (const nsRect& aRect) const; + + void MoveBy (int32_t aXOffset, int32_t aYOffset) + { + MoveBy (nsPoint (aXOffset, aYOffset)); + } + void MoveBy (nsPoint aPt) { pixman_region32_translate(&mImpl, aPt.x, aPt.y); } + void SetEmpty () + { + pixman_region32_clear(&mImpl); + } + + 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 !pixman_region32_not_empty(Impl()); } + bool IsComplex () const { return GetNumRects() > 1; } + bool IsEqual (const nsRegion& aRegion) const + { + return pixman_region32_equal(Impl(), aRegion.Impl()); + } + uint32_t GetNumRects () const + { + // Work around pixman bug. Sometimes pixman creates regions with 1 rect + // that's empty. + uint32_t result = pixman_region32_n_rects(Impl()); + return (result == 1 && GetBounds().IsEmpty()) ? 0 : result; + } + const nsRect GetBounds () const { return BoxToRect(mImpl.extents); } + 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 + */ + MOZ_MUST_USE nsRegion + ScaleToOtherAppUnitsRoundOut (int32_t aFromAPP, int32_t aToAPP) const; + MOZ_MUST_USE 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); + + nsCString ToString() const; + + class RectIterator + { + int mCurrent; // Index of the current entry + int mLimit; // Index one past the final entry. + mutable nsRect mTmp; // The most recently gotten rectangle. + pixman_box32_t *mBoxes; + + public: + explicit RectIterator(const nsRegion& aRegion) + { + mCurrent = 0; + mBoxes = pixman_region32_rectangles(aRegion.Impl(), &mLimit); + // Work around pixman bug. Sometimes pixman creates regions with 1 rect + // that's empty. + if (mLimit == 1 && nsRegion::BoxToRect(mBoxes[0]).IsEmpty()) { + mLimit = 0; + } + } + + bool Done() const { return mCurrent == mLimit; } + + const nsRect& Get() const + { + MOZ_ASSERT(!Done()); + mTmp = nsRegion::BoxToRect(mBoxes[mCurrent]); + NS_ASSERTION(!mTmp.IsEmpty(), "Shouldn't return empty rect"); + return mTmp; + } + + void Next() + { + MOZ_ASSERT(!Done()); + mCurrent++; + } + }; + + RectIterator RectIter() const { return RectIterator(*this); } + +private: + pixman_region32_t mImpl; + +#ifndef MOZ_TREE_PIXMAN + // For compatibility with pixman versions older than 0.25.2. + static inline void + pixman_region32_clear(pixman_region32_t *region) + { + pixman_region32_fini(region); + pixman_region32_init(region); + } +#endif + + nsIntRegion ToPixels(nscoord aAppUnitsPerPixel, bool aOutsidePixels) const; + + nsRegion& Copy (const nsRegion& aRegion) + { + pixman_region32_copy(&mImpl, aRegion.Impl()); + return *this; + } + + nsRegion& Copy (const nsRect& aRect) + { + // pixman needs to distinguish between an empty region and a region + // with one rect so that it can return a different number of rectangles. + // Empty rect: data = empty_box + // 1 rect: data = null + // >1 rect: data = rects + if (aRect.IsEmpty()) { + pixman_region32_clear(&mImpl); + } else { + pixman_box32_t box = RectToBox(aRect); + pixman_region32_reset(&mImpl, &box); + } + return *this; + } + + 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; + } + + + static inline nsRect BoxToRect(const pixman_box32_t &aBox) + { + return nsRect(aBox.x1, aBox.y1, + aBox.x2 - aBox.x1, + aBox.y2 - aBox.y1); + } + + pixman_region32_t* Impl() const + { + return const_cast<pixman_region32_t*>(&mImpl); + } +}; + +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 () {} + 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 (mozilla::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 = mozilla::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 Swap(Derived* aOther) + { + mImpl.Swap(&aOther->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 = RectIter(); !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) + { + 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() {} + 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(mozilla::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=(mozilla::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 000000000..6d5345d14 --- /dev/null +++ b/gfx/src/nsRegionFwd.h @@ -0,0 +1,26 @@ +/* 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/nsRenderingContext.h b/gfx/src/nsRenderingContext.h new file mode 100644 index 000000000..02bb397d6 --- /dev/null +++ b/gfx/src/nsRenderingContext.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 NSRENDERINGCONTEXT__H__ +#define NSRENDERINGCONTEXT__H__ + +#include "gfxContext.h" +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +class MOZ_STACK_CLASS nsRenderingContext final +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + +public: + explicit nsRenderingContext(gfxContext* aThebesContext) + : mThebes(aThebesContext) + {} + + explicit nsRenderingContext(already_AddRefed<gfxContext>&& aThebesContext) + : mThebes(aThebesContext) + {} + + // These accessors will never return null. + gfxContext *ThebesContext() { return mThebes; } + DrawTarget *GetDrawTarget() { return mThebes->GetDrawTarget(); } + +private: + RefPtr<gfxContext> mThebes; +}; + +#endif // NSRENDERINGCONTEXT__H__ diff --git a/gfx/src/nsScriptableRegion.cpp b/gfx/src/nsScriptableRegion.cpp new file mode 100644 index 000000000..f0afff8f4 --- /dev/null +++ b/gfx/src/nsScriptableRegion.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; 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 "nsScriptableRegion.h" +#include <stdint.h> // for uint32_t +#include <sys/types.h> // for int32_t +#include "js/RootingAPI.h" // for Rooted +#include "js/Value.h" // for INT_TO_JSVAL, etc +#include "jsapi.h" // for JS_DefineElement, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc +#include "nsID.h" +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nscore.h" // for NS_IMETHODIMP + +class JSObject; +struct JSContext; + +nsScriptableRegion::nsScriptableRegion() +{ +} + +NS_IMPL_ISUPPORTS(nsScriptableRegion, nsIScriptableRegion) + +NS_IMETHODIMP nsScriptableRegion::Init() +{ + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::SetToRegion(nsIScriptableRegion *aRegion) +{ + aRegion->GetRegion(&mRegion); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::SetToRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight) +{ + mRegion = mozilla::gfx::IntRect(aX, aY, aWidth, aHeight); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::IntersectRegion(nsIScriptableRegion *aRegion) +{ + nsIntRegion region; + aRegion->GetRegion(®ion); + mRegion.And(mRegion, region); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::IntersectRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight) +{ + mRegion.And(mRegion, mozilla::gfx::IntRect(aX, aY, aWidth, aHeight)); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::UnionRegion(nsIScriptableRegion *aRegion) +{ + nsIntRegion region; + aRegion->GetRegion(®ion); + mRegion.Or(mRegion, region); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::UnionRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight) +{ + mRegion.Or(mRegion, mozilla::gfx::IntRect(aX, aY, aWidth, aHeight)); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::SubtractRegion(nsIScriptableRegion *aRegion) +{ + nsIntRegion region; + aRegion->GetRegion(®ion); + mRegion.Sub(mRegion, region); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::SubtractRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight) +{ + mRegion.Sub(mRegion, mozilla::gfx::IntRect(aX, aY, aWidth, aHeight)); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::IsEmpty(bool *isEmpty) +{ + *isEmpty = mRegion.IsEmpty(); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::IsEqualRegion(nsIScriptableRegion *aRegion, bool *isEqual) +{ + nsIntRegion region; + aRegion->GetRegion(®ion); + *isEqual = mRegion.IsEqual(region); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::GetBoundingBox(int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight) +{ + mozilla::gfx::IntRect boundRect = mRegion.GetBounds(); + *aX = boundRect.x; + *aY = boundRect.y; + *aWidth = boundRect.width; + *aHeight = boundRect.height; + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::Offset(int32_t aXOffset, int32_t aYOffset) +{ + mRegion.MoveBy(aXOffset, aYOffset); + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::ContainsRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, bool *containsRect) +{ + *containsRect = mRegion.Contains(mozilla::gfx::IntRect(aX, aY, aWidth, aHeight)); + return NS_OK; +} + + +NS_IMETHODIMP nsScriptableRegion::GetRegion(nsIntRegion* outRgn) +{ + *outRgn = mRegion; + return NS_OK; +} + +NS_IMETHODIMP nsScriptableRegion::GetRects(JSContext* aCx, JS::MutableHandle<JS::Value> aRects) +{ + uint32_t numRects = mRegion.GetNumRects(); + + if (!numRects) { + aRects.setNull(); + return NS_OK; + } + + JS::Rooted<JSObject*> destArray(aCx, JS_NewArrayObject(aCx, numRects * 4)); + if (!destArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + aRects.setObject(*destArray); + + uint32_t n = 0; + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + const mozilla::gfx::IntRect& rect = iter.Get(); + if (!JS_DefineElement(aCx, destArray, n, rect.x, JSPROP_ENUMERATE) || + !JS_DefineElement(aCx, destArray, n + 1, rect.y, JSPROP_ENUMERATE) || + !JS_DefineElement(aCx, destArray, n + 2, rect.width, JSPROP_ENUMERATE) || + !JS_DefineElement(aCx, destArray, n + 3, rect.height, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + n += 4; + } + + return NS_OK; +} diff --git a/gfx/src/nsScriptableRegion.h b/gfx/src/nsScriptableRegion.h new file mode 100644 index 000000000..094093c75 --- /dev/null +++ b/gfx/src/nsScriptableRegion.h @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +#ifndef nsScriptableRegion_h +#define nsScriptableRegion_h + +#include "nsIScriptableRegion.h" +#include "nsISupports.h" +#include "nsRegion.h" +#include "mozilla/Attributes.h" + +class nsScriptableRegion final : public nsIScriptableRegion { +public: + nsScriptableRegion(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSISCRIPTABLEREGION + +private: + ~nsScriptableRegion() {} + nsIntRegion mRegion; +}; + +#endif diff --git a/gfx/src/nsSize.h b/gfx/src/nsSize.h new file mode 100644 index 000000000..f27c478f9 --- /dev/null +++ b/gfx/src/nsSize.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; 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/. */ + +#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; + + nsSize() : Super() {} + 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 + */ + MOZ_MUST_USE 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 000000000..3ef3f443b --- /dev/null +++ b/gfx/src/nsThebesFontEnumerator.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsCOMPtr.h" // for nsCOMPtr +#include "nsDebug.h" // for NS_ENSURE_ARG_POINTER +#include "nsError.h" // for NS_OK, NS_FAILED, nsresult +#include "nsIAtom.h" // for nsIAtom, 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 + +NS_IMPL_ISUPPORTS(nsThebesFontEnumerator, nsIFontEnumerator) + +nsThebesFontEnumerator::nsThebesFontEnumerator() +{ +} + +NS_IMETHODIMP +nsThebesFontEnumerator::EnumerateAllFonts(uint32_t *aCount, + char16_t ***aResult) +{ + return EnumerateFonts (nullptr, nullptr, aCount, aResult); +} + +NS_IMETHODIMP +nsThebesFontEnumerator::EnumerateFonts(const char *aLangGroup, + const char *aGeneric, + uint32_t *aCount, + char16_t ***aResult) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aResult); + + nsTArray<nsString> fontList; + + nsAutoCString generic; + if (aGeneric) + generic.Assign(aGeneric); + else + generic.SetIsVoid(true); + + nsCOMPtr<nsIAtom> langGroupAtom; + if (aLangGroup) { + nsAutoCString lowered; + lowered.Assign(aLangGroup); + ToLowerCase(lowered); + langGroupAtom = NS_Atomize(lowered); + } + + nsresult rv = gfxPlatform::GetPlatform()->GetFontList(langGroupAtom, generic, fontList); + + if (NS_FAILED(rv)) { + *aCount = 0; + *aResult = nullptr; + /* XXX in this case, do we want to return the CSS generics? */ + return NS_OK; + } + + char16_t **fs = static_cast<char16_t **> + (moz_xmalloc(fontList.Length() * sizeof(char16_t*))); + for (uint32_t i = 0; i < fontList.Length(); i++) { + fs[i] = ToNewUnicode(fontList[i]); + } + + *aResult = fs; + *aCount = fontList.Length(); + + 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) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + 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; + } + + nsAutoString family; + nsresult rv = gfxPlatform::GetPlatform()-> + GetStandardFamilyName(nsDependentString(aName), family); + if (NS_FAILED(rv) || family.IsEmpty()) { + *aResult = nullptr; + return NS_OK; + } + *aResult = ToNewUnicode(family); + return NS_OK; +} diff --git a/gfx/src/nsThebesFontEnumerator.h b/gfx/src/nsThebesFontEnumerator.h new file mode 100644 index 000000000..666663e3a --- /dev/null +++ b/gfx/src/nsThebesFontEnumerator.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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() {} +public: + nsThebesFontEnumerator(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSIFONTENUMERATOR +}; + +#endif /* _NSTHEBESFONTENUMERATOR_H_ */ diff --git a/gfx/src/nsThebesGfxFactory.cpp b/gfx/src/nsThebesGfxFactory.cpp new file mode 100644 index 000000000..8e1e18d5c --- /dev/null +++ b/gfx/src/nsThebesGfxFactory.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/Attributes.h" // for final +#include "mozilla/Module.h" // for Module, Module::CIDEntry, etc +#include "mozilla/ModuleUtils.h" +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsError.h" // for NS_ERROR_NO_AGGREGATION, etc +#include "nsGfxCIID.h" // for NS_FONT_ENUMERATOR_CID, etc +#include "nsID.h" // for NS_DEFINE_NAMED_CID, etc +#include "nsIScriptableRegion.h" // for nsIScriptableRegion +#include "nsISupports.h" // for NS_DECL_ISUPPORTS, etc +#include "nsScriptableRegion.h" // for nsScriptableRegion +#include "nsThebesFontEnumerator.h" // for nsThebesFontEnumerator + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsThebesFontEnumerator) + +static nsresult +nsScriptableRegionConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr<nsIScriptableRegion> scriptableRgn = new nsScriptableRegion(); + return scriptableRgn->QueryInterface(aIID, aResult); +} + +NS_DEFINE_NAMED_CID(NS_FONT_ENUMERATOR_CID); +NS_DEFINE_NAMED_CID(NS_SCRIPTABLE_REGION_CID); + +static const mozilla::Module::CIDEntry kThebesCIDs[] = { + { &kNS_FONT_ENUMERATOR_CID, false, nullptr, nsThebesFontEnumeratorConstructor }, + { &kNS_SCRIPTABLE_REGION_CID, false, nullptr, nsScriptableRegionConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kThebesContracts[] = { + { "@mozilla.org/gfx/fontenumerator;1", &kNS_FONT_ENUMERATOR_CID }, + { "@mozilla.org/gfx/region;1", &kNS_SCRIPTABLE_REGION_CID }, + { nullptr } +}; + +static const mozilla::Module kThebesModule = { + mozilla::Module::kVersion, + kThebesCIDs, + kThebesContracts, + nullptr, + nullptr, + nullptr, + nullptr +}; + +NSMODULE_DEFN(nsGfxModule) = &kThebesModule; diff --git a/gfx/src/nsThemeConstants.h b/gfx/src/nsThemeConstants.h new file mode 100644 index 000000000..7825b9c6f --- /dev/null +++ b/gfx/src/nsThemeConstants.h @@ -0,0 +1,296 @@ +/* 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/. */ + +// No appearance at all. +#define NS_THEME_NONE 0 + +// A typical dialog button. +#define NS_THEME_BUTTON 1 + +// A radio element within a radio group. +#define NS_THEME_RADIO 2 + +// A checkbox element. +#define NS_THEME_CHECKBOX 3 + +// A rectangular button that contains complex content +// like images (e.g. HTML <button> elements) +#define NS_THEME_BUTTON_BEVEL 7 + +// A themed focus outline (for outline:auto) +#define NS_THEME_FOCUS_OUTLINE 8 + +// The toolbox that contains the toolbars. +#define NS_THEME_TOOLBOX 11 + +// A toolbar in an application window. +#define NS_THEME_TOOLBAR 12 + +// A single toolbar button (with no associated dropdown) +#define NS_THEME_TOOLBARBUTTON 13 + +// A dual toolbar button (e.g., a Back button with a dropdown) +#define NS_THEME_DUALBUTTON 14 + +// The dropdown portion of a toolbar button +#define NS_THEME_TOOLBARBUTTON_DROPDOWN 15 + +// Various arrows that go in buttons +#define NS_THEME_BUTTON_ARROW_UP 16 +#define NS_THEME_BUTTON_ARROW_DOWN 17 +#define NS_THEME_BUTTON_ARROW_NEXT 18 +#define NS_THEME_BUTTON_ARROW_PREVIOUS 19 + +// A separator. Can be horizontal or vertical. +#define NS_THEME_SEPARATOR 20 + +// The gripper for a toolbar. +#define NS_THEME_TOOLBARGRIPPER 21 + +// A splitter. Can be horizontal or vertical. +#define NS_THEME_SPLITTER 22 + +// A status bar in a main application window. +#define NS_THEME_STATUSBAR 23 + +// A single pane of a status bar. +#define NS_THEME_STATUSBARPANEL 24 + +// The resizer background area in a status bar +// for the resizer widget in the corner of a window. +#define NS_THEME_RESIZERPANEL 25 + +// The resizer itself. +#define NS_THEME_RESIZER 26 + +// List boxes +#define NS_THEME_LISTBOX 31 + +// A listbox item +#define NS_THEME_LISTITEM 32 + +// A tree widget +#define NS_THEME_TREEVIEW 41 + +// A tree item +#define NS_THEME_TREEITEM 42 + +// A tree widget twisty +#define NS_THEME_TREETWISTY 43 + +// A tree widget branch line +#define NS_THEME_TREELINE 44 + +// A listbox or tree widget header +#define NS_THEME_TREEHEADER 45 + +// An individual header cell +#define NS_THEME_TREEHEADERCELL 46 + +// The sort arrow for a header. +#define NS_THEME_TREEHEADERSORTARROW 47 + +// Open tree widget twisty +#define NS_THEME_TREETWISTYOPEN 48 + +// A horizontal progress bar. +#define NS_THEME_PROGRESSBAR 51 + +// The progress bar's progress indicator +#define NS_THEME_PROGRESSCHUNK 52 + +// A vertical progress bar. +#define NS_THEME_PROGRESSBAR_VERTICAL 53 + +// A vertical progress chunk +#define NS_THEME_PROGRESSCHUNK_VERTICAL 54 + +// A horizontal meter bar. +#define NS_THEME_METERBAR 55 + +// The meter bar's meter indicator +#define NS_THEME_METERCHUNK 56 + +// A single tab in a tab widget. +#define NS_THEME_TAB 61 + +// A single pane (inside the tabpanels container) +#define NS_THEME_TABPANEL 62 + +// The tab panels container. +#define NS_THEME_TABPANELS 65 + +// The tabs scroll arrows (left/right) +#define NS_THEME_TAB_SCROLL_ARROW_BACK 66 +#define NS_THEME_TAB_SCROLL_ARROW_FORWARD 67 + +// A tooltip +#define NS_THEME_TOOLTIP 71 + +// A spin control (up/down control for time/date pickers) +#define NS_THEME_SPINNER 72 + +// The up button of a spin control +#define NS_THEME_SPINNER_UPBUTTON 73 + +// The down button of a spin control +#define NS_THEME_SPINNER_DOWNBUTTON 74 + +// The textfield of a spin control +#define NS_THEME_SPINNER_TEXTFIELD 75 + +// For HTML's <input type=number> +#define NS_THEME_NUMBER_INPUT 76 + +// A scrollbar. +#define NS_THEME_SCROLLBAR 80 + +// A small scrollbar. +#define NS_THEME_SCROLLBAR_SMALL 81 + +// The scrollbar slider +#define NS_THEME_SCROLLBAR_HORIZONTAL 82 +#define NS_THEME_SCROLLBAR_VERTICAL 83 + +// A scrollbar button (up/down/left/right) +#define NS_THEME_SCROLLBARBUTTON_UP 84 +#define NS_THEME_SCROLLBARBUTTON_DOWN 85 +#define NS_THEME_SCROLLBARBUTTON_LEFT 86 +#define NS_THEME_SCROLLBARBUTTON_RIGHT 87 + +// The scrollbar track +#define NS_THEME_SCROLLBARTRACK_HORIZONTAL 88 +#define NS_THEME_SCROLLBARTRACK_VERTICAL 89 + +// The scrollbar thumb +#define NS_THEME_SCROLLBARTHUMB_HORIZONTAL 90 +#define NS_THEME_SCROLLBARTHUMB_VERTICAL 91 + +// A non-disappearing scrollbar. +#define NS_THEME_SCROLLBAR_NON_DISAPPEARING 92 + +// A textfield or text area +#define NS_THEME_TEXTFIELD 95 + +// The caret of a text area +#define NS_THEME_CARET 96 + +// A multiline text field +#define NS_THEME_TEXTFIELD_MULTILINE 97 + +// A searchfield +#define NS_THEME_SEARCHFIELD 98 + +// A dropdown list. +#define NS_THEME_MENULIST 101 + +// The dropdown button(s) that open up a dropdown list. +#define NS_THEME_MENULIST_BUTTON 102 + +// The text part of a dropdown list, to left of button +#define NS_THEME_MENULIST_TEXT 103 + +// An editable textfield with a dropdown list (a combobox) +#define NS_THEME_MENULIST_TEXTFIELD 104 + +// A slider +#define NS_THEME_SCALE_HORIZONTAL 111 +#define NS_THEME_SCALE_VERTICAL 112 + +// A slider's thumb +#define NS_THEME_SCALETHUMB_HORIZONTAL 113 +#define NS_THEME_SCALETHUMB_VERTICAL 114 + +// If the platform supports it, the left/right chunks +// of the slider thumb +#define NS_THEME_SCALETHUMBSTART 115 +#define NS_THEME_SCALETHUMBEND 116 + +// The ticks for a slider. +#define NS_THEME_SCALETHUMBTICK 117 + +// nsRangeFrame and its subparts +#define NS_THEME_RANGE 120 +#define NS_THEME_RANGE_THUMB 121 + +// A groupbox +#define NS_THEME_GROUPBOX 149 + +// A generic container that always repaints on state +// changes. This is a hack to make checkboxes and +// radio buttons work. +#define NS_THEME_CHECKBOX_CONTAINER 150 +#define NS_THEME_RADIO_CONTAINER 151 + +// The label part of a checkbox or radio button, used for painting +// a focus outline. +#define NS_THEME_CHECKBOX_LABEL 152 +#define NS_THEME_RADIO_LABEL 153 + +// The focus outline box inside of a button +#define NS_THEME_BUTTON_FOCUS 154 + +// Window and dialog backgrounds +#define NS_THEME_WINDOW 200 +#define NS_THEME_DIALOG 201 + +// Menu Bar background +#define NS_THEME_MENUBAR 210 +// Menu Popup background +#define NS_THEME_MENUPOPUP 211 +// <menu> and <menuitem> appearances +#define NS_THEME_MENUITEM 212 +#define NS_THEME_CHECKMENUITEM 213 +#define NS_THEME_RADIOMENUITEM 214 + +// menu checkbox/radio appearances +#define NS_THEME_MENUCHECKBOX 215 +#define NS_THEME_MENURADIO 216 +#define NS_THEME_MENUSEPARATOR 217 +#define NS_THEME_MENUARROW 218 +// An image in the menu gutter, like in bookmarks or history +#define NS_THEME_MENUIMAGE 219 +// For text on non-iconic menuitems only +#define NS_THEME_MENUITEMTEXT 220 + +// Vista Rebars +#define NS_THEME_WIN_COMMUNICATIONS_TOOLBOX 221 +#define NS_THEME_WIN_MEDIA_TOOLBOX 222 +#define NS_THEME_WIN_BROWSERTABBAR_TOOLBOX 223 + +// Titlebar elements on the Mac +#define NS_THEME_MAC_FULLSCREEN_BUTTON 226 + +// Mac help button +#define NS_THEME_MAC_HELP_BUTTON 227 + +// Vista glass +#define NS_THEME_WIN_BORDERLESS_GLASS 229 +#define NS_THEME_WIN_GLASS 230 + +// Windows themed window frame elements +#define NS_THEME_WINDOW_TITLEBAR 231 +#define NS_THEME_WINDOW_TITLEBAR_MAXIMIZED 232 +#define NS_THEME_WINDOW_FRAME_LEFT 233 +#define NS_THEME_WINDOW_FRAME_RIGHT 234 +#define NS_THEME_WINDOW_FRAME_BOTTOM 235 +#define NS_THEME_WINDOW_BUTTON_CLOSE 236 +#define NS_THEME_WINDOW_BUTTON_MINIMIZE 237 +#define NS_THEME_WINDOW_BUTTON_MAXIMIZE 238 +#define NS_THEME_WINDOW_BUTTON_RESTORE 239 +#define NS_THEME_WINDOW_BUTTON_BOX 240 +#define NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED 241 + +// moz-apperance style used in setting proper glass margins +#define NS_THEME_WIN_EXCLUDE_GLASS 242 + +#define NS_THEME_MAC_VIBRANCY_LIGHT 243 +#define NS_THEME_MAC_VIBRANCY_DARK 244 +#define NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN 245 +#define NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED 246 + +#define NS_THEME_GTK_INFO_BAR 247 +#define NS_THEME_MAC_SOURCE_LIST 248 +#define NS_THEME_MAC_SOURCE_LIST_SELECTION 249 +#define NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION 250 diff --git a/gfx/src/nsTransform2D.cpp b/gfx/src/nsTransform2D.cpp new file mode 100644 index 000000000..becea4392 --- /dev/null +++ b/gfx/src/nsTransform2D.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; 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 "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 000000000..8999b2cf3 --- /dev/null +++ b/gfx/src/nsTransform2D.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; 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/. */ + +#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) { } + + /** + * 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 |