diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/src | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/src')
50 files changed, 12471 insertions, 0 deletions
diff --git a/gfx/src/AAStroke.h b/gfx/src/AAStroke.h new file mode 100644 index 0000000000..be6d0e7e4c --- /dev/null +++ b/gfx/src/AAStroke.h @@ -0,0 +1,52 @@ +/* -*- 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_AA_STROKE_H +#define MOZILLA_GFX_AA_STROKE_H + +#include <stddef.h> + +namespace AAStroke { + +enum class LineCap { Round, Square, Butt }; +enum class LineJoin { Round, Miter, Bevel }; +struct StrokeStyle { + float width; + LineCap cap; + LineJoin join; + float miter_limit; +}; +struct Stroker; +struct OutputVertex { + float x; + float y; + float coverage; +}; +struct VertexBuffer { + OutputVertex* data; + size_t len; +}; + +extern "C" { +Stroker* aa_stroke_new(const StrokeStyle* style, + OutputVertex* output_ptr = nullptr, + size_t output_capacity = 0); +void aa_stroke_move_to(Stroker* s, float x, float y, bool closed); +void aa_stroke_line_to(Stroker* s, float x, float y, bool end); +void aa_stroke_curve_to(Stroker* s, float c1x, float c1y, float c2x, float c2y, + float x, float y, bool end); +void aa_stroke_close(Stroker* s); +VertexBuffer aa_stroke_finish(Stroker* s); +VertexBuffer aa_stroke_filled_circle(float cx, float cy, float radius, + OutputVertex* output_ptr = nullptr, + size_t output_capacity = 0); +void aa_stroke_vertex_buffer_release(VertexBuffer vb); +void aa_stroke_release(Stroker* s); +}; + +} // namespace AAStroke + +#endif // MOZILLA_GFX_AA_STROKE_H diff --git a/gfx/src/AppUnits.h b/gfx/src/AppUnits.h new file mode 100644 index 0000000000..2b00ec7d56 --- /dev/null +++ b/gfx/src/AppUnits.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AppUnits_h +#define mozilla_AppUnits_h + +#include <stdint.h> + +namespace mozilla { +constexpr inline int32_t AppUnitsPerCSSPixel() { return 60; } +constexpr inline int32_t AppUnitsPerCSSInch() { + return 96 * AppUnitsPerCSSPixel(); +} +} // namespace mozilla +#endif /* _NS_APPUNITS_H_ */ diff --git a/gfx/src/ArrayView.h b/gfx/src/ArrayView.h new file mode 100644 index 0000000000..e5d5ace976 --- /dev/null +++ b/gfx/src/ArrayView.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_ARRAY_VIEW_H_ +#define MOZILLA_GFX_ARRAY_VIEW_H_ + +#include "nsTArray.h" + +/* This is similar to mfbt/Range.h but has implicit conversion + * from nsTArray and less bounds checking. + * For now, prefer Range over ArrayView */ + +namespace mozilla { +namespace gfx { + +template <typename T> +class ArrayView { + public: + MOZ_IMPLICIT ArrayView(const nsTArray<T>& aData) + : mData(aData.Elements()), mLength(aData.Length()) {} + ArrayView(const T* aData, const size_t aLength) + : mData(aData), mLength(aLength) {} + const T& operator[](const size_t aIdx) const { return mData[aIdx]; } + size_t Length() const { return mLength; } + const T* Data() const { return mData; } + + private: + const T* mData; + const size_t mLength; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_ARRAY_VIEW_H_ */ diff --git a/gfx/src/CompositorHitTestInfo.h b/gfx/src/CompositorHitTestInfo.h new file mode 100644 index 0000000000..840d1e5dd5 --- /dev/null +++ b/gfx/src/CompositorHitTestInfo.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITORHITTESTINFO_H_ +#define MOZILLA_GFX_COMPOSITORHITTESTINFO_H_ + +#include "mozilla/EnumSet.h" +#include "mozilla/EnumTypeTraits.h" + +namespace mozilla { +namespace gfx { + +// This set of flags is used to figure out what information a frame has +// that is relevant to hit-testing in the compositor. The flags are +// intentionally set up so that if all of them are 0 the item is effectively +// invisible to hit-testing, and no information for this frame needs to be +// sent to the compositor. +// Each enumerator is annotated with the value it contributes to an +// EnumSet (2 ^ <value of enumerator>), in hexadecimal. +enum class CompositorHitTestFlags : uint8_t { + // The frame participates in hit-testing + eVisibleToHitTest = 0, // 0x0001 + + // The frame may have odd shapes that requires the main thread to do accurate + // hit-testing. + eIrregularArea, // 0x0002 + // The frame has APZ-aware listeners and so inputs targeted at this area + // need to be handled by the main thread before APZ can use them, as they + // might be prevent-defaulted. + eApzAwareListeners, // 0x0004 + // This is an inactive scrollframe or unlayerized scrollthumb. In this state + // it cannot be used by APZ for async scrolling, so APZ will defer to the main + // thread. + eInactiveScrollframe, // 0x0008 + + // The touch action flags are set up so that the default of + // touch-action:auto on an element leaves all the flags as 0. + eTouchActionPanXDisabled, // 0x0010 + eTouchActionPanYDisabled, // 0x0020 + eTouchActionPinchZoomDisabled, // 0x0040 + eTouchActionAnimatingZoomDisabled, // 0x0080 + + // The frame is a scrollbar or a subframe inside a scrollbar (including + // scroll thumbs) + eScrollbar, // 0x0100 + // The frame is a scrollthumb. If this is set then eScrollbar will also be + // set, unless gecko somehow generates a scroll thumb without a containing + // scrollbar. + eScrollbarThumb, // 0x0200 + // If eScrollbar is set, this flag indicates if the scrollbar is a vertical + // one (if set) or a horizontal one (if not set) + eScrollbarVertical, // 0x0400 + + // Events targeting this frame should only be processed if a target + // confirmation is received from the main thread. If no such confirmation + // is received within a timeout period, the event may be dropped. + // Only meaningful in combination with eDispatchToContent. + eRequiresTargetConfirmation, // 0x0800 + + // Bits 0x1000, 0x2000, 0x4000, and 0x8000 are used by SideBitsPacked in + // WebRenderAPI.cpp when we pack SideBits and CompositorHitTestInfo into a + // uint16_t and pass in into webrender to store in the item tag. + +}; + +using CompositorHitTestInfo = EnumSet<CompositorHitTestFlags, uint32_t>; + +// A CompositorHitTestInfo with none of the flags set +constexpr CompositorHitTestInfo CompositorHitTestInvisibleToHit; + +// Mask to check for all the touch-action flags at once +constexpr CompositorHitTestInfo CompositorHitTestTouchActionMask( + CompositorHitTestFlags::eTouchActionPanXDisabled, + CompositorHitTestFlags::eTouchActionPanYDisabled, + CompositorHitTestFlags::eTouchActionPinchZoomDisabled, + CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled); + +// Mask to check all the flags that involve APZ waiting for results from the +// main thread +constexpr CompositorHitTestInfo CompositorHitTestDispatchToContent( + CompositorHitTestFlags::eIrregularArea, + CompositorHitTestFlags::eApzAwareListeners, + CompositorHitTestFlags::eInactiveScrollframe); + +} // namespace gfx + +// Used for IPDL serialization. The 'value' have to be the biggest enum from +// CompositorHitTestFlags. +template <> +struct MaxEnumValue<::mozilla::gfx::CompositorHitTestFlags> { + static constexpr unsigned int value = static_cast<unsigned int>( + gfx::CompositorHitTestFlags::eRequiresTargetConfirmation); +}; + +namespace gfx { + +// Checks if the CompositorHitTestFlags max enum value is less than N. +template <int N> +static constexpr bool DoesCompositorHitTestInfoFitIntoBits() { + if (MaxEnumValue<CompositorHitTestInfo::valueType>::value < N) { + return true; + } + + return false; +} +} // namespace gfx + +} // namespace mozilla + +#endif /* MOZILLA_GFX_COMPOSITORHITTESTINFO_H_ */ diff --git a/gfx/src/DriverCrashGuard.cpp b/gfx/src/DriverCrashGuard.cpp new file mode 100644 index 0000000000..9b68c07e55 --- /dev/null +++ b/gfx/src/DriverCrashGuard.cpp @@ -0,0 +1,528 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "DriverCrashGuard.h" +#include "gfxEnv.h" +#include "gfxConfig.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsExceptionHandler.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Components.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/dom/ContentChild.h" + +namespace mozilla { +namespace gfx { + +static const size_t NUM_CRASH_GUARD_TYPES = size_t(CrashGuardType::NUM_TYPES); +static const char* sCrashGuardNames[] = { + "d3d11layers", + "glcontext", + "wmfvpxvideo", +}; +static_assert(MOZ_ARRAY_LENGTH(sCrashGuardNames) == NUM_CRASH_GUARD_TYPES, + "CrashGuardType updated without a name string"); + +static inline void BuildCrashGuardPrefName(CrashGuardType aType, + nsCString& aOutPrefName) { + MOZ_ASSERT(aType < CrashGuardType::NUM_TYPES); + MOZ_ASSERT(sCrashGuardNames[size_t(aType)]); + + aOutPrefName.AssignLiteral("gfx.crash-guard.status."); + aOutPrefName.Append(sCrashGuardNames[size_t(aType)]); +} + +DriverCrashGuard::DriverCrashGuard(CrashGuardType aType, + dom::ContentParent* aContentParent) + : mType(aType), + mMode(aContentParent ? Mode::Proxy : Mode::Normal), + mInitialized(false), + mGuardActivated(false), + mCrashDetected(false) { + BuildCrashGuardPrefName(aType, mStatusPref); +} + +void DriverCrashGuard::InitializeIfNeeded() { + if (mInitialized) { + return; + } + + mInitialized = true; + Initialize(); +} + +static inline bool AreCrashGuardsEnabled(CrashGuardType aType) { + // Crash guard isn't supported in the GPU or RDD process since the entire + // process is basically a crash guard. + if (XRE_IsGPUProcess() || XRE_IsRDDProcess()) { + return false; + } +#ifdef NIGHTLY_BUILD + // We only use the crash guard on non-nightly channels, since the nightly + // channel is for development and having graphics features perma-disabled + // is rather annoying. Unless the user forces is with an environment + // variable, which comes in handy for testing. + // We handle the WMFVPXVideo crash guard differently to the other and always + // enable it as it completely breaks playback and there's no way around it. + if (aType != CrashGuardType::WMFVPXVideo) { + return gfxEnv::MOZ_FORCE_CRASH_GUARD_NIGHTLY(); + } +#endif + // Check to see if all guards have been disabled through the environment. + return !gfxEnv::MOZ_DISABLE_CRASH_GUARD(); +} + +void DriverCrashGuard::Initialize() { + if (!AreCrashGuardsEnabled(mType)) { + return; + } + + // Using DriverCrashGuard off the main thread currently does not work. Under + // e10s it could conceivably work by dispatching the IPC calls via the main + // thread. In the parent process this would be harder. For now, we simply + // exit early instead. + if (!NS_IsMainThread()) { + return; + } + + mGfxInfo = components::GfxInfo::Service(); + + if (XRE_IsContentProcess()) { + // Ask the parent whether or not activating the guard is okay. The parent + // won't bother if it detected a crash. + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendBeginDriverCrashGuard(uint32_t(mType), &mCrashDetected); + if (mCrashDetected) { + LogFeatureDisabled(); + return; + } + + ActivateGuard(); + return; + } + + // Always check whether or not the lock file exists. For example, we could + // have crashed creating a D3D9 device in the parent process, and on restart + // are now requesting one in the child process. We catch everything here. + if (RecoverFromCrash()) { + mCrashDetected = true; + return; + } + + // If the environment has changed, we always activate the guard. In the + // parent process this performs main-thread disk I/O. Child process guards + // only incur an IPC cost, so if we're proxying for a child process, we + // play it safe and activate the guard as long as we don't expect it to + // crash. + if (CheckOrRefreshEnvironment() || + (mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed)) { + ActivateGuard(); + return; + } + + // If we got here and our status is "crashed", then the environment has not + // updated and we do not want to attempt to use the driver again. + if (GetStatus() == DriverInitStatus::Crashed) { + mCrashDetected = true; + LogFeatureDisabled(); + } +} + +DriverCrashGuard::~DriverCrashGuard() { + if (!mGuardActivated) { + return; + } + + if (XRE_IsParentProcess()) { + if (mGuardFile) { + mGuardFile->Remove(false); + } + + // If during our initialization, no other process encountered a crash, we + // proceed to mark the status as okay. + if (GetStatus() != DriverInitStatus::Crashed) { + SetStatus(DriverInitStatus::Okay); + } + } else { + dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType)); + } + + CrashReporter::RemoveCrashReportAnnotation( + CrashReporter::Annotation::GraphicsStartupTest); +} + +bool DriverCrashGuard::Crashed() { + InitializeIfNeeded(); + + // Note, we read mCrashDetected instead of GetStatus(), since in child + // processes we're not guaranteed that the prefs have been synced in + // time. + return mCrashDetected; +} + +nsCOMPtr<nsIFile> DriverCrashGuard::GetGuardFile() { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCString filename; + filename.Assign(sCrashGuardNames[size_t(mType)]); + filename.AppendLiteral(".guard"); + + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(file)); + if (!file) { + return nullptr; + } + if (!NS_SUCCEEDED(file->AppendNative(filename))) { + return nullptr; + } + return file; +} + +void DriverCrashGuard::ActivateGuard() { + mGuardActivated = true; + + // Anotate crash reports only if we're a real guard. Otherwise, we could + // attribute a random parent process crash to a graphics problem in a child + // process. + if (mMode != Mode::Proxy) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::GraphicsStartupTest, true); + } + + // If we're in the content process, the rest of the guarding is handled + // in the parent. + if (XRE_IsContentProcess()) { + return; + } + + SetStatus(DriverInitStatus::Attempting); + + if (mMode != Mode::Proxy) { + // In parent process guards, we use two tombstones to detect crashes: a + // preferences and a zero-byte file on the filesystem. + FlushPreferences(); + + // Create a temporary tombstone/lockfile. + FILE* fp = nullptr; + mGuardFile = GetGuardFile(); + if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) { + return; + } + fclose(fp); + } +} + +void DriverCrashGuard::NotifyCrashed() { + SetStatus(DriverInitStatus::Crashed); + FlushPreferences(); + LogCrashRecovery(); +} + +bool DriverCrashGuard::RecoverFromCrash() { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsIFile> file = GetGuardFile(); + bool exists; + if ((file && NS_SUCCEEDED(file->Exists(&exists)) && exists) || + (GetStatus() == DriverInitStatus::Attempting)) { + // If we get here, we've just recovered from a crash. Disable acceleration + // until the environment changes. + if (file) { + file->Remove(false); + } + NotifyCrashed(); + return true; + } + return false; +} + +// Return true if the caller should proceed to guard for crashes. False if +// the environment has not changed. We persist the "changed" status across +// calls, so that after an environment changes, all guards for the new +// session are activated rather than just the first. +bool DriverCrashGuard::CheckOrRefreshEnvironment() { + // Our result can be cached statically since we don't check live prefs. + // We need to cache once per crash guard type. + // The first call to CheckOrRefrechEnvironment will always return true should + // the configuration had changed, following calls will return false. + static bool sBaseInfoChanged[NUM_CRASH_GUARD_TYPES]; + static bool sBaseInfoChecked[NUM_CRASH_GUARD_TYPES]; + + const uint32_t type = uint32_t(mType); + if (!sBaseInfoChecked[type]) { + // None of the prefs we care about, so we cache the result statically. + sBaseInfoChecked[type] = true; + sBaseInfoChanged[type] = UpdateBaseEnvironment(); + } + + // Always update the full environment, even if the base info didn't change. + bool result = UpdateEnvironment() || sBaseInfoChanged[type] || + GetStatus() == DriverInitStatus::Unknown; + sBaseInfoChanged[type] = false; + return result; +} + +bool DriverCrashGuard::UpdateBaseEnvironment() { + bool changed = false; + if (mGfxInfo) { + nsString value; + + // Driver properties. + mGfxInfo->GetAdapterDriverVersion(value); + changed |= CheckAndUpdatePref("driverVersion", value); + mGfxInfo->GetAdapterDeviceID(value); + changed |= CheckAndUpdatePref("deviceID", value); + } + + // Firefox properties. + changed |= CheckAndUpdatePref( + "appVersion", NS_LITERAL_STRING_FROM_CSTRING(MOZ_APP_VERSION)); + + return changed; +} + +bool DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault) { + if (!mGfxInfo) { + return aDefault; + } + int32_t status; + nsCString discardFailureId; + if (!NS_SUCCEEDED( + mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) { + return false; + } + return status == nsIGfxInfo::FEATURE_STATUS_OK; +} + +bool DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName, + bool aCurrentValue) { + std::string pref = GetFullPrefName(aPrefName); + + bool oldValue; + if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) && + oldValue == aCurrentValue) { + return false; + } + Preferences::SetBool(pref.c_str(), aCurrentValue); + return true; +} + +bool DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName, + const nsAString& aCurrentValue) { + std::string pref = GetFullPrefName(aPrefName); + + nsAutoString oldValue; + Preferences::GetString(pref.c_str(), oldValue); + if (oldValue == aCurrentValue) { + return false; + } + Preferences::SetString(pref.c_str(), aCurrentValue); + return true; +} + +std::string DriverCrashGuard::GetFullPrefName(const char* aPref) { + return std::string("gfx.crash-guard.") + + std::string(sCrashGuardNames[uint32_t(mType)]) + std::string(".") + + std::string(aPref); +} + +DriverInitStatus DriverCrashGuard::GetStatus() const { + return (DriverInitStatus)Preferences::GetInt(mStatusPref.get(), 0); +} + +void DriverCrashGuard::SetStatus(DriverInitStatus aStatus) { + MOZ_ASSERT(XRE_IsParentProcess()); + + Preferences::SetInt(mStatusPref.get(), int32_t(aStatus)); +} + +void DriverCrashGuard::FlushPreferences() { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (nsIPrefService* prefService = Preferences::GetService()) { + static_cast<Preferences*>(prefService)->SavePrefFileBlocking(); + } +} + +void DriverCrashGuard::ForEachActiveCrashGuard( + const CrashGuardCallback& aCallback) { + for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) { + CrashGuardType type = static_cast<CrashGuardType>(i); + + if (!AreCrashGuardsEnabled(type)) { + // Even if guards look active (via prefs), they can be ignored if globally + // disabled. + continue; + } + + nsCString prefName; + BuildCrashGuardPrefName(type, prefName); + + auto status = + static_cast<DriverInitStatus>(Preferences::GetInt(prefName.get(), 0)); + if (status != DriverInitStatus::Crashed) { + continue; + } + + aCallback(sCrashGuardNames[i], prefName.get()); + } +} + +D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent) + : DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent) {} + +void D3D11LayersCrashGuard::Initialize() { + if (!XRE_IsParentProcess()) { + // We assume the parent process already performed crash detection for + // graphics devices. + return; + } + + DriverCrashGuard::Initialize(); + + // If no telemetry states have been recorded, this will set the state to okay. + // Otherwise, it will have no effect. + RecordTelemetry(TelemetryState::Okay); +} + +bool D3D11LayersCrashGuard::UpdateEnvironment() { + // Our result can be cached statically since we don't check live prefs. + static bool checked = false; + + if (checked) { + // We no longer need to bypass the crash guard. + return false; + } + + checked = true; + + bool changed = false; + // Feature status. +#if defined(XP_WIN) + bool d2dEnabled = StaticPrefs::gfx_direct2d_force_enabled_AtStartup() || + (!StaticPrefs::gfx_direct2d_disabled_AtStartup() && + FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D)); + changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled); + + bool d3d11Enabled = gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING); + changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled); + if (changed) { + RecordTelemetry(TelemetryState::EnvironmentChanged); + } +#endif + + return changed; +} + +void D3D11LayersCrashGuard::LogCrashRecovery() { + RecordTelemetry(TelemetryState::RecoveredFromCrash); + gfxCriticalNote << "D3D11 layers just crashed; D3D11 will be disabled."; +} + +void D3D11LayersCrashGuard::LogFeatureDisabled() { + RecordTelemetry(TelemetryState::FeatureDisabled); + gfxCriticalNote << "D3D11 layers disabled due to a prior crash."; +} + +void D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState) { + // D3D11LayersCrashGuard is a no-op in the child process. + if (!XRE_IsParentProcess()) { + return; + } + + // Since we instantiate this class more than once, make sure we only record + // the first state (since that is really all we care about). + static bool sTelemetryStateRecorded = false; + if (sTelemetryStateRecorded) { + return; + } + + Telemetry::Accumulate(Telemetry::GRAPHICS_DRIVER_STARTUP_TEST, + int32_t(aState)); + sTelemetryStateRecorded = true; +} + +GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent* aContentParent) + : DriverCrashGuard(CrashGuardType::GLContext, aContentParent) {} + +void GLContextCrashGuard::Initialize() { + if (XRE_IsContentProcess()) { + // Disable the GL crash guard in content processes, since we're not going + // to lose the entire browser and we don't want to hinder WebGL + // availability. + return; + } + +#if defined(MOZ_WIDGET_ANDROID) + // Disable the WebGL crash guard on Android - it doesn't use E10S, and + // its drivers will essentially never change, so the crash guard could + // permanently disable WebGL. + return; +#endif + + DriverCrashGuard::Initialize(); +} + +bool GLContextCrashGuard::UpdateEnvironment() { + static bool checked = false; + + if (checked) { + // We no longer need to bypass the crash guard. + return false; + } + + checked = true; + + bool changed = false; + +#if defined(XP_WIN) + changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11", + StaticPrefs::webgl_angle_force_d3d11()); + changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11", + StaticPrefs::webgl_angle_try_d3d11()); + changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp", + StaticPrefs::webgl_angle_force_warp()); + changed |= CheckAndUpdateBoolPref( + "gfx.driver-init.webgl-angle", + FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE, false)); + changed |= CheckAndUpdateBoolPref( + "gfx.driver-init.direct3d11-angle", + FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, false)); +#endif + + return changed; +} + +void GLContextCrashGuard::LogCrashRecovery() { + gfxCriticalNote << "GLContext just crashed."; +} + +void GLContextCrashGuard::LogFeatureDisabled() { + gfxCriticalNote << "GLContext remains enabled despite a previous crash."; +} + +WMFVPXVideoCrashGuard::WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent) + : DriverCrashGuard(CrashGuardType::WMFVPXVideo, aContentParent) {} + +void WMFVPXVideoCrashGuard::LogCrashRecovery() { + gfxCriticalNote + << "WMF VPX decoder just crashed; hardware video will be disabled."; +} + +void WMFVPXVideoCrashGuard::LogFeatureDisabled() { + gfxCriticalNote + << "WMF VPX video decoding is disabled due to a previous crash."; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/src/DriverCrashGuard.h b/gfx/src/DriverCrashGuard.h new file mode 100644 index 0000000000..1cb8d78d56 --- /dev/null +++ b/gfx/src/DriverCrashGuard.h @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef gfx_src_DriverCrashGuard_h__ +#define gfx_src_DriverCrashGuard_h__ + +#include "nsCOMPtr.h" +#include "nsIGfxInfo.h" +#include "nsIFile.h" +#include "nsString.h" +#include <functional> +#include <string> + +namespace mozilla { + +namespace dom { +class ContentParent; +} // namespace dom + +namespace gfx { + +enum class DriverInitStatus { + // Drivers have not been initialized yet. + Unknown, + + // We're attempting to initialize drivers. + Attempting, + + // Drivers were successfully initialized last run. + Okay, + + // We crashed during driver initialization, and have restarted. + Crashed +}; + +enum class CrashGuardType : uint32_t { + D3D11Layers, + GLContext, + WMFVPXVideo, + // Add new entries above this line, update the name array in + // DriverCrashGuard.cpp, make sure to add an entry in ContentParent.cpp, + // and add a fluent identifier in aboutSupport.ftl. + + NUM_TYPES +}; + +// DriverCrashGuard is used to detect crashes at graphics driver callsites. +// +// If the graphics environment is unrecognized or has changed since the last +// session, the crash guard will activate and will detect any crashes within +// the scope of the guard object. +// +// If a callsite has a previously encountered crash, and the environment has +// not changed since the last session, then the guard will set a status flag +// indicating that the driver should not be used. +class DriverCrashGuard { + public: + DriverCrashGuard(CrashGuardType aType, dom::ContentParent* aContentParent); + virtual ~DriverCrashGuard(); + + bool Crashed(); + void NotifyCrashed(); + + // These are the values reported to Telemetry (GRAPHICS_DRIVER_STARTUP_TEST). + // Values should not change; add new values to the end. + enum class TelemetryState { + Okay = 0, + EnvironmentChanged = 1, + RecoveredFromCrash = 2, + FeatureDisabled = 3 + }; + + enum class Mode { + // Normal operation. + Normal, + + // Acting as a proxy between the parent and child process. + Proxy + }; + + typedef std::function<void(const char* aName, const char* aPrefName)> + CrashGuardCallback; + static void ForEachActiveCrashGuard(const CrashGuardCallback& aCallback); + + protected: + virtual void Initialize(); + // UpdateEnvironment needs to return true should we need to attempt the + // operation once again. + // It should return true once only so that in case of a crash, we won't + // needlessly attempt the operation over and over again leading to continual + // crashes. several times + virtual bool UpdateEnvironment() { + // We don't care about any extra preferences here. + return false; + } + virtual void LogCrashRecovery() = 0; + virtual void LogFeatureDisabled() = 0; + + // Helper functions. + bool FeatureEnabled(int aFeature, bool aDefault = true); + bool CheckAndUpdatePref(const char* aPrefName, + const nsAString& aCurrentValue); + bool CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue); + std::string GetFullPrefName(const char* aPref); + + private: + // Either process. + void InitializeIfNeeded(); + bool CheckOrRefreshEnvironment(); + bool UpdateBaseEnvironment(); + DriverInitStatus GetStatus() const; + + // Parent process only. + nsCOMPtr<nsIFile> GetGuardFile(); + bool RecoverFromCrash(); + void ActivateGuard(); + void FlushPreferences(); + void SetStatus(DriverInitStatus aStatus); + + private: + CrashGuardType mType; + Mode mMode; + bool mInitialized; + bool mGuardActivated; + bool mCrashDetected; + nsCOMPtr<nsIFile> mGuardFile; + + protected: + nsCString mStatusPref; + nsCOMPtr<nsIGfxInfo> mGfxInfo; +}; + +class D3D11LayersCrashGuard final : public DriverCrashGuard { + public: + explicit D3D11LayersCrashGuard(dom::ContentParent* aContentParent = nullptr); + + protected: + void Initialize() override; + bool UpdateEnvironment() override; + void LogCrashRecovery() override; + void LogFeatureDisabled() override; + + private: + void RecordTelemetry(TelemetryState aState); +}; + +class GLContextCrashGuard final : public DriverCrashGuard { + public: + explicit GLContextCrashGuard(dom::ContentParent* aContentParent = nullptr); + void Initialize() override; + + protected: + bool UpdateEnvironment() override; + void LogCrashRecovery() override; + void LogFeatureDisabled() override; +}; + +class WMFVPXVideoCrashGuard final : public DriverCrashGuard { + public: + explicit WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent = nullptr); + + protected: + void LogCrashRecovery() override; + void LogFeatureDisabled() override; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // gfx_src_DriverCrashGuard_h__ diff --git a/gfx/src/FilterDescription.h b/gfx/src/FilterDescription.h new file mode 100644 index 0000000000..8c2ae12c98 --- /dev/null +++ b/gfx/src/FilterDescription.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __FilterDescription_h +#define __FilterDescription_h + +#include "FilterSupport.h" +#include "mozilla/Variant.h" +#include "mozilla/gfx/Rect.h" +#include "nsTArray.h" + +namespace mozilla::gfx { +class FilterPrimitiveDescription; +} + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::gfx::FilterPrimitiveDescription) + +namespace mozilla::gfx { +typedef Variant< + EmptyAttributes, BlendAttributes, MorphologyAttributes, + ColorMatrixAttributes, FloodAttributes, TileAttributes, + ComponentTransferAttributes, OpacityAttributes, ConvolveMatrixAttributes, + OffsetAttributes, DisplacementMapAttributes, TurbulenceAttributes, + CompositeAttributes, MergeAttributes, ImageAttributes, + GaussianBlurAttributes, DropShadowAttributes, DiffuseLightingAttributes, + SpecularLightingAttributes, ToAlphaAttributes> + PrimitiveAttributes; + +/** + * A data structure to carry attributes for a given primitive that's part of a + * filter. Will be serializable via IPDL, so it must not contain complex + * functionality. + * Used as part of a FilterDescription. + */ +class FilterPrimitiveDescription final { + public: + enum { + kPrimitiveIndexSourceGraphic = -1, + kPrimitiveIndexSourceAlpha = -2, + kPrimitiveIndexFillPaint = -3, + kPrimitiveIndexStrokePaint = -4 + }; + + FilterPrimitiveDescription(); + explicit FilterPrimitiveDescription(PrimitiveAttributes&& aAttributes); + FilterPrimitiveDescription(FilterPrimitiveDescription&& aOther) = default; + FilterPrimitiveDescription& operator=(FilterPrimitiveDescription&& aOther) = + default; + FilterPrimitiveDescription(const FilterPrimitiveDescription& aOther) + : mAttributes(aOther.mAttributes), + mInputPrimitives(aOther.mInputPrimitives.Clone()), + mFilterPrimitiveSubregion(aOther.mFilterPrimitiveSubregion), + mFilterSpaceBounds(aOther.mFilterSpaceBounds), + mInputColorSpaces(aOther.mInputColorSpaces.Clone()), + mOutputColorSpace(aOther.mOutputColorSpace), + mIsTainted(aOther.mIsTainted) {} + + const PrimitiveAttributes& Attributes() const { return mAttributes; } + PrimitiveAttributes& Attributes() { return mAttributes; } + + IntRect PrimitiveSubregion() const { return mFilterPrimitiveSubregion; } + IntRect FilterSpaceBounds() const { return mFilterSpaceBounds; } + bool IsTainted() const { return mIsTainted; } + + size_t NumberOfInputs() const { return mInputPrimitives.Length(); } + int32_t InputPrimitiveIndex(size_t aInputIndex) const { + return aInputIndex < mInputPrimitives.Length() + ? mInputPrimitives[aInputIndex] + : 0; + } + + ColorSpace InputColorSpace(size_t aInputIndex) const { + return aInputIndex < mInputColorSpaces.Length() + ? mInputColorSpaces[aInputIndex] + : ColorSpace(); + } + + ColorSpace OutputColorSpace() const { return mOutputColorSpace; } + + void SetPrimitiveSubregion(const IntRect& aRect) { + mFilterPrimitiveSubregion = aRect; + } + + void SetFilterSpaceBounds(const IntRect& aRect) { + mFilterSpaceBounds = aRect; + } + + void SetIsTainted(bool aIsTainted) { mIsTainted = aIsTainted; } + + void SetInputPrimitive(size_t aInputIndex, int32_t aInputPrimitiveIndex) { + mInputPrimitives.EnsureLengthAtLeast(aInputIndex + 1); + mInputPrimitives[aInputIndex] = aInputPrimitiveIndex; + } + + void SetInputColorSpace(size_t aInputIndex, ColorSpace aColorSpace) { + mInputColorSpaces.EnsureLengthAtLeast(aInputIndex + 1); + mInputColorSpaces[aInputIndex] = aColorSpace; + } + + void SetOutputColorSpace(const ColorSpace& aColorSpace) { + mOutputColorSpace = aColorSpace; + } + + bool operator==(const FilterPrimitiveDescription& aOther) const; + bool operator!=(const FilterPrimitiveDescription& aOther) const { + return !(*this == aOther); + } + + private: + PrimitiveAttributes mAttributes; + AutoTArray<int32_t, 2> mInputPrimitives; + IntRect mFilterPrimitiveSubregion; + IntRect mFilterSpaceBounds; + AutoTArray<ColorSpace, 2> mInputColorSpaces; + ColorSpace mOutputColorSpace; + bool mIsTainted; +}; + +/** + * A data structure that contains one or more FilterPrimitiveDescriptions. + * Designed to be serializable via IPDL, so it must not contain complex + * functionality. + */ +struct FilterDescription final { + FilterDescription() = default; + explicit FilterDescription(nsTArray<FilterPrimitiveDescription>&& aPrimitives) + : mPrimitives(std::move(aPrimitives)) {} + + bool operator==(const FilterDescription& aOther) const; + bool operator!=(const FilterDescription& aOther) const { + return !(*this == aOther); + } + + CopyableTArray<FilterPrimitiveDescription> mPrimitives; +}; +} // namespace mozilla::gfx + +#endif // __FilterSupport_h diff --git a/gfx/src/FilterSupport.cpp b/gfx/src/FilterSupport.cpp new file mode 100644 index 0000000000..d46de042f7 --- /dev/null +++ b/gfx/src/FilterSupport.cpp @@ -0,0 +1,1957 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilterSupport.h" +#include "FilterDescription.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Filters.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/PodOperations.h" + +#include "gfxContext.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "gfx2DGlue.h" + +#include "nsMargin.h" + +// c = n / 255 +// c <= 0.0031308f ? c * 12.92f : 1.055f * powf(c, 1 / 2.4f) - 0.055f +static const float glinearRGBTosRGBMap[256] = { + 0.000f, 0.050f, 0.085f, 0.111f, 0.132f, 0.150f, 0.166f, 0.181f, 0.194f, + 0.207f, 0.219f, 0.230f, 0.240f, 0.250f, 0.260f, 0.269f, 0.278f, 0.286f, + 0.295f, 0.303f, 0.310f, 0.318f, 0.325f, 0.332f, 0.339f, 0.346f, 0.352f, + 0.359f, 0.365f, 0.371f, 0.378f, 0.383f, 0.389f, 0.395f, 0.401f, 0.406f, + 0.412f, 0.417f, 0.422f, 0.427f, 0.433f, 0.438f, 0.443f, 0.448f, 0.452f, + 0.457f, 0.462f, 0.466f, 0.471f, 0.476f, 0.480f, 0.485f, 0.489f, 0.493f, + 0.498f, 0.502f, 0.506f, 0.510f, 0.514f, 0.518f, 0.522f, 0.526f, 0.530f, + 0.534f, 0.538f, 0.542f, 0.546f, 0.549f, 0.553f, 0.557f, 0.561f, 0.564f, + 0.568f, 0.571f, 0.575f, 0.579f, 0.582f, 0.586f, 0.589f, 0.592f, 0.596f, + 0.599f, 0.603f, 0.606f, 0.609f, 0.613f, 0.616f, 0.619f, 0.622f, 0.625f, + 0.629f, 0.632f, 0.635f, 0.638f, 0.641f, 0.644f, 0.647f, 0.650f, 0.653f, + 0.656f, 0.659f, 0.662f, 0.665f, 0.668f, 0.671f, 0.674f, 0.677f, 0.680f, + 0.683f, 0.685f, 0.688f, 0.691f, 0.694f, 0.697f, 0.699f, 0.702f, 0.705f, + 0.708f, 0.710f, 0.713f, 0.716f, 0.718f, 0.721f, 0.724f, 0.726f, 0.729f, + 0.731f, 0.734f, 0.737f, 0.739f, 0.742f, 0.744f, 0.747f, 0.749f, 0.752f, + 0.754f, 0.757f, 0.759f, 0.762f, 0.764f, 0.767f, 0.769f, 0.772f, 0.774f, + 0.776f, 0.779f, 0.781f, 0.784f, 0.786f, 0.788f, 0.791f, 0.793f, 0.795f, + 0.798f, 0.800f, 0.802f, 0.805f, 0.807f, 0.809f, 0.812f, 0.814f, 0.816f, + 0.818f, 0.821f, 0.823f, 0.825f, 0.827f, 0.829f, 0.832f, 0.834f, 0.836f, + 0.838f, 0.840f, 0.843f, 0.845f, 0.847f, 0.849f, 0.851f, 0.853f, 0.855f, + 0.857f, 0.860f, 0.862f, 0.864f, 0.866f, 0.868f, 0.870f, 0.872f, 0.874f, + 0.876f, 0.878f, 0.880f, 0.882f, 0.884f, 0.886f, 0.888f, 0.890f, 0.892f, + 0.894f, 0.896f, 0.898f, 0.900f, 0.902f, 0.904f, 0.906f, 0.908f, 0.910f, + 0.912f, 0.914f, 0.916f, 0.918f, 0.920f, 0.922f, 0.924f, 0.926f, 0.928f, + 0.930f, 0.931f, 0.933f, 0.935f, 0.937f, 0.939f, 0.941f, 0.943f, 0.945f, + 0.946f, 0.948f, 0.950f, 0.952f, 0.954f, 0.956f, 0.957f, 0.959f, 0.961f, + 0.963f, 0.965f, 0.967f, 0.968f, 0.970f, 0.972f, 0.974f, 0.975f, 0.977f, + 0.979f, 0.981f, 0.983f, 0.984f, 0.986f, 0.988f, 0.990f, 0.991f, 0.993f, + 0.995f, 0.997f, 0.998f, 1.000f}; + +// c = n / 255 +// c <= 0.04045f ? c / 12.92f : powf((c + 0.055f) / 1.055f, 2.4f) +extern const float gsRGBToLinearRGBMap[256] = { + 0.000f, 0.000f, 0.001f, 0.001f, 0.001f, 0.002f, 0.002f, 0.002f, 0.002f, + 0.003f, 0.003f, 0.003f, 0.004f, 0.004f, 0.004f, 0.005f, 0.005f, 0.006f, + 0.006f, 0.007f, 0.007f, 0.007f, 0.008f, 0.009f, 0.009f, 0.010f, 0.010f, + 0.011f, 0.012f, 0.012f, 0.013f, 0.014f, 0.014f, 0.015f, 0.016f, 0.017f, + 0.018f, 0.019f, 0.019f, 0.020f, 0.021f, 0.022f, 0.023f, 0.024f, 0.025f, + 0.026f, 0.027f, 0.028f, 0.030f, 0.031f, 0.032f, 0.033f, 0.034f, 0.036f, + 0.037f, 0.038f, 0.040f, 0.041f, 0.042f, 0.044f, 0.045f, 0.047f, 0.048f, + 0.050f, 0.051f, 0.053f, 0.054f, 0.056f, 0.058f, 0.060f, 0.061f, 0.063f, + 0.065f, 0.067f, 0.068f, 0.070f, 0.072f, 0.074f, 0.076f, 0.078f, 0.080f, + 0.082f, 0.084f, 0.087f, 0.089f, 0.091f, 0.093f, 0.095f, 0.098f, 0.100f, + 0.102f, 0.105f, 0.107f, 0.109f, 0.112f, 0.114f, 0.117f, 0.120f, 0.122f, + 0.125f, 0.127f, 0.130f, 0.133f, 0.136f, 0.138f, 0.141f, 0.144f, 0.147f, + 0.150f, 0.153f, 0.156f, 0.159f, 0.162f, 0.165f, 0.168f, 0.171f, 0.175f, + 0.178f, 0.181f, 0.184f, 0.188f, 0.191f, 0.195f, 0.198f, 0.202f, 0.205f, + 0.209f, 0.212f, 0.216f, 0.220f, 0.223f, 0.227f, 0.231f, 0.235f, 0.238f, + 0.242f, 0.246f, 0.250f, 0.254f, 0.258f, 0.262f, 0.266f, 0.270f, 0.275f, + 0.279f, 0.283f, 0.287f, 0.292f, 0.296f, 0.301f, 0.305f, 0.309f, 0.314f, + 0.319f, 0.323f, 0.328f, 0.332f, 0.337f, 0.342f, 0.347f, 0.352f, 0.356f, + 0.361f, 0.366f, 0.371f, 0.376f, 0.381f, 0.386f, 0.392f, 0.397f, 0.402f, + 0.407f, 0.413f, 0.418f, 0.423f, 0.429f, 0.434f, 0.440f, 0.445f, 0.451f, + 0.456f, 0.462f, 0.468f, 0.474f, 0.479f, 0.485f, 0.491f, 0.497f, 0.503f, + 0.509f, 0.515f, 0.521f, 0.527f, 0.533f, 0.539f, 0.546f, 0.552f, 0.558f, + 0.565f, 0.571f, 0.578f, 0.584f, 0.591f, 0.597f, 0.604f, 0.610f, 0.617f, + 0.624f, 0.631f, 0.638f, 0.644f, 0.651f, 0.658f, 0.665f, 0.672f, 0.680f, + 0.687f, 0.694f, 0.701f, 0.708f, 0.716f, 0.723f, 0.730f, 0.738f, 0.745f, + 0.753f, 0.761f, 0.768f, 0.776f, 0.784f, 0.791f, 0.799f, 0.807f, 0.815f, + 0.823f, 0.831f, 0.839f, 0.847f, 0.855f, 0.863f, 0.871f, 0.880f, 0.888f, + 0.896f, 0.905f, 0.913f, 0.922f, 0.930f, 0.939f, 0.947f, 0.956f, 0.965f, + 0.973f, 0.982f, 0.991f, 1.000f}; + +namespace mozilla { +namespace gfx { + +// Some convenience FilterNode creation functions. + +namespace FilterWrappers { + +static already_AddRefed<FilterNode> Unpremultiply(DrawTarget* aDT, + FilterNode* aInput) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::UNPREMULTIPLY); + if (filter) { + filter->SetInput(IN_UNPREMULTIPLY_IN, aInput); + return filter.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> Premultiply(DrawTarget* aDT, + FilterNode* aInput) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::PREMULTIPLY); + if (filter) { + filter->SetInput(IN_PREMULTIPLY_IN, aInput); + return filter.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> LinearRGBToSRGB(DrawTarget* aDT, + FilterNode* aInput) { + RefPtr<FilterNode> transfer = + aDT->CreateFilter(FilterType::DISCRETE_TRANSFER); + if (transfer) { + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, glinearRGBTosRGBMap, + 256); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, glinearRGBTosRGBMap, + 256); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, glinearRGBTosRGBMap, + 256); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true); + transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput); + return transfer.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> SRGBToLinearRGB(DrawTarget* aDT, + FilterNode* aInput) { + RefPtr<FilterNode> transfer = + aDT->CreateFilter(FilterType::DISCRETE_TRANSFER); + if (transfer) { + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, gsRGBToLinearRGBMap, + 256); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, gsRGBToLinearRGBMap, + 256); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, gsRGBToLinearRGBMap, + 256); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true); + transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput); + return transfer.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> Crop(DrawTarget* aDT, + FilterNode* aInputFilter, + const IntRect& aRect) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::CROP); + if (filter) { + filter->SetAttribute(ATT_CROP_RECT, Rect(aRect)); + filter->SetInput(IN_CROP_IN, aInputFilter); + return filter.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> Offset(DrawTarget* aDT, + FilterNode* aInputFilter, + const IntPoint& aOffset) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TRANSFORM); + if (filter) { + filter->SetAttribute(ATT_TRANSFORM_MATRIX, + Matrix::Translation(aOffset.x, aOffset.y)); + filter->SetInput(IN_TRANSFORM_IN, aInputFilter); + return filter.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> GaussianBlur(DrawTarget* aDT, + FilterNode* aInputFilter, + const Size& aStdDeviation) { + float stdX = float(std::min(aStdDeviation.width, kMaxStdDeviation)); + float stdY = float(std::min(aStdDeviation.height, kMaxStdDeviation)); + if (stdX == stdY) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::GAUSSIAN_BLUR); + if (filter) { + filter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdX); + filter->SetInput(IN_GAUSSIAN_BLUR_IN, aInputFilter); + return filter.forget(); + } + return nullptr; + } + RefPtr<FilterNode> filterH = aDT->CreateFilter(FilterType::DIRECTIONAL_BLUR); + RefPtr<FilterNode> filterV = aDT->CreateFilter(FilterType::DIRECTIONAL_BLUR); + if (filterH && filterV) { + filterH->SetAttribute(ATT_DIRECTIONAL_BLUR_DIRECTION, + (uint32_t)BLUR_DIRECTION_X); + filterH->SetAttribute(ATT_DIRECTIONAL_BLUR_STD_DEVIATION, stdX); + filterV->SetAttribute(ATT_DIRECTIONAL_BLUR_DIRECTION, + (uint32_t)BLUR_DIRECTION_Y); + filterV->SetAttribute(ATT_DIRECTIONAL_BLUR_STD_DEVIATION, stdY); + filterH->SetInput(IN_DIRECTIONAL_BLUR_IN, aInputFilter); + filterV->SetInput(IN_DIRECTIONAL_BLUR_IN, filterH); + return filterV.forget(); + } + return nullptr; +} + +already_AddRefed<FilterNode> Clear(DrawTarget* aDT) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::FLOOD); + if (filter) { + filter->SetAttribute(ATT_FLOOD_COLOR, DeviceColor()); + return filter.forget(); + } + return nullptr; +} + +already_AddRefed<FilterNode> ForSurface(DrawTarget* aDT, + SourceSurface* aSurface, + const IntPoint& aSurfacePosition) { + RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TRANSFORM); + if (filter) { + filter->SetAttribute( + ATT_TRANSFORM_MATRIX, + Matrix::Translation(aSurfacePosition.x, aSurfacePosition.y)); + filter->SetInput(IN_TRANSFORM_IN, aSurface); + return filter.forget(); + } + return nullptr; +} + +static already_AddRefed<FilterNode> ToAlpha(DrawTarget* aDT, + FilterNode* aInput) { + float zero = 0.0f; + RefPtr<FilterNode> transfer = + aDT->CreateFilter(FilterType::DISCRETE_TRANSFER); + if (transfer) { + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, &zero, 1); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, &zero, 1); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, &zero, 1); + transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true); + transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput); + return transfer.forget(); + } + return nullptr; +} + +} // namespace FilterWrappers + +// A class that wraps a FilterNode and handles conversion between different +// color models. Create FilterCachedColorModels with your original filter and +// the color model that this filter outputs in natively, and then call +// ->ForColorModel(colorModel) in order to get a FilterNode which outputs to +// the specified colorModel. +// Internally, this is achieved by wrapping the original FilterNode with +// conversion FilterNodes. These filter nodes are cached in such a way that no +// repeated or back-and-forth conversions happen. +class FilterCachedColorModels { + public: + NS_INLINE_DECL_REFCOUNTING(FilterCachedColorModels) + // aFilter can be null. In that case, ForColorModel will return a non-null + // completely transparent filter for all color models. + FilterCachedColorModels(DrawTarget* aDT, FilterNode* aFilter, + ColorModel aOriginalColorModel); + + // Get a FilterNode for the specified color model, guaranteed to be non-null. + already_AddRefed<FilterNode> ForColorModel(ColorModel aColorModel); + + AlphaModel OriginalAlphaModel() const { + return mOriginalColorModel.mAlphaModel; + } + + private: + // Create the required FilterNode that will be cached by ForColorModel. + already_AddRefed<FilterNode> WrapForColorModel(ColorModel aColorModel); + + RefPtr<DrawTarget> mDT; + ColorModel mOriginalColorModel; + + // This array is indexed by ColorModel::ToIndex. + RefPtr<FilterNode> mFilterForColorModel[4]; + + ~FilterCachedColorModels() = default; +}; + +FilterCachedColorModels::FilterCachedColorModels(DrawTarget* aDT, + FilterNode* aFilter, + ColorModel aOriginalColorModel) + : mDT(aDT), mOriginalColorModel(aOriginalColorModel) { + if (aFilter) { + mFilterForColorModel[aOriginalColorModel.ToIndex()] = aFilter; + } else { + RefPtr<FilterNode> clear = FilterWrappers::Clear(aDT); + mFilterForColorModel[0] = clear; + mFilterForColorModel[1] = clear; + mFilterForColorModel[2] = clear; + mFilterForColorModel[3] = clear; + } +} + +already_AddRefed<FilterNode> FilterCachedColorModels::ForColorModel( + ColorModel aColorModel) { + if (aColorModel == mOriginalColorModel) { + // Make sure to not call WrapForColorModel if our original filter node was + // null, because then we'd get an infinite recursion. + RefPtr<FilterNode> filter = + mFilterForColorModel[mOriginalColorModel.ToIndex()]; + return filter.forget(); + } + + if (!mFilterForColorModel[aColorModel.ToIndex()]) { + mFilterForColorModel[aColorModel.ToIndex()] = + WrapForColorModel(aColorModel); + } + RefPtr<FilterNode> filter(mFilterForColorModel[aColorModel.ToIndex()]); + return filter.forget(); +} + +already_AddRefed<FilterNode> FilterCachedColorModels::WrapForColorModel( + ColorModel aColorModel) { + // Convert one aspect at a time and recurse. + // Conversions between premultiplied / unpremultiplied color channels for the + // same color space can happen directly. + // Conversions between different color spaces can only happen on + // unpremultiplied color channels. + + if (aColorModel.mAlphaModel == AlphaModel::Premultiplied) { + RefPtr<FilterNode> unpre = ForColorModel( + ColorModel(aColorModel.mColorSpace, AlphaModel::Unpremultiplied)); + return FilterWrappers::Premultiply(mDT, unpre); + } + + MOZ_ASSERT(aColorModel.mAlphaModel == AlphaModel::Unpremultiplied); + if (aColorModel.mColorSpace == mOriginalColorModel.mColorSpace) { + RefPtr<FilterNode> premultiplied = ForColorModel( + ColorModel(aColorModel.mColorSpace, AlphaModel::Premultiplied)); + return FilterWrappers::Unpremultiply(mDT, premultiplied); + } + + RefPtr<FilterNode> unpremultipliedOriginal = ForColorModel( + ColorModel(mOriginalColorModel.mColorSpace, AlphaModel::Unpremultiplied)); + if (aColorModel.mColorSpace == ColorSpace::LinearRGB) { + return FilterWrappers::SRGBToLinearRGB(mDT, unpremultipliedOriginal); + } + return FilterWrappers::LinearRGBToSRGB(mDT, unpremultipliedOriginal); +} + +static const float identityMatrix[] = {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 1, 0}; + +// When aAmount == 0, the identity matrix is returned. +// When aAmount == 1, aToMatrix is returned. +// When aAmount > 1, an exaggerated version of aToMatrix is returned. This can +// be useful in certain cases, such as producing a color matrix to oversaturate +// an image. +// +// This function is a shortcut of a full matrix addition and a scalar multiply, +// and it assumes that the following elements in aToMatrix are 0 and 1: +// x x x 0 0 +// x x x 0 0 +// x x x 0 0 +// 0 0 0 1 0 +static void InterpolateFromIdentityMatrix(const float aToMatrix[20], + float aAmount, float aOutMatrix[20]) { + PodCopy(aOutMatrix, identityMatrix, 20); + + float oneMinusAmount = 1 - aAmount; + + aOutMatrix[0] = aAmount * aToMatrix[0] + oneMinusAmount; + aOutMatrix[1] = aAmount * aToMatrix[1]; + aOutMatrix[2] = aAmount * aToMatrix[2]; + + aOutMatrix[5] = aAmount * aToMatrix[5]; + aOutMatrix[6] = aAmount * aToMatrix[6] + oneMinusAmount; + aOutMatrix[7] = aAmount * aToMatrix[7]; + + aOutMatrix[10] = aAmount * aToMatrix[10]; + aOutMatrix[11] = aAmount * aToMatrix[11]; + aOutMatrix[12] = aAmount * aToMatrix[12] + oneMinusAmount; +} + +// Create a 4x5 color matrix for the different ways to specify color matrices +// in SVG. +bool ComputeColorMatrix(const ColorMatrixAttributes& aMatrixAttributes, + float aOutMatrix[20]) { + // Luminance coefficients. + static const float lumR = 0.2126f; + static const float lumG = 0.7152f; + static const float lumB = 0.0722f; + + static const float oneMinusLumR = 1 - lumR; + static const float oneMinusLumG = 1 - lumG; + static const float oneMinusLumB = 1 - lumB; + + static const float luminanceToAlphaMatrix[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, lumR, lumG, lumB, 0, 0}; + + static const float saturateMatrix[] = { + lumR, lumG, lumB, 0, 0, lumR, lumG, lumB, 0, 0, + lumR, lumG, lumB, 0, 0, 0, 0, 0, 1, 0}; + + static const float sepiaMatrix[] = { + 0.393f, 0.769f, 0.189f, 0, 0, 0.349f, 0.686f, 0.168f, 0, 0, + 0.272f, 0.534f, 0.131f, 0, 0, 0, 0, 0, 1, 0}; + + // Hue rotate specific coefficients. + static const float hueRotateR = 0.143f; + static const float hueRotateG = 0.140f; + static const float hueRotateB = 0.283f; + + switch (aMatrixAttributes.mType) { + case SVG_FECOLORMATRIX_TYPE_MATRIX: { + if (aMatrixAttributes.mValues.Length() != 20) { + return false; + } + + PodCopy(aOutMatrix, aMatrixAttributes.mValues.Elements(), 20); + break; + } + + case SVG_FECOLORMATRIX_TYPE_SATURATE: { + if (aMatrixAttributes.mValues.Length() != 1) { + return false; + } + + float s = aMatrixAttributes.mValues[0]; + + if (s < 0) { + return false; + } + + InterpolateFromIdentityMatrix(saturateMatrix, 1 - s, aOutMatrix); + break; + } + + case SVG_FECOLORMATRIX_TYPE_HUE_ROTATE: { + if (aMatrixAttributes.mValues.Length() != 1) { + return false; + } + + PodCopy(aOutMatrix, identityMatrix, 20); + + float hueRotateValue = aMatrixAttributes.mValues[0]; + + float c = static_cast<float>(cos(hueRotateValue * M_PI / 180)); + float s = static_cast<float>(sin(hueRotateValue * M_PI / 180)); + + aOutMatrix[0] = lumR + oneMinusLumR * c - lumR * s; + aOutMatrix[1] = lumG - lumG * c - lumG * s; + aOutMatrix[2] = lumB - lumB * c + oneMinusLumB * s; + + aOutMatrix[5] = lumR - lumR * c + hueRotateR * s; + aOutMatrix[6] = lumG + oneMinusLumG * c + hueRotateG * s; + aOutMatrix[7] = lumB - lumB * c - hueRotateB * s; + + aOutMatrix[10] = lumR - lumR * c - oneMinusLumR * s; + aOutMatrix[11] = lumG - lumG * c + lumG * s; + aOutMatrix[12] = lumB + oneMinusLumB * c + lumB * s; + + break; + } + + case SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA: { + PodCopy(aOutMatrix, luminanceToAlphaMatrix, 20); + break; + } + + case SVG_FECOLORMATRIX_TYPE_SEPIA: { + if (aMatrixAttributes.mValues.Length() != 1) { + return false; + } + + float amount = aMatrixAttributes.mValues[0]; + + if (amount < 0 || amount > 1) { + return false; + } + + InterpolateFromIdentityMatrix(sepiaMatrix, amount, aOutMatrix); + break; + } + + default: { + return false; + } + } + + return !ArrayEqual(aOutMatrix, identityMatrix, 20); +} + +static void DisableAllTransfers(FilterNode* aTransferFilterNode) { + aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_R, true); + aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_G, true); + aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_B, true); + aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_A, true); +} + +// Called for one channel at a time. +// This function creates the required FilterNodes on demand and tries to +// merge conversions of different channels into the same FilterNode if +// possible. +// There's a mismatch between the way SVG and the Moz2D API handle transfer +// functions: In SVG, it's possible to specify a different transfer function +// type for each color channel, but in Moz2D, a given transfer function type +// applies to all color channels. +// +// @param aFunctionAttributes The attributes of the transfer function for this +// channel. +// @param aChannel The color channel that this function applies to, where +// 0 = red, 1 = green, 2 = blue, 3 = alpha +// @param aDT The DrawTarget that the FilterNodes should be created for. +// @param aTableTransfer Existing FilterNode holders (which may still be +// null) that the resulting FilterNodes from this +// function will be stored in. +// +static void ConvertComponentTransferFunctionToFilter( + const ComponentTransferAttributes& aFunctionAttributes, int32_t aInChannel, + int32_t aOutChannel, DrawTarget* aDT, RefPtr<FilterNode>& aTableTransfer, + RefPtr<FilterNode>& aDiscreteTransfer, RefPtr<FilterNode>& aLinearTransfer, + RefPtr<FilterNode>& aGammaTransfer) { + static const TransferAtts disableAtt[4] = { + ATT_TRANSFER_DISABLE_R, ATT_TRANSFER_DISABLE_G, ATT_TRANSFER_DISABLE_B, + ATT_TRANSFER_DISABLE_A}; + + RefPtr<FilterNode> filter; + + uint32_t type = aFunctionAttributes.mTypes[aInChannel]; + + switch (type) { + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: { + const nsTArray<float>& tableValues = + aFunctionAttributes.mValues[aInChannel]; + if (tableValues.Length() < 2) return; + + if (!aTableTransfer) { + aTableTransfer = aDT->CreateFilter(FilterType::TABLE_TRANSFER); + if (!aTableTransfer) { + return; + } + DisableAllTransfers(aTableTransfer); + } + filter = aTableTransfer; + static const TableTransferAtts tableAtt[4] = { + ATT_TABLE_TRANSFER_TABLE_R, ATT_TABLE_TRANSFER_TABLE_G, + ATT_TABLE_TRANSFER_TABLE_B, ATT_TABLE_TRANSFER_TABLE_A}; + filter->SetAttribute(disableAtt[aOutChannel], false); + filter->SetAttribute(tableAtt[aOutChannel], &tableValues[0], + tableValues.Length()); + break; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: { + const nsTArray<float>& tableValues = + aFunctionAttributes.mValues[aInChannel]; + if (tableValues.Length() < 1) return; + + if (!aDiscreteTransfer) { + aDiscreteTransfer = aDT->CreateFilter(FilterType::DISCRETE_TRANSFER); + if (!aDiscreteTransfer) { + return; + } + DisableAllTransfers(aDiscreteTransfer); + } + filter = aDiscreteTransfer; + static const DiscreteTransferAtts tableAtt[4] = { + ATT_DISCRETE_TRANSFER_TABLE_R, ATT_DISCRETE_TRANSFER_TABLE_G, + ATT_DISCRETE_TRANSFER_TABLE_B, ATT_DISCRETE_TRANSFER_TABLE_A}; + filter->SetAttribute(disableAtt[aOutChannel], false); + filter->SetAttribute(tableAtt[aOutChannel], &tableValues[0], + tableValues.Length()); + + break; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: { + static const LinearTransferAtts slopeAtt[4] = { + ATT_LINEAR_TRANSFER_SLOPE_R, ATT_LINEAR_TRANSFER_SLOPE_G, + ATT_LINEAR_TRANSFER_SLOPE_B, ATT_LINEAR_TRANSFER_SLOPE_A}; + static const LinearTransferAtts interceptAtt[4] = { + ATT_LINEAR_TRANSFER_INTERCEPT_R, ATT_LINEAR_TRANSFER_INTERCEPT_G, + ATT_LINEAR_TRANSFER_INTERCEPT_B, ATT_LINEAR_TRANSFER_INTERCEPT_A}; + if (!aLinearTransfer) { + aLinearTransfer = aDT->CreateFilter(FilterType::LINEAR_TRANSFER); + if (!aLinearTransfer) { + return; + } + DisableAllTransfers(aLinearTransfer); + } + filter = aLinearTransfer; + filter->SetAttribute(disableAtt[aOutChannel], false); + const nsTArray<float>& slopeIntercept = + aFunctionAttributes.mValues[aInChannel]; + float slope = slopeIntercept[kComponentTransferSlopeIndex]; + float intercept = slopeIntercept[kComponentTransferInterceptIndex]; + filter->SetAttribute(slopeAtt[aOutChannel], slope); + filter->SetAttribute(interceptAtt[aOutChannel], intercept); + break; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: { + static const GammaTransferAtts amplitudeAtt[4] = { + ATT_GAMMA_TRANSFER_AMPLITUDE_R, ATT_GAMMA_TRANSFER_AMPLITUDE_G, + ATT_GAMMA_TRANSFER_AMPLITUDE_B, ATT_GAMMA_TRANSFER_AMPLITUDE_A}; + static const GammaTransferAtts exponentAtt[4] = { + ATT_GAMMA_TRANSFER_EXPONENT_R, ATT_GAMMA_TRANSFER_EXPONENT_G, + ATT_GAMMA_TRANSFER_EXPONENT_B, ATT_GAMMA_TRANSFER_EXPONENT_A}; + static const GammaTransferAtts offsetAtt[4] = { + ATT_GAMMA_TRANSFER_OFFSET_R, ATT_GAMMA_TRANSFER_OFFSET_G, + ATT_GAMMA_TRANSFER_OFFSET_B, ATT_GAMMA_TRANSFER_OFFSET_A}; + if (!aGammaTransfer) { + aGammaTransfer = aDT->CreateFilter(FilterType::GAMMA_TRANSFER); + if (!aGammaTransfer) { + return; + } + DisableAllTransfers(aGammaTransfer); + } + filter = aGammaTransfer; + filter->SetAttribute(disableAtt[aOutChannel], false); + const nsTArray<float>& gammaValues = + aFunctionAttributes.mValues[aInChannel]; + float amplitude = gammaValues[kComponentTransferAmplitudeIndex]; + float exponent = gammaValues[kComponentTransferExponentIndex]; + float offset = gammaValues[kComponentTransferOffsetIndex]; + filter->SetAttribute(amplitudeAtt[aOutChannel], amplitude); + filter->SetAttribute(exponentAtt[aOutChannel], exponent); + filter->SetAttribute(offsetAtt[aOutChannel], offset); + break; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY: + default: + break; + } +} + +const int32_t kMorphologyMaxRadius = 100000; + +// Handle the different primitive description types and create the necessary +// FilterNode(s) for each. +// Returns nullptr for invalid filter primitives. This should be interpreted as +// transparent black by the caller. +// aSourceRegions contains the filter primitive subregions of the source +// primitives; only needed for eTile primitives. +// aInputImages carries additional surfaces that are used by eImage primitives. +static already_AddRefed<FilterNode> FilterNodeFromPrimitiveDescription( + const FilterPrimitiveDescription& aDescription, DrawTarget* aDT, + nsTArray<RefPtr<FilterNode>>& aSources, nsTArray<IntRect>& aSourceRegions, + nsTArray<RefPtr<SourceSurface>>& aInputImages) { + struct PrimitiveAttributesMatcher { + PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription, + DrawTarget* aDT, + nsTArray<RefPtr<FilterNode>>& aSources, + nsTArray<IntRect>& aSourceRegions, + nsTArray<RefPtr<SourceSurface>>& aInputImages) + : mDescription(aDescription), + mDT(aDT), + mSources(aSources), + mSourceRegions(aSourceRegions), + mInputImages(aInputImages) {} + + const FilterPrimitiveDescription& mDescription; + DrawTarget* mDT; + nsTArray<RefPtr<FilterNode>>& mSources; + nsTArray<IntRect>& mSourceRegions; + nsTArray<RefPtr<SourceSurface>>& mInputImages; + + already_AddRefed<FilterNode> operator()( + const EmptyAttributes& aEmptyAttributes) { + return nullptr; + } + + already_AddRefed<FilterNode> operator()(const BlendAttributes& aBlend) { + uint32_t mode = aBlend.mBlendMode; + RefPtr<FilterNode> filter; + if (mode == SVG_FEBLEND_MODE_UNKNOWN) { + return nullptr; + } + if (mode == SVG_FEBLEND_MODE_NORMAL) { + filter = mDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + filter->SetInput(IN_COMPOSITE_IN_START, mSources[1]); + filter->SetInput(IN_COMPOSITE_IN_START + 1, mSources[0]); + } else { + filter = mDT->CreateFilter(FilterType::BLEND); + if (!filter) { + return nullptr; + } + static const uint8_t blendModes[SVG_FEBLEND_MODE_LUMINOSITY + 1] = { + 0, + 0, + BLEND_MODE_MULTIPLY, + BLEND_MODE_SCREEN, + BLEND_MODE_DARKEN, + BLEND_MODE_LIGHTEN, + BLEND_MODE_OVERLAY, + BLEND_MODE_COLOR_DODGE, + BLEND_MODE_COLOR_BURN, + BLEND_MODE_HARD_LIGHT, + BLEND_MODE_SOFT_LIGHT, + BLEND_MODE_DIFFERENCE, + BLEND_MODE_EXCLUSION, + BLEND_MODE_HUE, + BLEND_MODE_SATURATION, + BLEND_MODE_COLOR, + BLEND_MODE_LUMINOSITY}; + filter->SetAttribute(ATT_BLEND_BLENDMODE, (uint32_t)blendModes[mode]); + // The correct input order for both software and D2D filters is flipped + // from our source order, so flip here. + filter->SetInput(IN_BLEND_IN, mSources[1]); + filter->SetInput(IN_BLEND_IN2, mSources[0]); + } + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const ColorMatrixAttributes& aMatrixAttributes) { + float colorMatrix[20]; + if (!ComputeColorMatrix(aMatrixAttributes, colorMatrix)) { + RefPtr<FilterNode> filter(mSources[0]); + return filter.forget(); + } + + Matrix5x4 matrix( + colorMatrix[0], colorMatrix[5], colorMatrix[10], colorMatrix[15], + colorMatrix[1], colorMatrix[6], colorMatrix[11], colorMatrix[16], + colorMatrix[2], colorMatrix[7], colorMatrix[12], colorMatrix[17], + colorMatrix[3], colorMatrix[8], colorMatrix[13], colorMatrix[18], + colorMatrix[4], colorMatrix[9], colorMatrix[14], colorMatrix[19]); + + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::COLOR_MATRIX); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_COLOR_MATRIX_MATRIX, matrix); + filter->SetAttribute(ATT_COLOR_MATRIX_ALPHA_MODE, + (uint32_t)ALPHA_MODE_STRAIGHT); + filter->SetInput(IN_COLOR_MATRIX_IN, mSources[0]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const MorphologyAttributes& aMorphology) { + Size radii = aMorphology.mRadii; + int32_t rx = radii.width; + int32_t ry = radii.height; + + // Is one of the radii zero or negative, return the input image + if (rx <= 0 || ry <= 0) { + RefPtr<FilterNode> filter(mSources[0]); + return filter.forget(); + } + + // Clamp radii to prevent completely insane values: + rx = std::min(rx, kMorphologyMaxRadius); + ry = std::min(ry, kMorphologyMaxRadius); + + MorphologyOperator op = aMorphology.mOperator == SVG_OPERATOR_ERODE + ? MORPHOLOGY_OPERATOR_ERODE + : MORPHOLOGY_OPERATOR_DILATE; + + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::MORPHOLOGY); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_MORPHOLOGY_RADII, IntSize(rx, ry)); + filter->SetAttribute(ATT_MORPHOLOGY_OPERATOR, (uint32_t)op); + filter->SetInput(IN_MORPHOLOGY_IN, mSources[0]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()(const FloodAttributes& aFlood) { + DeviceColor color = ToDeviceColor(aFlood.mColor); + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::FLOOD); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_FLOOD_COLOR, color); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()(const TileAttributes& aTile) { + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::TILE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_TILE_SOURCE_RECT, mSourceRegions[0]); + filter->SetInput(IN_TILE_IN, mSources[0]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const ComponentTransferAttributes& aComponentTransfer) { + MOZ_ASSERT(aComponentTransfer.mTypes[0] != + SVG_FECOMPONENTTRANSFER_SAME_AS_R); + MOZ_ASSERT(aComponentTransfer.mTypes[3] != + SVG_FECOMPONENTTRANSFER_SAME_AS_R); + + RefPtr<FilterNode> filters[4]; // one for each FILTER_*_TRANSFER type + for (int32_t i = 0; i < 4; i++) { + int32_t inputIndex = (aComponentTransfer.mTypes[i] == + SVG_FECOMPONENTTRANSFER_SAME_AS_R) && + (i < 3) + ? 0 + : i; + ConvertComponentTransferFunctionToFilter(aComponentTransfer, inputIndex, + i, mDT, filters[0], filters[1], + filters[2], filters[3]); + } + + // Connect all used filters nodes. + RefPtr<FilterNode> lastFilter = mSources[0]; + for (int32_t i = 0; i < 4; i++) { + if (filters[i]) { + filters[i]->SetInput(0, lastFilter); + lastFilter = filters[i]; + } + } + + return lastFilter.forget(); + } + + already_AddRefed<FilterNode> operator()(const OpacityAttributes& aOpacity) { + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::OPACITY); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_OPACITY_VALUE, aOpacity.mOpacity); + filter->SetInput(IN_OPACITY_IN, mSources[0]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const ConvolveMatrixAttributes& aConvolveMatrix) { + RefPtr<FilterNode> filter = + mDT->CreateFilter(FilterType::CONVOLVE_MATRIX); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_SIZE, + aConvolveMatrix.mKernelSize); + const nsTArray<float>& matrix = aConvolveMatrix.mKernelMatrix; + filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_MATRIX, matrix.Elements(), + matrix.Length()); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_DIVISOR, + aConvolveMatrix.mDivisor); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_BIAS, aConvolveMatrix.mBias); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_TARGET, aConvolveMatrix.mTarget); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_SOURCE_RECT, mSourceRegions[0]); + uint32_t edgeMode = aConvolveMatrix.mEdgeMode; + static const uint8_t edgeModes[SVG_EDGEMODE_NONE + 1] = { + EDGE_MODE_NONE, // SVG_EDGEMODE_UNKNOWN + EDGE_MODE_DUPLICATE, // SVG_EDGEMODE_DUPLICATE + EDGE_MODE_WRAP, // SVG_EDGEMODE_WRAP + EDGE_MODE_NONE // SVG_EDGEMODE_NONE + }; + filter->SetAttribute(ATT_CONVOLVE_MATRIX_EDGE_MODE, + (uint32_t)edgeModes[edgeMode]); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH, + aConvolveMatrix.mKernelUnitLength); + filter->SetAttribute(ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA, + aConvolveMatrix.mPreserveAlpha); + filter->SetInput(IN_CONVOLVE_MATRIX_IN, mSources[0]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()(const OffsetAttributes& aOffset) { + return FilterWrappers::Offset(mDT, mSources[0], aOffset.mValue); + } + + already_AddRefed<FilterNode> operator()( + const DisplacementMapAttributes& aDisplacementMap) { + RefPtr<FilterNode> filter = + mDT->CreateFilter(FilterType::DISPLACEMENT_MAP); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_DISPLACEMENT_MAP_SCALE, aDisplacementMap.mScale); + static const uint8_t channel[SVG_CHANNEL_A + 1] = { + COLOR_CHANNEL_R, // SVG_CHANNEL_UNKNOWN + COLOR_CHANNEL_R, // SVG_CHANNEL_R + COLOR_CHANNEL_G, // SVG_CHANNEL_G + COLOR_CHANNEL_B, // SVG_CHANNEL_B + COLOR_CHANNEL_A // SVG_CHANNEL_A + }; + filter->SetAttribute(ATT_DISPLACEMENT_MAP_X_CHANNEL, + (uint32_t)channel[aDisplacementMap.mXChannel]); + filter->SetAttribute(ATT_DISPLACEMENT_MAP_Y_CHANNEL, + (uint32_t)channel[aDisplacementMap.mYChannel]); + filter->SetInput(IN_DISPLACEMENT_MAP_IN, mSources[0]); + filter->SetInput(IN_DISPLACEMENT_MAP_IN2, mSources[1]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const TurbulenceAttributes& aTurbulence) { + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::TURBULENCE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_TURBULENCE_BASE_FREQUENCY, + aTurbulence.mBaseFrequency); + filter->SetAttribute(ATT_TURBULENCE_NUM_OCTAVES, aTurbulence.mOctaves); + filter->SetAttribute(ATT_TURBULENCE_STITCHABLE, aTurbulence.mStitchable); + filter->SetAttribute(ATT_TURBULENCE_SEED, (uint32_t)aTurbulence.mSeed); + static const uint8_t type[SVG_TURBULENCE_TYPE_TURBULENCE + 1] = { + TURBULENCE_TYPE_FRACTAL_NOISE, // SVG_TURBULENCE_TYPE_UNKNOWN + TURBULENCE_TYPE_FRACTAL_NOISE, // SVG_TURBULENCE_TYPE_FRACTALNOISE + TURBULENCE_TYPE_TURBULENCE // SVG_TURBULENCE_TYPE_TURBULENCE + }; + filter->SetAttribute(ATT_TURBULENCE_TYPE, + (uint32_t)type[aTurbulence.mType]); + filter->SetAttribute( + ATT_TURBULENCE_RECT, + mDescription.PrimitiveSubregion() - aTurbulence.mOffset); + return FilterWrappers::Offset(mDT, filter, aTurbulence.mOffset); + } + + already_AddRefed<FilterNode> operator()( + const CompositeAttributes& aComposite) { + RefPtr<FilterNode> filter; + uint32_t op = aComposite.mOperator; + if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + const nsTArray<float>& coefficients = aComposite.mCoefficients; + static const float allZero[4] = {0, 0, 0, 0}; + filter = mDT->CreateFilter(FilterType::ARITHMETIC_COMBINE); + // All-zero coefficients sometimes occur in junk filters. + if (!filter || (coefficients.Length() == ArrayLength(allZero) && + ArrayEqual(coefficients.Elements(), allZero, + ArrayLength(allZero)))) { + return nullptr; + } + filter->SetAttribute(ATT_ARITHMETIC_COMBINE_COEFFICIENTS, + coefficients.Elements(), coefficients.Length()); + filter->SetInput(IN_ARITHMETIC_COMBINE_IN, mSources[0]); + filter->SetInput(IN_ARITHMETIC_COMBINE_IN2, mSources[1]); + } else { + filter = mDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + static const uint8_t operators[SVG_FECOMPOSITE_OPERATOR_LIGHTER + 1] = { + COMPOSITE_OPERATOR_OVER, // SVG_FECOMPOSITE_OPERATOR_UNKNOWN + COMPOSITE_OPERATOR_OVER, // SVG_FECOMPOSITE_OPERATOR_OVER + COMPOSITE_OPERATOR_IN, // SVG_FECOMPOSITE_OPERATOR_IN + COMPOSITE_OPERATOR_OUT, // SVG_FECOMPOSITE_OPERATOR_OUT + COMPOSITE_OPERATOR_ATOP, // SVG_FECOMPOSITE_OPERATOR_ATOP + COMPOSITE_OPERATOR_XOR, // SVG_FECOMPOSITE_OPERATOR_XOR + COMPOSITE_OPERATOR_OVER, // Unused, arithmetic is handled above + COMPOSITE_OPERATOR_LIGHTER // SVG_FECOMPOSITE_OPERATOR_LIGHTER + }; + filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)operators[op]); + filter->SetInput(IN_COMPOSITE_IN_START, mSources[1]); + filter->SetInput(IN_COMPOSITE_IN_START + 1, mSources[0]); + } + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()(const MergeAttributes& aMerge) { + if (mSources.Length() == 0) { + return nullptr; + } + if (mSources.Length() == 1) { + RefPtr<FilterNode> filter(mSources[0]); + return filter.forget(); + } + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_COMPOSITE_OPERATOR, + (uint32_t)COMPOSITE_OPERATOR_OVER); + for (size_t i = 0; i < mSources.Length(); i++) { + filter->SetInput(IN_COMPOSITE_IN_START + i, mSources[i]); + } + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const GaussianBlurAttributes& aGaussianBlur) { + return FilterWrappers::GaussianBlur(mDT, mSources[0], + aGaussianBlur.mStdDeviation); + } + + already_AddRefed<FilterNode> operator()( + const DropShadowAttributes& aDropShadow) { + RefPtr<FilterNode> alpha = FilterWrappers::ToAlpha(mDT, mSources[0]); + RefPtr<FilterNode> blur = + FilterWrappers::GaussianBlur(mDT, alpha, aDropShadow.mStdDeviation); + RefPtr<FilterNode> offsetBlur = FilterWrappers::Offset( + mDT, blur, IntPoint::Truncate(aDropShadow.mOffset)); + RefPtr<FilterNode> flood = mDT->CreateFilter(FilterType::FLOOD); + if (!flood) { + return nullptr; + } + sRGBColor color = aDropShadow.mColor; + if (mDescription.InputColorSpace(0) == ColorSpace::LinearRGB) { + color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)], + gsRGBToLinearRGBMap[uint8_t(color.g * 255)], + gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a); + } + flood->SetAttribute(ATT_FLOOD_COLOR, ToDeviceColor(color)); + + RefPtr<FilterNode> composite = mDT->CreateFilter(FilterType::COMPOSITE); + if (!composite) { + return nullptr; + } + composite->SetAttribute(ATT_COMPOSITE_OPERATOR, + (uint32_t)COMPOSITE_OPERATOR_IN); + composite->SetInput(IN_COMPOSITE_IN_START, offsetBlur); + composite->SetInput(IN_COMPOSITE_IN_START + 1, flood); + + RefPtr<FilterNode> filter = mDT->CreateFilter(FilterType::COMPOSITE); + if (!filter) { + return nullptr; + } + filter->SetAttribute(ATT_COMPOSITE_OPERATOR, + (uint32_t)COMPOSITE_OPERATOR_OVER); + filter->SetInput(IN_COMPOSITE_IN_START, composite); + filter->SetInput(IN_COMPOSITE_IN_START + 1, mSources[0]); + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()( + const SpecularLightingAttributes& aLighting) { + return operator()( + *(static_cast<const DiffuseLightingAttributes*>(&aLighting))); + } + + already_AddRefed<FilterNode> operator()( + const DiffuseLightingAttributes& aLighting) { + bool isSpecular = + mDescription.Attributes().is<SpecularLightingAttributes>(); + + if (aLighting.mLightType == LightType::None) { + return nullptr; + } + + enum { POINT = 0, SPOT, DISTANT } lightType = POINT; + + switch (aLighting.mLightType) { + case LightType::Point: + lightType = POINT; + break; + case LightType::Spot: + lightType = SPOT; + break; + case LightType::Distant: + lightType = DISTANT; + break; + default: + break; + } + + static const FilterType filterType[2][DISTANT + 1] = { + {FilterType::POINT_DIFFUSE, FilterType::SPOT_DIFFUSE, + FilterType::DISTANT_DIFFUSE}, + {FilterType::POINT_SPECULAR, FilterType::SPOT_SPECULAR, + FilterType::DISTANT_SPECULAR}}; + RefPtr<FilterNode> filter = + mDT->CreateFilter(filterType[isSpecular][lightType]); + if (!filter) { + return nullptr; + } + + filter->SetAttribute(ATT_LIGHTING_COLOR, ToDeviceColor(aLighting.mColor)); + filter->SetAttribute(ATT_LIGHTING_SURFACE_SCALE, aLighting.mSurfaceScale); + filter->SetAttribute(ATT_LIGHTING_KERNEL_UNIT_LENGTH, + aLighting.mKernelUnitLength); + + if (isSpecular) { + filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + aLighting.mLightingConstant); + filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT, + aLighting.mSpecularExponent); + } else { + filter->SetAttribute(ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT, + aLighting.mLightingConstant); + } + + switch (lightType) { + case POINT: { + Point3D position(aLighting.mLightValues[kPointLightPositionXIndex], + aLighting.mLightValues[kPointLightPositionYIndex], + aLighting.mLightValues[kPointLightPositionZIndex]); + filter->SetAttribute(ATT_POINT_LIGHT_POSITION, position); + break; + } + case SPOT: { + Point3D position(aLighting.mLightValues[kSpotLightPositionXIndex], + aLighting.mLightValues[kSpotLightPositionYIndex], + aLighting.mLightValues[kSpotLightPositionZIndex]); + filter->SetAttribute(ATT_SPOT_LIGHT_POSITION, position); + Point3D pointsAt(aLighting.mLightValues[kSpotLightPointsAtXIndex], + aLighting.mLightValues[kSpotLightPointsAtYIndex], + aLighting.mLightValues[kSpotLightPointsAtZIndex]); + filter->SetAttribute(ATT_SPOT_LIGHT_POINTS_AT, pointsAt); + filter->SetAttribute(ATT_SPOT_LIGHT_FOCUS, + aLighting.mLightValues[kSpotLightFocusIndex]); + filter->SetAttribute( + ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, + aLighting.mLightValues[kSpotLightLimitingConeAngleIndex]); + break; + } + case DISTANT: { + filter->SetAttribute( + ATT_DISTANT_LIGHT_AZIMUTH, + aLighting.mLightValues[kDistantLightAzimuthIndex]); + filter->SetAttribute( + ATT_DISTANT_LIGHT_ELEVATION, + aLighting.mLightValues[kDistantLightElevationIndex]); + break; + } + } + + filter->SetInput(IN_LIGHTING_IN, mSources[0]); + + return filter.forget(); + } + + already_AddRefed<FilterNode> operator()(const ImageAttributes& aImage) { + const Matrix& TM = aImage.mTransform; + if (!TM.Determinant()) { + return nullptr; + } + + // Pull the image from the additional image list using the index that's + // stored in the primitive description. + RefPtr<SourceSurface> inputImage = mInputImages[aImage.mInputIndex]; + + RefPtr<FilterNode> transform = mDT->CreateFilter(FilterType::TRANSFORM); + if (!transform) { + return nullptr; + } + transform->SetInput(IN_TRANSFORM_IN, inputImage); + transform->SetAttribute(ATT_TRANSFORM_MATRIX, TM); + transform->SetAttribute(ATT_TRANSFORM_FILTER, aImage.mFilter); + return transform.forget(); + } + + already_AddRefed<FilterNode> operator()(const ToAlphaAttributes& aToAlpha) { + return FilterWrappers::ToAlpha(mDT, mSources[0]); + } + }; + + return aDescription.Attributes().match(PrimitiveAttributesMatcher( + aDescription, aDT, aSources, aSourceRegions, aInputImages)); +} + +template <typename T> +static const T& ElementForIndex(int32_t aIndex, + const nsTArray<T>& aPrimitiveElements, + const T& aSourceGraphicElement, + const T& aFillPaintElement, + const T& aStrokePaintElement) { + switch (aIndex) { + case FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic: + case FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha: + return aSourceGraphicElement; + case FilterPrimitiveDescription::kPrimitiveIndexFillPaint: + return aFillPaintElement; + case FilterPrimitiveDescription::kPrimitiveIndexStrokePaint: + return aStrokePaintElement; + default: + MOZ_ASSERT(aIndex >= 0, "bad index"); + return aPrimitiveElements[aIndex]; + } +} + +static AlphaModel InputAlphaModelForPrimitive( + const FilterPrimitiveDescription& aDescr, int32_t aInputIndex, + AlphaModel aOriginalAlphaModel) { + const PrimitiveAttributes& atts = aDescr.Attributes(); + if (atts.is<TileAttributes>() || atts.is<OffsetAttributes>() || + atts.is<ToAlphaAttributes>()) { + return aOriginalAlphaModel; + } + if (atts.is<ColorMatrixAttributes>() || + atts.is<ComponentTransferAttributes>()) { + return AlphaModel::Unpremultiplied; + } + if (atts.is<DisplacementMapAttributes>()) { + return aInputIndex == 0 ? AlphaModel::Premultiplied + : AlphaModel::Unpremultiplied; + } + if (atts.is<ConvolveMatrixAttributes>()) { + return atts.as<ConvolveMatrixAttributes>().mPreserveAlpha + ? AlphaModel::Unpremultiplied + : AlphaModel::Premultiplied; + } + return AlphaModel::Premultiplied; +} + +static AlphaModel OutputAlphaModelForPrimitive( + const FilterPrimitiveDescription& aDescr, + const nsTArray<AlphaModel>& aInputAlphaModels) { + if (aInputAlphaModels.Length()) { + // For filters with inputs, the output is premultiplied if and only if the + // first input is premultiplied. + return InputAlphaModelForPrimitive(aDescr, 0, aInputAlphaModels[0]); + } + + // All filters without inputs produce premultiplied alpha. + return AlphaModel::Premultiplied; +} + +// Returns the output FilterNode, in premultiplied sRGB space. +already_AddRefed<FilterNode> FilterNodeGraphFromDescription( + DrawTarget* aDT, const FilterDescription& aFilter, + const Rect& aResultNeededRect, FilterNode* aSourceGraphic, + const IntRect& aSourceGraphicRect, FilterNode* aFillPaint, + FilterNode* aStrokePaint, + nsTArray<RefPtr<SourceSurface>>& aAdditionalImages) { + const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; + MOZ_RELEASE_ASSERT(!primitives.IsEmpty()); + + RefPtr<FilterCachedColorModels> sourceFilters[4]; + nsTArray<RefPtr<FilterCachedColorModels>> primitiveFilters; + + for (size_t i = 0; i < primitives.Length(); ++i) { + const FilterPrimitiveDescription& descr = primitives[i]; + + nsTArray<RefPtr<FilterNode>> inputFilterNodes; + nsTArray<IntRect> inputSourceRects; + nsTArray<AlphaModel> inputAlphaModels; + + for (size_t j = 0; j < descr.NumberOfInputs(); j++) { + int32_t inputIndex = descr.InputPrimitiveIndex(j); + if (inputIndex < 0) { + inputSourceRects.AppendElement(descr.FilterSpaceBounds()); + } else { + inputSourceRects.AppendElement( + primitives[inputIndex].PrimitiveSubregion()); + } + + RefPtr<FilterCachedColorModels> inputFilter; + if (inputIndex >= 0) { + MOZ_ASSERT(inputIndex < (int64_t)primitiveFilters.Length(), + "out-of-bounds input index!"); + inputFilter = primitiveFilters[inputIndex]; + MOZ_ASSERT( + inputFilter, + "Referred to input filter that comes after the current one?"); + } else { + int32_t sourceIndex = -inputIndex - 1; + MOZ_ASSERT(sourceIndex >= 0, "invalid source index"); + MOZ_ASSERT(sourceIndex < 4, "invalid source index"); + inputFilter = sourceFilters[sourceIndex]; + if (!inputFilter) { + RefPtr<FilterNode> sourceFilterNode; + + nsTArray<FilterNode*> primitiveFilters; + RefPtr<FilterNode> filt = + ElementForIndex(inputIndex, primitiveFilters, aSourceGraphic, + aFillPaint, aStrokePaint); + if (filt) { + sourceFilterNode = filt; + + // Clip the original SourceGraphic to the first filter region if the + // surface isn't already sized appropriately. + if ((inputIndex == + FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic || + inputIndex == + FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha) && + !descr.FilterSpaceBounds().Contains(aSourceGraphicRect)) { + sourceFilterNode = FilterWrappers::Crop( + aDT, sourceFilterNode, descr.FilterSpaceBounds()); + } + + if (inputIndex == + FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha) { + sourceFilterNode = FilterWrappers::ToAlpha(aDT, sourceFilterNode); + } + } + + inputFilter = new FilterCachedColorModels(aDT, sourceFilterNode, + ColorModel::PremulSRGB()); + sourceFilters[sourceIndex] = inputFilter; + } + } + MOZ_ASSERT(inputFilter); + + AlphaModel inputAlphaModel = InputAlphaModelForPrimitive( + descr, j, inputFilter->OriginalAlphaModel()); + inputAlphaModels.AppendElement(inputAlphaModel); + ColorModel inputColorModel(descr.InputColorSpace(j), inputAlphaModel); + inputFilterNodes.AppendElement( + inputFilter->ForColorModel(inputColorModel)); + } + + RefPtr<FilterNode> primitiveFilterNode = FilterNodeFromPrimitiveDescription( + descr, aDT, inputFilterNodes, inputSourceRects, aAdditionalImages); + + if (primitiveFilterNode) { + primitiveFilterNode = FilterWrappers::Crop(aDT, primitiveFilterNode, + descr.PrimitiveSubregion()); + } + + ColorModel outputColorModel( + descr.OutputColorSpace(), + OutputAlphaModelForPrimitive(descr, inputAlphaModels)); + RefPtr<FilterCachedColorModels> primitiveFilter = + new FilterCachedColorModels(aDT, primitiveFilterNode, outputColorModel); + + primitiveFilters.AppendElement(primitiveFilter); + } + + MOZ_RELEASE_ASSERT(!primitiveFilters.IsEmpty()); + return primitiveFilters.LastElement()->ForColorModel( + ColorModel::PremulSRGB()); +} + +// FilterSupport + +void FilterSupport::RenderFilterDescription( + DrawTarget* aDT, const FilterDescription& aFilter, const Rect& aRenderRect, + SourceSurface* aSourceGraphic, const IntRect& aSourceGraphicRect, + SourceSurface* aFillPaint, const IntRect& aFillPaintRect, + SourceSurface* aStrokePaint, const IntRect& aStrokePaintRect, + nsTArray<RefPtr<SourceSurface>>& aAdditionalImages, const Point& aDestPoint, + const DrawOptions& aOptions) { + RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint; + if (aSourceGraphic) { + sourceGraphic = FilterWrappers::ForSurface(aDT, aSourceGraphic, + aSourceGraphicRect.TopLeft()); + } + if (aFillPaint) { + fillPaint = + FilterWrappers::ForSurface(aDT, aFillPaint, aFillPaintRect.TopLeft()); + } + if (aStrokePaint) { + strokePaint = FilterWrappers::ForSurface(aDT, aStrokePaint, + aStrokePaintRect.TopLeft()); + } + RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription( + aDT, aFilter, aRenderRect, sourceGraphic, aSourceGraphicRect, fillPaint, + strokePaint, aAdditionalImages); + if (!resultFilter) { + gfxWarning() << "Filter is NULL."; + return; + } + aDT->DrawFilter(resultFilter, aRenderRect, aDestPoint, aOptions); +} + +static nsIntRegion UnionOfRegions(const nsTArray<nsIntRegion>& aRegions) { + nsIntRegion result; + for (size_t i = 0; i < aRegions.Length(); i++) { + result.Or(result, aRegions[i]); + } + return result; +} + +static int32_t InflateSizeForBlurStdDev(float aStdDev) { + double size = + std::min(aStdDev, kMaxStdDeviation) * (3 * sqrt(2 * M_PI) / 4) * 1.5; + return uint32_t(floor(size + 0.5)); +} + +static nsIntRegion ResultChangeRegionForPrimitive( + const FilterPrimitiveDescription& aDescription, + const nsTArray<nsIntRegion>& aInputChangeRegions) { + struct PrimitiveAttributesMatcher { + PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription, + const nsTArray<nsIntRegion>& aInputChangeRegions) + : mDescription(aDescription), + mInputChangeRegions(aInputChangeRegions) {} + + const FilterPrimitiveDescription& mDescription; + const nsTArray<nsIntRegion>& mInputChangeRegions; + + nsIntRegion operator()(const EmptyAttributes& aEmptyAttributes) { + return nsIntRegion(); + } + + nsIntRegion operator()(const BlendAttributes& aBlend) { + return UnionOfRegions(mInputChangeRegions); + } + + nsIntRegion operator()(const ColorMatrixAttributes& aColorMatrix) { + return mInputChangeRegions[0]; + } + + nsIntRegion operator()(const MorphologyAttributes& aMorphology) { + Size radii = aMorphology.mRadii; + int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius); + int32_t ry = + clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius); + return mInputChangeRegions[0].Inflated(nsIntMargin(ry, rx, ry, rx)); + } + + nsIntRegion operator()(const FloodAttributes& aFlood) { + return nsIntRegion(); + } + + nsIntRegion operator()(const TileAttributes& aTile) { + return mDescription.PrimitiveSubregion(); + } + + nsIntRegion operator()( + const ComponentTransferAttributes& aComponentTransfer) { + return mInputChangeRegions[0]; + } + + nsIntRegion operator()(const OpacityAttributes& aOpacity) { + return UnionOfRegions(mInputChangeRegions); + } + + nsIntRegion operator()(const ConvolveMatrixAttributes& aConvolveMatrix) { + if (aConvolveMatrix.mEdgeMode != EDGE_MODE_NONE) { + return mDescription.PrimitiveSubregion(); + } + Size kernelUnitLength = aConvolveMatrix.mKernelUnitLength; + IntSize kernelSize = aConvolveMatrix.mKernelSize; + IntPoint target = aConvolveMatrix.mTarget; + nsIntMargin m( + static_cast<int32_t>(ceil(kernelUnitLength.width * (target.x))), + static_cast<int32_t>(ceil(kernelUnitLength.height * (target.y))), + static_cast<int32_t>( + ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1))), + static_cast<int32_t>(ceil(kernelUnitLength.height * + (kernelSize.height - target.y - 1)))); + return mInputChangeRegions[0].Inflated(m); + } + + nsIntRegion operator()(const OffsetAttributes& aOffset) { + IntPoint offset = aOffset.mValue; + return mInputChangeRegions[0].MovedBy(offset.x, offset.y); + } + + nsIntRegion operator()(const DisplacementMapAttributes& aDisplacementMap) { + int32_t scale = ceil(std::abs(aDisplacementMap.mScale)); + return mInputChangeRegions[0].Inflated( + nsIntMargin(scale, scale, scale, scale)); + } + + nsIntRegion operator()(const TurbulenceAttributes& aTurbulence) { + return nsIntRegion(); + } + + nsIntRegion operator()(const CompositeAttributes& aComposite) { + return UnionOfRegions(mInputChangeRegions); + } + + nsIntRegion operator()(const MergeAttributes& aMerge) { + return UnionOfRegions(mInputChangeRegions); + } + + nsIntRegion operator()(const GaussianBlurAttributes& aGaussianBlur) { + const Size& stdDeviation = aGaussianBlur.mStdDeviation; + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + return mInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + nsIntRegion operator()(const DropShadowAttributes& aDropShadow) { + IntPoint offset = IntPoint::Truncate(aDropShadow.mOffset); + nsIntRegion offsetRegion = + mInputChangeRegions[0].MovedBy(offset.x, offset.y); + Size stdDeviation = aDropShadow.mStdDeviation; + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + nsIntRegion blurRegion = + offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + blurRegion.Or(blurRegion, mInputChangeRegions[0]); + return blurRegion; + } + + nsIntRegion operator()(const SpecularLightingAttributes& aLighting) { + return operator()( + *(static_cast<const DiffuseLightingAttributes*>(&aLighting))); + } + + nsIntRegion operator()(const DiffuseLightingAttributes& aLighting) { + Size kernelUnitLength = aLighting.mKernelUnitLength; + int32_t dx = ceil(kernelUnitLength.width); + int32_t dy = ceil(kernelUnitLength.height); + return mInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + nsIntRegion operator()(const ImageAttributes& aImage) { + return nsIntRegion(); + } + + nsIntRegion operator()(const ToAlphaAttributes& aToAlpha) { + return mInputChangeRegions[0]; + } + }; + + return aDescription.Attributes().match( + PrimitiveAttributesMatcher(aDescription, aInputChangeRegions)); +} + +/* static */ +nsIntRegion FilterSupport::ComputeResultChangeRegion( + const FilterDescription& aFilter, const nsIntRegion& aSourceGraphicChange, + const nsIntRegion& aFillPaintChange, + const nsIntRegion& aStrokePaintChange) { + const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; + MOZ_RELEASE_ASSERT(!primitives.IsEmpty()); + + nsTArray<nsIntRegion> resultChangeRegions; + + for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) { + const FilterPrimitiveDescription& descr = primitives[i]; + + nsTArray<nsIntRegion> inputChangeRegions; + for (size_t j = 0; j < descr.NumberOfInputs(); j++) { + int32_t inputIndex = descr.InputPrimitiveIndex(j); + MOZ_ASSERT(inputIndex < i, "bad input index"); + nsIntRegion inputChangeRegion = + ElementForIndex(inputIndex, resultChangeRegions, aSourceGraphicChange, + aFillPaintChange, aStrokePaintChange); + inputChangeRegions.AppendElement(inputChangeRegion); + } + nsIntRegion changeRegion = + ResultChangeRegionForPrimitive(descr, inputChangeRegions); + changeRegion.And(changeRegion, descr.PrimitiveSubregion()); + resultChangeRegions.AppendElement(changeRegion); + } + + MOZ_RELEASE_ASSERT(!resultChangeRegions.IsEmpty()); + return resultChangeRegions[resultChangeRegions.Length() - 1]; +} + +static float ResultOfZeroUnderTransferFunction( + const ComponentTransferAttributes& aFunctionAttributes, int32_t channel) { + switch (aFunctionAttributes.mTypes[channel]) { + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: { + const nsTArray<float>& tableValues = aFunctionAttributes.mValues[channel]; + if (tableValues.Length() < 2) { + return 0.0f; + } + return tableValues[0]; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: { + const nsTArray<float>& tableValues = aFunctionAttributes.mValues[channel]; + if (tableValues.Length() < 1) { + return 0.0f; + } + return tableValues[0]; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: { + const nsTArray<float>& values = aFunctionAttributes.mValues[channel]; + return values[kComponentTransferInterceptIndex]; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: { + const nsTArray<float>& values = aFunctionAttributes.mValues[channel]; + return values[kComponentTransferOffsetIndex]; + } + + case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY: + default: + return 0.0f; + } +} + +nsIntRegion FilterSupport::PostFilterExtentsForPrimitive( + const FilterPrimitiveDescription& aDescription, + const nsTArray<nsIntRegion>& aInputExtents) { + struct PrimitiveAttributesMatcher { + PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription, + const nsTArray<nsIntRegion>& aInputExtents) + : mDescription(aDescription), mInputExtents(aInputExtents) {} + + const FilterPrimitiveDescription& mDescription; + const nsTArray<nsIntRegion>& mInputExtents; + + nsIntRegion operator()(const EmptyAttributes& aEmptyAttributes) { + return IntRect(); + } + + nsIntRegion operator()(const BlendAttributes& aBlend) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const ColorMatrixAttributes& aColorMatrix) { + if (aColorMatrix.mType == (uint32_t)SVG_FECOLORMATRIX_TYPE_MATRIX) { + const nsTArray<float>& values = aColorMatrix.mValues; + if (values.Length() == 20 && values[19] > 0.0f) { + return mDescription.PrimitiveSubregion(); + } + } + return mInputExtents[0]; + } + + nsIntRegion operator()(const MorphologyAttributes& aMorphology) { + uint32_t op = aMorphology.mOperator; + if (op == SVG_OPERATOR_ERODE) { + return mInputExtents[0]; + } + Size radii = aMorphology.mRadii; + int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius); + int32_t ry = + clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius); + return mInputExtents[0].Inflated(nsIntMargin(ry, rx, ry, rx)); + } + + nsIntRegion operator()(const FloodAttributes& aFlood) { + if (aFlood.mColor.a == 0.0f) { + return IntRect(); + } + return mDescription.PrimitiveSubregion(); + } + + nsIntRegion operator()(const TileAttributes& aTile) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()( + const ComponentTransferAttributes& aComponentTransfer) { + if (ResultOfZeroUnderTransferFunction(aComponentTransfer, kChannelA) > + 0.0f) { + return mDescription.PrimitiveSubregion(); + } + return mInputExtents[0]; + } + + nsIntRegion operator()(const OpacityAttributes& aOpacity) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const ConvolveMatrixAttributes& aConvolveMatrix) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const OffsetAttributes& aOffset) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const DisplacementMapAttributes& aDisplacementMap) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const TurbulenceAttributes& aTurbulence) { + return mDescription.PrimitiveSubregion(); + } + + nsIntRegion operator()(const CompositeAttributes& aComposite) { + uint32_t op = aComposite.mOperator; + if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + // The arithmetic composite primitive can draw outside the bounding + // box of its source images. + const nsTArray<float>& coefficients = aComposite.mCoefficients; + MOZ_ASSERT(coefficients.Length() == 4); + + // The calculation is: + // r = c[0] * in[0] * in[1] + c[1] * in[0] + c[2] * in[1] + c[3] + nsIntRegion region; + if (coefficients[0] > 0.0f) { + region = mInputExtents[0].Intersect(mInputExtents[1]); + } + if (coefficients[1] > 0.0f) { + region.Or(region, mInputExtents[0]); + } + if (coefficients[2] > 0.0f) { + region.Or(region, mInputExtents[1]); + } + if (coefficients[3] > 0.0f) { + region = mDescription.PrimitiveSubregion(); + } + return region; + } + if (op == SVG_FECOMPOSITE_OPERATOR_IN) { + return mInputExtents[0].Intersect(mInputExtents[1]); + } + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const MergeAttributes& aMerge) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const GaussianBlurAttributes& aGaussianBlur) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const DropShadowAttributes& aDropShadow) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + + nsIntRegion operator()(const DiffuseLightingAttributes& aDiffuseLighting) { + return mDescription.PrimitiveSubregion(); + } + + nsIntRegion operator()( + const SpecularLightingAttributes& aSpecularLighting) { + return mDescription.PrimitiveSubregion(); + } + + nsIntRegion operator()(const ImageAttributes& aImage) { + return mDescription.PrimitiveSubregion(); + } + + nsIntRegion operator()(const ToAlphaAttributes& aToAlpha) { + return ResultChangeRegionForPrimitive(mDescription, mInputExtents); + } + }; + + return aDescription.Attributes().match( + PrimitiveAttributesMatcher(aDescription, aInputExtents)); +} + +/* static */ +nsIntRegion FilterSupport::ComputePostFilterExtents( + const FilterDescription& aFilter, + const nsIntRegion& aSourceGraphicExtents) { + const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; + MOZ_RELEASE_ASSERT(!primitives.IsEmpty()); + nsTArray<nsIntRegion> postFilterExtents; + + for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) { + const FilterPrimitiveDescription& descr = primitives[i]; + nsIntRegion filterSpace = descr.FilterSpaceBounds(); + + nsTArray<nsIntRegion> inputExtents; + for (size_t j = 0; j < descr.NumberOfInputs(); j++) { + int32_t inputIndex = descr.InputPrimitiveIndex(j); + MOZ_ASSERT(inputIndex < i, "bad input index"); + nsIntRegion inputExtent = + ElementForIndex(inputIndex, postFilterExtents, aSourceGraphicExtents, + filterSpace, filterSpace); + inputExtents.AppendElement(inputExtent); + } + nsIntRegion extent = PostFilterExtentsForPrimitive(descr, inputExtents); + extent.And(extent, descr.PrimitiveSubregion()); + postFilterExtents.AppendElement(extent); + } + + MOZ_RELEASE_ASSERT(!postFilterExtents.IsEmpty()); + return postFilterExtents[postFilterExtents.Length() - 1]; +} + +static nsIntRegion SourceNeededRegionForPrimitive( + const FilterPrimitiveDescription& aDescription, + const nsIntRegion& aResultNeededRegion, int32_t aInputIndex) { + struct PrimitiveAttributesMatcher { + PrimitiveAttributesMatcher(const FilterPrimitiveDescription& aDescription, + const nsIntRegion& aResultNeededRegion, + int32_t aInputIndex) + : mDescription(aDescription), + mResultNeededRegion(aResultNeededRegion), + mInputIndex(aInputIndex) {} + + const FilterPrimitiveDescription& mDescription; + const nsIntRegion& mResultNeededRegion; + const int32_t mInputIndex; + + nsIntRegion operator()(const EmptyAttributes& aEmptyAttributes) { + return nsIntRegion(); + } + + nsIntRegion operator()(const BlendAttributes& aBlend) { + return mResultNeededRegion; + } + + nsIntRegion operator()(const ColorMatrixAttributes& aColorMatrix) { + return mResultNeededRegion; + } + + nsIntRegion operator()(const MorphologyAttributes& aMorphology) { + Size radii = aMorphology.mRadii; + int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius); + int32_t ry = + clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius); + return mResultNeededRegion.Inflated(nsIntMargin(ry, rx, ry, rx)); + } + + nsIntRegion operator()(const FloodAttributes& aFlood) { + MOZ_CRASH("GFX: this shouldn't be called for filters without inputs"); + return nsIntRegion(); + } + + nsIntRegion operator()(const TileAttributes& aTile) { + return IntRect(INT32_MIN / 2, INT32_MIN / 2, INT32_MAX, INT32_MAX); + } + + nsIntRegion operator()( + const ComponentTransferAttributes& aComponentTransfer) { + return mResultNeededRegion; + } + + nsIntRegion operator()(const OpacityAttributes& aOpacity) { + return mResultNeededRegion; + } + + nsIntRegion operator()(const ConvolveMatrixAttributes& aConvolveMatrix) { + Size kernelUnitLength = aConvolveMatrix.mKernelUnitLength; + IntSize kernelSize = aConvolveMatrix.mKernelSize; + IntPoint target = aConvolveMatrix.mTarget; + nsIntMargin m( + static_cast<int32_t>( + ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1))), + static_cast<int32_t>(ceil(kernelUnitLength.height * + (kernelSize.height - target.y - 1))), + static_cast<int32_t>(ceil(kernelUnitLength.width * (target.x))), + static_cast<int32_t>(ceil(kernelUnitLength.height * (target.y)))); + return mResultNeededRegion.Inflated(m); + } + + nsIntRegion operator()(const OffsetAttributes& aOffset) { + IntPoint offset = aOffset.mValue; + return mResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y)); + } + + nsIntRegion operator()(const DisplacementMapAttributes& aDisplacementMap) { + if (mInputIndex == 1) { + return mResultNeededRegion; + } + int32_t scale = ceil(std::abs(aDisplacementMap.mScale)); + return mResultNeededRegion.Inflated( + nsIntMargin(scale, scale, scale, scale)); + } + + nsIntRegion operator()(const TurbulenceAttributes& aTurbulence) { + MOZ_CRASH("GFX: this shouldn't be called for filters without inputs"); + return nsIntRegion(); + } + + nsIntRegion operator()(const CompositeAttributes& aComposite) { + return mResultNeededRegion; + } + + nsIntRegion operator()(const MergeAttributes& aMerge) { + return mResultNeededRegion; + } + + nsIntRegion operator()(const GaussianBlurAttributes& aGaussianBlur) { + const Size& stdDeviation = aGaussianBlur.mStdDeviation; + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + return mResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + nsIntRegion operator()(const DropShadowAttributes& aDropShadow) { + IntPoint offset = IntPoint::Truncate(aDropShadow.mOffset); + nsIntRegion offsetRegion = + mResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y)); + Size stdDeviation = aDropShadow.mStdDeviation; + int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width); + int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height); + nsIntRegion blurRegion = + offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + blurRegion.Or(blurRegion, mResultNeededRegion); + return blurRegion; + } + + nsIntRegion operator()(const SpecularLightingAttributes& aLighting) { + return operator()( + *(static_cast<const DiffuseLightingAttributes*>(&aLighting))); + } + + nsIntRegion operator()(const DiffuseLightingAttributes& aLighting) { + Size kernelUnitLength = aLighting.mKernelUnitLength; + int32_t dx = ceil(kernelUnitLength.width); + int32_t dy = ceil(kernelUnitLength.height); + return mResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx)); + } + + nsIntRegion operator()(const ImageAttributes& aImage) { + MOZ_CRASH("GFX: this shouldn't be called for filters without inputs"); + return nsIntRegion(); + } + + nsIntRegion operator()(const ToAlphaAttributes& aToAlpha) { + return mResultNeededRegion; + } + }; + + return aDescription.Attributes().match(PrimitiveAttributesMatcher( + aDescription, aResultNeededRegion, aInputIndex)); +} + +/* static */ +void FilterSupport::ComputeSourceNeededRegions( + const FilterDescription& aFilter, const nsIntRegion& aResultNeededRegion, + nsIntRegion& aSourceGraphicNeededRegion, + nsIntRegion& aFillPaintNeededRegion, + nsIntRegion& aStrokePaintNeededRegion) { + const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives; + MOZ_ASSERT(!primitives.IsEmpty()); + if (primitives.IsEmpty()) { + return; + } + + nsTArray<nsIntRegion> primitiveNeededRegions; + primitiveNeededRegions.AppendElements(primitives.Length()); + + primitiveNeededRegions[primitives.Length() - 1] = aResultNeededRegion; + + for (int32_t i = primitives.Length() - 1; i >= 0; --i) { + const FilterPrimitiveDescription& descr = primitives[i]; + nsIntRegion neededRegion = primitiveNeededRegions[i]; + neededRegion.And(neededRegion, descr.PrimitiveSubregion()); + + for (size_t j = 0; j < descr.NumberOfInputs(); j++) { + int32_t inputIndex = descr.InputPrimitiveIndex(j); + MOZ_ASSERT(inputIndex < i, "bad input index"); + nsIntRegion* inputNeededRegion = + const_cast<nsIntRegion*>(&ElementForIndex( + inputIndex, primitiveNeededRegions, aSourceGraphicNeededRegion, + aFillPaintNeededRegion, aStrokePaintNeededRegion)); + inputNeededRegion->Or(*inputNeededRegion, SourceNeededRegionForPrimitive( + descr, neededRegion, j)); + } + } + + // Clip original SourceGraphic to first filter region. + const FilterPrimitiveDescription& firstDescr = primitives[0]; + aSourceGraphicNeededRegion.And(aSourceGraphicNeededRegion, + firstDescr.FilterSpaceBounds()); +} + +// FilterPrimitiveDescription + +FilterPrimitiveDescription::FilterPrimitiveDescription() + : mAttributes(EmptyAttributes()), + mOutputColorSpace(ColorSpace::SRGB), + mIsTainted(false) {} + +FilterPrimitiveDescription::FilterPrimitiveDescription( + PrimitiveAttributes&& aAttributes) + : mAttributes(std::move(aAttributes)), + mOutputColorSpace(ColorSpace::SRGB), + mIsTainted(false) {} + +bool FilterPrimitiveDescription::operator==( + const FilterPrimitiveDescription& aOther) const { + return mFilterPrimitiveSubregion.IsEqualInterior( + aOther.mFilterPrimitiveSubregion) && + mFilterSpaceBounds.IsEqualInterior(aOther.mFilterSpaceBounds) && + mOutputColorSpace == aOther.mOutputColorSpace && + mIsTainted == aOther.mIsTainted && + mInputPrimitives == aOther.mInputPrimitives && + mInputColorSpaces == aOther.mInputColorSpaces && + mAttributes == aOther.mAttributes; +} + +// FilterDescription + +bool FilterDescription::operator==(const FilterDescription& aOther) const { + return mPrimitives == aOther.mPrimitives; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/src/FilterSupport.h b/gfx/src/FilterSupport.h new file mode 100644 index 0000000000..94c535ab0f --- /dev/null +++ b/gfx/src/FilterSupport.h @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __FilterSupport_h +#define __FilterSupport_h + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Rect.h" +#include "nsRegion.h" +#include "nsTArray.h" + +namespace mozilla { +namespace gfx { +class FilterPrimitiveDescription; +class FilterNode; +struct FilterDescription; +} // namespace gfx +} // namespace mozilla + +extern const float gsRGBToLinearRGBMap[256]; + +namespace mozilla { +namespace gfx { +namespace FilterWrappers { +extern already_AddRefed<FilterNode> Clear(DrawTarget* aDT); +extern already_AddRefed<FilterNode> ForSurface( + DrawTarget* aDT, SourceSurface* aSurface, const IntPoint& aSurfacePosition); +} // namespace FilterWrappers + +// Morphology Operators +const unsigned short SVG_OPERATOR_UNKNOWN = 0; +const unsigned short SVG_OPERATOR_ERODE = 1; +const unsigned short SVG_OPERATOR_DILATE = 2; + +// ColorMatrix types +const unsigned short SVG_FECOLORMATRIX_TYPE_UNKNOWN = 0; +const unsigned short SVG_FECOLORMATRIX_TYPE_MATRIX = 1; +const unsigned short SVG_FECOLORMATRIX_TYPE_SATURATE = 2; +const unsigned short SVG_FECOLORMATRIX_TYPE_HUE_ROTATE = 3; +const unsigned short SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA = 4; +// ColorMatrix types for CSS filters +const unsigned short SVG_FECOLORMATRIX_TYPE_SEPIA = 5; + +// ComponentTransfer types +const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN = 0; +const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY = 1; +const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_TABLE = 2; +const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE = 3; +const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_LINEAR = 4; +const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_GAMMA = 5; +const unsigned short SVG_FECOMPONENTTRANSFER_SAME_AS_R = 6; + +// Blend Mode Values +const unsigned short SVG_FEBLEND_MODE_UNKNOWN = 0; +const unsigned short SVG_FEBLEND_MODE_NORMAL = 1; +const unsigned short SVG_FEBLEND_MODE_MULTIPLY = 2; +const unsigned short SVG_FEBLEND_MODE_SCREEN = 3; +const unsigned short SVG_FEBLEND_MODE_DARKEN = 4; +const unsigned short SVG_FEBLEND_MODE_LIGHTEN = 5; +const unsigned short SVG_FEBLEND_MODE_OVERLAY = 6; +const unsigned short SVG_FEBLEND_MODE_COLOR_DODGE = 7; +const unsigned short SVG_FEBLEND_MODE_COLOR_BURN = 8; +const unsigned short SVG_FEBLEND_MODE_HARD_LIGHT = 9; +const unsigned short SVG_FEBLEND_MODE_SOFT_LIGHT = 10; +const unsigned short SVG_FEBLEND_MODE_DIFFERENCE = 11; +const unsigned short SVG_FEBLEND_MODE_EXCLUSION = 12; +const unsigned short SVG_FEBLEND_MODE_HUE = 13; +const unsigned short SVG_FEBLEND_MODE_SATURATION = 14; +const unsigned short SVG_FEBLEND_MODE_COLOR = 15; +const unsigned short SVG_FEBLEND_MODE_LUMINOSITY = 16; + +// Edge Mode Values +const unsigned short SVG_EDGEMODE_UNKNOWN = 0; +const unsigned short SVG_EDGEMODE_DUPLICATE = 1; +const unsigned short SVG_EDGEMODE_WRAP = 2; +const unsigned short SVG_EDGEMODE_NONE = 3; + +// Channel Selectors +const unsigned short SVG_CHANNEL_UNKNOWN = 0; +const unsigned short SVG_CHANNEL_R = 1; +const unsigned short SVG_CHANNEL_G = 2; +const unsigned short SVG_CHANNEL_B = 3; +const unsigned short SVG_CHANNEL_A = 4; + +// Turbulence Types +const unsigned short SVG_TURBULENCE_TYPE_UNKNOWN = 0; +const unsigned short SVG_TURBULENCE_TYPE_FRACTALNOISE = 1; +const unsigned short SVG_TURBULENCE_TYPE_TURBULENCE = 2; + +// Composite Operators +const unsigned short SVG_FECOMPOSITE_OPERATOR_UNKNOWN = 0; +const unsigned short SVG_FECOMPOSITE_OPERATOR_OVER = 1; +const unsigned short SVG_FECOMPOSITE_OPERATOR_IN = 2; +const unsigned short SVG_FECOMPOSITE_OPERATOR_OUT = 3; +const unsigned short SVG_FECOMPOSITE_OPERATOR_ATOP = 4; +const unsigned short SVG_FECOMPOSITE_OPERATOR_XOR = 5; +const unsigned short SVG_FECOMPOSITE_OPERATOR_ARITHMETIC = 6; +const unsigned short SVG_FECOMPOSITE_OPERATOR_LIGHTER = 7; + +struct FilterAttribute; + +// Limits +const float kMaxStdDeviation = 500; + +// Simple PrimitiveAttributes: + +struct EmptyAttributes { + bool operator==(const EmptyAttributes& aOther) const { return true; } +}; + +struct BlendAttributes { + uint32_t mBlendMode; + + bool operator==(const BlendAttributes& aOther) const { + return mBlendMode == aOther.mBlendMode; + } +}; + +struct MorphologyAttributes { + uint32_t mOperator; + Size mRadii; + + bool operator==(const MorphologyAttributes& aOther) const { + return mOperator == aOther.mOperator && mRadii == aOther.mRadii; + } +}; + +struct FloodAttributes { + sRGBColor mColor; + + bool operator==(const FloodAttributes& aOther) const { + return mColor == aOther.mColor; + } +}; + +struct TileAttributes { + bool operator==(const TileAttributes& aOther) const { return true; } +}; + +struct OpacityAttributes { + float mOpacity; + + bool operator==(const OpacityAttributes& aOther) const { + return mOpacity == aOther.mOpacity; + } +}; + +struct OffsetAttributes { + IntPoint mValue; + + bool operator==(const OffsetAttributes& aOther) const { + return mValue == aOther.mValue; + } +}; + +struct DisplacementMapAttributes { + float mScale; + uint32_t mXChannel; + uint32_t mYChannel; + + bool operator==(const DisplacementMapAttributes& aOther) const { + return mScale == aOther.mScale && mXChannel == aOther.mXChannel && + mYChannel == aOther.mYChannel; + } +}; + +struct TurbulenceAttributes { + IntPoint mOffset; + Size mBaseFrequency; + float mSeed; + uint32_t mOctaves; + bool mStitchable; + uint32_t mType; + + bool operator==(const TurbulenceAttributes& aOther) const { + return mOffset == aOther.mOffset && + mBaseFrequency == aOther.mBaseFrequency && mSeed == aOther.mSeed && + mOctaves == aOther.mOctaves && mStitchable == aOther.mStitchable && + mType == aOther.mType; + } +}; + +struct MergeAttributes { + bool operator==(const MergeAttributes& aOther) const { return true; } +}; + +struct ImageAttributes { + uint32_t mFilter; + uint32_t mInputIndex; + Matrix mTransform; + + bool operator==(const ImageAttributes& aOther) const { + return mFilter == aOther.mFilter && mInputIndex == aOther.mInputIndex && + mTransform.ExactlyEquals(aOther.mTransform); + } +}; + +struct GaussianBlurAttributes { + Size mStdDeviation; + + bool operator==(const GaussianBlurAttributes& aOther) const { + return mStdDeviation == aOther.mStdDeviation; + } +}; + +struct DropShadowAttributes { + Size mStdDeviation; + Point mOffset; + sRGBColor mColor; + + bool operator==(const DropShadowAttributes& aOther) const { + return mStdDeviation == aOther.mStdDeviation && mOffset == aOther.mOffset && + mColor == aOther.mColor; + } +}; + +struct ToAlphaAttributes { + bool operator==(const ToAlphaAttributes& aOther) const { return true; } +}; + +// Complex PrimitiveAttributes: + +class ImplicitlyCopyableFloatArray : public CopyableTArray<float> { + public: + ImplicitlyCopyableFloatArray() = default; + + ImplicitlyCopyableFloatArray(ImplicitlyCopyableFloatArray&& aOther) = default; + + ImplicitlyCopyableFloatArray& operator=( + ImplicitlyCopyableFloatArray&& aOther) = default; + + ImplicitlyCopyableFloatArray(const ImplicitlyCopyableFloatArray& aOther) = + default; + + ImplicitlyCopyableFloatArray& operator=( + const ImplicitlyCopyableFloatArray& aOther) = default; +}; + +struct ColorMatrixAttributes { + uint32_t mType; + ImplicitlyCopyableFloatArray mValues; + + bool operator==(const ColorMatrixAttributes& aOther) const { + return mType == aOther.mType && mValues == aOther.mValues; + } +}; + +// If the types for G and B are SVG_FECOMPONENTTRANSFER_SAME_AS_R, +// use the R channel values - this lets us avoid copies. +const uint32_t kChannelROrRGB = 0; +const uint32_t kChannelG = 1; +const uint32_t kChannelB = 2; +const uint32_t kChannelA = 3; + +const uint32_t kComponentTransferSlopeIndex = 0; +const uint32_t kComponentTransferInterceptIndex = 1; + +const uint32_t kComponentTransferAmplitudeIndex = 0; +const uint32_t kComponentTransferExponentIndex = 1; +const uint32_t kComponentTransferOffsetIndex = 2; + +struct ComponentTransferAttributes { + uint8_t mTypes[4]; + ImplicitlyCopyableFloatArray mValues[4]; + + bool operator==(const ComponentTransferAttributes& aOther) const { + return mTypes[0] == aOther.mTypes[0] && mTypes[1] == aOther.mTypes[1] && + mTypes[2] == aOther.mTypes[2] && mTypes[3] == aOther.mTypes[3] && + mValues[0] == aOther.mValues[0] && mValues[1] == aOther.mValues[1] && + mValues[2] == aOther.mValues[2] && mValues[3] == aOther.mValues[3]; + } +}; + +struct ConvolveMatrixAttributes { + IntSize mKernelSize; + ImplicitlyCopyableFloatArray mKernelMatrix; + float mDivisor; + float mBias; + IntPoint mTarget; + uint32_t mEdgeMode; + Size mKernelUnitLength; + bool mPreserveAlpha; + + bool operator==(const ConvolveMatrixAttributes& aOther) const { + return mKernelSize == aOther.mKernelSize && + mKernelMatrix == aOther.mKernelMatrix && + mDivisor == aOther.mDivisor && mBias == aOther.mBias && + mTarget == aOther.mTarget && mEdgeMode == aOther.mEdgeMode && + mKernelUnitLength == aOther.mKernelUnitLength && + mPreserveAlpha == aOther.mPreserveAlpha; + } +}; + +struct CompositeAttributes { + uint32_t mOperator; + ImplicitlyCopyableFloatArray mCoefficients; + + bool operator==(const CompositeAttributes& aOther) const { + return mOperator == aOther.mOperator && + mCoefficients == aOther.mCoefficients; + } +}; + +enum class LightType { + None = 0, + Point, + Spot, + Distant, + Max, +}; + +const uint32_t kDistantLightAzimuthIndex = 0; +const uint32_t kDistantLightElevationIndex = 1; +const uint32_t kDistantLightNumAttributes = 2; + +const uint32_t kPointLightPositionXIndex = 0; +const uint32_t kPointLightPositionYIndex = 1; +const uint32_t kPointLightPositionZIndex = 2; +const uint32_t kPointLightNumAttributes = 3; + +const uint32_t kSpotLightPositionXIndex = 0; +const uint32_t kSpotLightPositionYIndex = 1; +const uint32_t kSpotLightPositionZIndex = 2; +const uint32_t kSpotLightPointsAtXIndex = 3; +const uint32_t kSpotLightPointsAtYIndex = 4; +const uint32_t kSpotLightPointsAtZIndex = 5; +const uint32_t kSpotLightFocusIndex = 6; +const uint32_t kSpotLightLimitingConeAngleIndex = 7; +const uint32_t kSpotLightNumAttributes = 8; + +struct DiffuseLightingAttributes { + LightType mLightType; + ImplicitlyCopyableFloatArray mLightValues; + float mSurfaceScale; + Size mKernelUnitLength; + sRGBColor mColor; + float mLightingConstant; + float mSpecularExponent; + + bool operator==(const DiffuseLightingAttributes& aOther) const { + return mLightType == aOther.mLightType && + mLightValues == aOther.mLightValues && + mSurfaceScale == aOther.mSurfaceScale && + mKernelUnitLength == aOther.mKernelUnitLength && + mColor == aOther.mColor; + } +}; + +struct SpecularLightingAttributes : public DiffuseLightingAttributes {}; + +enum class ColorSpace { SRGB, LinearRGB, Max }; + +enum class AlphaModel { Unpremultiplied, Premultiplied }; + +class ColorModel { + public: + static ColorModel PremulSRGB() { + return ColorModel(ColorSpace::SRGB, AlphaModel::Premultiplied); + } + + ColorModel(ColorSpace aColorSpace, AlphaModel aAlphaModel) + : mColorSpace(aColorSpace), mAlphaModel(aAlphaModel) {} + ColorModel() + : mColorSpace(ColorSpace::SRGB), mAlphaModel(AlphaModel::Premultiplied) {} + bool operator==(const ColorModel& aOther) const { + return mColorSpace == aOther.mColorSpace && + mAlphaModel == aOther.mAlphaModel; + } + + // Used to index FilterCachedColorModels::mFilterForColorModel. + uint8_t ToIndex() const { + return static_cast<uint8_t>(static_cast<uint8_t>(mColorSpace) << 1) | + static_cast<uint8_t>(mAlphaModel); + } + + ColorSpace mColorSpace; + AlphaModel mAlphaModel; +}; + +already_AddRefed<FilterNode> FilterNodeGraphFromDescription( + DrawTarget* aDT, const FilterDescription& aFilter, + const Rect& aResultNeededRect, FilterNode* aSourceGraphic, + const IntRect& aSourceGraphicRect, FilterNode* aFillPaint, + FilterNode* aStrokePaint, + nsTArray<RefPtr<SourceSurface>>& aAdditionalImages); + +/** + * The methods of this class are not on FilterDescription because + * FilterDescription is designed as a simple value holder that can be used + * on any thread. + */ +class FilterSupport { + public: + /** + * Draw the filter described by aFilter. All rect parameters are in filter + * space coordinates. aRenderRect specifies the part of the filter output + * that will be drawn at (0, 0) into the draw target aDT, subject to the + * current transform on aDT but with no additional scaling. + * The source surfaces must match their corresponding rect in size. + * aAdditionalImages carries the images that are referenced by the + * eImageInputIndex attribute on any image primitives in the filter. + */ + static void RenderFilterDescription( + DrawTarget* aDT, const FilterDescription& aFilter, + const Rect& aRenderRect, SourceSurface* aSourceGraphic, + const IntRect& aSourceGraphicRect, SourceSurface* aFillPaint, + const IntRect& aFillPaintRect, SourceSurface* aStrokePaint, + const IntRect& aStrokePaintRect, + nsTArray<RefPtr<SourceSurface>>& aAdditionalImages, + const Point& aDestPoint, const DrawOptions& aOptions = DrawOptions()); + + /** + * Computes the region that changes in the filter output due to a change in + * input. This is primarily needed when an individual piece of content inside + * a filtered container element changes. + */ + static nsIntRegion ComputeResultChangeRegion( + const FilterDescription& aFilter, const nsIntRegion& aSourceGraphicChange, + const nsIntRegion& aFillPaintChange, + const nsIntRegion& aStrokePaintChange); + + /** + * Computes the regions that need to be supplied in the filter inputs when + * painting aResultNeededRegion of the filter output. + */ + static void ComputeSourceNeededRegions( + const FilterDescription& aFilter, const nsIntRegion& aResultNeededRegion, + nsIntRegion& aSourceGraphicNeededRegion, + nsIntRegion& aFillPaintNeededRegion, + nsIntRegion& aStrokePaintNeededRegion); + + /** + * Computes the size of the filter output. + */ + static nsIntRegion ComputePostFilterExtents( + const FilterDescription& aFilter, + const nsIntRegion& aSourceGraphicExtents); + + /** + * Computes the size of a single FilterPrimitiveDescription's output given a + * set of input extents. + */ + static nsIntRegion PostFilterExtentsForPrimitive( + const FilterPrimitiveDescription& aDescription, + const nsTArray<nsIntRegion>& aInputExtents); +}; + +/** + * Create a 4x5 color matrix for the different ways to specify color matrices + * in SVG. + * + * Return false if the input is invalid or if the resulting matrix is the + * identity. + */ +bool ComputeColorMatrix(const ColorMatrixAttributes& aMatrixAttributes, + float aOutMatrix[20]); + +} // namespace gfx +} // namespace mozilla + +#endif // __FilterSupport_h diff --git a/gfx/src/FontPropertyTypes.h b/gfx/src/FontPropertyTypes.h new file mode 100644 index 0000000000..fe4f1f06e4 --- /dev/null +++ b/gfx/src/FontPropertyTypes.h @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* font specific types shared by both thebes and layout */ + +#ifndef GFX_FONT_PROPERTY_TYPES_H +#define GFX_FONT_PROPERTY_TYPES_H + +#include <algorithm> +#include <cstdint> +#include <cmath> +#include <utility> + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "mozilla/Assertions.h" +#include "mozilla/ServoStyleConsts.h" +#include "nsString.h" + +/* + * This file is separate from gfxFont.h so that layout can include it + * without bringing in gfxFont.h and everything it includes. + */ + +namespace mozilla { + +using FontSlantStyle = StyleFontStyle; +using FontWeight = StyleFontWeight; +using FontStretch = StyleFontStretch; + +/** + * Convenience type to hold a <min, max> pair representing a range of values. + * + * The min and max are both inclusive, so when min == max the range represents + * a single value (not an empty range). + */ +template <class T, class Derived> +class FontPropertyRange { + // This implementation assumes the underlying property type is a 16-bit value + // (see FromScalar and AsScalar below). + static_assert(sizeof(T) == 2, "FontPropertyValue should be a 16-bit type!"); + + public: + /** + * Construct a range from given minimum and maximum values (inclusive). + */ + FontPropertyRange(T aMin, T aMax) : mValues(aMin, aMax) { + MOZ_ASSERT(aMin <= aMax); + } + + /** + * Construct a range representing a single value (min==max). + */ + explicit FontPropertyRange(T aValue) : mValues(aValue, aValue) {} + + explicit FontPropertyRange(const FontPropertyRange& aOther) = default; + FontPropertyRange& operator=(const FontPropertyRange& aOther) = default; + + T Min() const { return mValues.first; } + T Max() const { return mValues.second; } + + /** + * Clamp the given value to this range. + * + * (We can't use mozilla::Clamp here because it only accepts integral types.) + */ + T Clamp(T aValue) const { + return aValue <= Min() ? Min() : (aValue >= Max() ? Max() : aValue); + } + + /** + * Return whether the range consists of a single unique value. + */ + bool IsSingle() const { return Min() == Max(); } + + bool operator==(const FontPropertyRange& aOther) const { + return mValues == aOther.mValues; + } + bool operator!=(const FontPropertyRange& aOther) const { + return mValues != aOther.mValues; + } + + /** + * Conversion of the property range to/from a single 32-bit scalar value, + * suitable for IPC serialization, hashing, caching. + * + * No assumptions should be made about the numeric value of the scalar. + * + * This depends on the underlying property type being a 16-bit value! + */ + using ScalarType = uint32_t; + + ScalarType AsScalar() const { + return (mValues.first.UnsignedRaw() << 16) | mValues.second.UnsignedRaw(); + } + + static Derived FromScalar(ScalarType aScalar) { + static_assert(std::is_base_of_v<FontPropertyRange, Derived>); + return Derived(T::FromRaw(aScalar >> 16), T::FromRaw(aScalar & 0xffff)); + } + + protected: + std::pair<T, T> mValues; +}; + +class WeightRange : public FontPropertyRange<FontWeight, WeightRange> { + public: + WeightRange(FontWeight aMin, FontWeight aMax) + : FontPropertyRange(aMin, aMax) {} + + explicit WeightRange(FontWeight aWeight) : FontPropertyRange(aWeight) {} + + WeightRange(const WeightRange& aOther) = default; + + void ToString(nsACString& aOutString, const char* aDelim = "..") const { + aOutString.AppendFloat(Min().ToFloat()); + if (!IsSingle()) { + aOutString.Append(aDelim); + aOutString.AppendFloat(Max().ToFloat()); + } + } +}; + +class StretchRange : public FontPropertyRange<FontStretch, StretchRange> { + public: + StretchRange(FontStretch aMin, FontStretch aMax) + : FontPropertyRange(aMin, aMax) {} + + explicit StretchRange(FontStretch aStretch) : FontPropertyRange(aStretch) {} + + StretchRange(const StretchRange& aOther) = default; + + void ToString(nsACString& aOutString, const char* aDelim = "..") const { + aOutString.AppendFloat(Min().ToFloat()); + if (!IsSingle()) { + aOutString.Append(aDelim); + aOutString.AppendFloat(Max().ToFloat()); + } + } +}; + +class SlantStyleRange + : public FontPropertyRange<FontSlantStyle, SlantStyleRange> { + public: + SlantStyleRange(FontSlantStyle aMin, FontSlantStyle aMax) + : FontPropertyRange(aMin, aMax) {} + + explicit SlantStyleRange(FontSlantStyle aStyle) : FontPropertyRange(aStyle) {} + + SlantStyleRange(const SlantStyleRange& aOther) = default; + + void ToString(nsACString& aOutString, const char* aDelim = "..") const { + Min().ToString(aOutString); + if (!IsSingle()) { + aOutString.Append(aDelim); + Max().ToString(aOutString); + } + } +}; + +} // namespace mozilla + +#endif // GFX_FONT_PROPERTY_TYPES_H diff --git a/gfx/src/RegionBuilder.h b/gfx/src/RegionBuilder.h new file mode 100644 index 0000000000..4913c43619 --- /dev/null +++ b/gfx/src/RegionBuilder.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * * License, v. 2.0. If a copy of the MPL was not distributed with this + * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RegionBuilder_h__ +#define RegionBuilder_h__ + +#include "nsTArray.h" +#include "pixman.h" + +template <typename RegionType> +class RegionBuilder { + public: + typedef typename RegionType::RectType RectType; + + RegionBuilder() = default; + + void OrWith(const RectType& aRect) { + pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()}; + mRects.AppendElement(box); + } + + RegionType ToRegion() const { return RegionType(mRects); } + + private: + nsTArray<pixman_box32_t> mRects; +}; + +#endif // RegionBuilder_h__ diff --git a/gfx/src/RelativeLuminanceUtils.h b/gfx/src/RelativeLuminanceUtils.h new file mode 100644 index 0000000000..34e3ccf6da --- /dev/null +++ b/gfx/src/RelativeLuminanceUtils.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RelativeLuminanceUtils_h +#define mozilla_RelativeLuminanceUtils_h + +#include "nsColor.h" + +namespace mozilla { + +// Utilities for calculating relative luminance based on the algorithm +// defined in https://www.w3.org/TR/WCAG20/#relativeluminancedef +class RelativeLuminanceUtils { + public: + // Compute the relative luminance. + static float Compute(nscolor aColor) { + float r = ComputeComponent(NS_GET_R(aColor)); + float g = ComputeComponent(NS_GET_G(aColor)); + float b = ComputeComponent(NS_GET_B(aColor)); + return ComputeFromComponents(r, g, b); + } + + // Adjust the relative luminance of the given color. + static nscolor Adjust(nscolor aColor, float aLuminance) { + float r = ComputeComponent(NS_GET_R(aColor)); + float g = ComputeComponent(NS_GET_G(aColor)); + float b = ComputeComponent(NS_GET_B(aColor)); + float luminance = ComputeFromComponents(r, g, b); + float factor = (aLuminance + 0.05f) / (luminance + 0.05f); + uint8_t r1 = + DecomputeComponent(std::max(0.0f, (r + 0.05f) * factor - 0.05f)); + uint8_t g1 = + DecomputeComponent(std::max(0.0f, (g + 0.05f) * factor - 0.05f)); + uint8_t b1 = + DecomputeComponent(std::max(0.0f, (b + 0.05f) * factor - 0.05f)); + return NS_RGBA(r1, g1, b1, NS_GET_A(aColor)); + } + + // https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio + static float ContrastRatio(nscolor aColor1, nscolor aColor2) { + float l1 = Compute(aColor1); + float l2 = Compute(aColor2); + if (l1 < l2) { + std::swap(l1, l2); + } + return (l1 + 0.05f) / (l2 + 0.05f); + } + + private: + static float ComputeComponent(uint8_t aComponent) { + float v = float(aComponent) / 255.0f; + if (v <= 0.03928f) { + return v / 12.92f; + } + return std::pow((v + 0.055f) / 1.055f, 2.4f); + } + + static constexpr float ComputeFromComponents(float aR, float aG, float aB) { + return 0.2126f * aR + 0.7152f * aG + 0.0722f * aB; + } + + // Inverse function of ComputeComponent. + static uint8_t DecomputeComponent(float aComponent) { + if (aComponent <= 0.03928f / 12.92f) { + aComponent *= 12.92f; + } else { + aComponent = std::pow(aComponent, 1.0f / 2.4f) * 1.055f - 0.055f; + } + return ClampColor(aComponent * 255.0f); + } +}; + +} // namespace mozilla + +#endif // mozilla_RelativeLuminanceUtils_h diff --git a/gfx/src/WPFGpuRaster.h b/gfx/src/WPFGpuRaster.h new file mode 100644 index 0000000000..2dcd3af648 --- /dev/null +++ b/gfx/src/WPFGpuRaster.h @@ -0,0 +1,66 @@ +/* -*- 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_WPF_GPU_RASTER_H +#define MOZILLA_GFX_WPF_GPU_RASTER_H + +#include <stddef.h> +#include <stdint.h> + +namespace WGR { + +enum class FillMode { EvenOdd, Winding }; +struct PathBuilder; +struct Point { + int32_t x; + int32_t y; +}; +constexpr uint8_t PathPointTypeStart = 0; +constexpr uint8_t PathPointTypeLine = 1; +constexpr uint8_t PathPointTypeBezier = 3; +constexpr uint8_t PathPointTypePathTypeMask = 0x07; +constexpr uint8_t PathPointTypeCloseSubpath = 0x80; +struct Path { + FillMode fill_mode; + const Point* points; + size_t num_points; + const uint8_t* types; + size_t num_types; +}; +struct OutputVertex { + float x; + float y; + float coverage; +}; +struct VertexBuffer { + OutputVertex* data; + size_t len; +}; + +extern "C" { +PathBuilder* wgr_new_builder(); +void wgr_builder_reset(PathBuilder* pb); +void wgr_builder_move_to(PathBuilder* pb, float x, float y); +void wgr_builder_line_to(PathBuilder* pb, float x, float y); +void wgr_builder_curve_to(PathBuilder* pb, float c1x, float c1y, float c2x, + float c2y, float x, float y); +void wgr_builder_quad_to(PathBuilder* pb, float cx, float cy, float x, float y); +void wgr_builder_close(PathBuilder* pb); +void wgr_builder_set_fill_mode(PathBuilder* pb, FillMode fill_mode); +Path wgr_builder_get_path(PathBuilder* pb); +VertexBuffer wgr_path_rasterize_to_tri_list( + const Path* p, int32_t clip_x, int32_t clip_y, int32_t clip_width, + int32_t clip_height, bool need_inside = true, bool need_outside = false, + bool rasterization_truncates = false, OutputVertex* output_ptr = nullptr, + size_t output_capacity = 0); +void wgr_path_release(Path p); +void wgr_vertex_buffer_release(VertexBuffer vb); +void wgr_builder_release(PathBuilder* pb); +}; + +} // namespace WGR + +#endif // MOZILLA_GFX_WPF_GPU_RASTER_H diff --git a/gfx/src/X11UndefineNone.h b/gfx/src/X11UndefineNone.h new file mode 100644 index 0000000000..3f0c4aef31 --- /dev/null +++ b/gfx/src/X11UndefineNone.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The header <X11/X.h> defines "None" as a macro that expands to "0L". +// This is terrible because many enumerations have an enumerator named "None". +// To work around this, we undefine the macro "None", and define a replacement +// macro named "X11None". +// Include this header after including X11 headers, where necessary. +#ifdef None +# undef None +# define X11None 0L +// <X11/X.h> also defines "RevertToNone" as a macro that expands to "(int)None". +// Since we are undefining "None", that stops working. To keep it working, +// we undefine "RevertToNone" and redefine it in terms of "X11None". +# ifdef RevertToNone +# undef RevertToNone +# define RevertToNone (int)X11None +# endif +#endif + +// X11 also defines Always, which conflicts with some style system enum variant +// names, so get rid of that too, given we don't use it anywhere else. +#ifdef Always +# undef Always +#endif + +// And Complex... +#ifdef Complex +# undef Complex +#endif + +#ifdef CurrentTime +# undef CurrentTime +# define X11CurrentTime 0L +#endif + +// X11/Xlib.h also defines True and False, get rid of those too for +// the same reasons as above... +#ifdef True +# undef True +# define X11True 1 +#endif +#ifdef False +# undef False +# define X11False 0 +#endif diff --git a/gfx/src/X11Util.cpp b/gfx/src/X11Util.cpp new file mode 100644 index 0000000000..90ad08202b --- /dev/null +++ b/gfx/src/X11Util.cpp @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "X11Util.h" +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "MainThreadUtils.h" // for NS_IsMainThread + +namespace mozilla { + +void FindVisualAndDepth(Display* aDisplay, VisualID aVisualID, Visual** aVisual, + int* aDepth) { + const Screen* screen = DefaultScreenOfDisplay(aDisplay); + + for (int d = 0; d < screen->ndepths; d++) { + Depth* d_info = &screen->depths[d]; + for (int v = 0; v < d_info->nvisuals; v++) { + Visual* visual = &d_info->visuals[v]; + if (visual->visualid == aVisualID) { + *aVisual = visual; + *aDepth = d_info->depth; + return; + } + } + } + + NS_ASSERTION(aVisualID == X11None, "VisualID not on Screen."); + *aVisual = nullptr; + *aDepth = 0; +} + +void FinishX(Display* aDisplay) { + unsigned long lastRequest = NextRequest(aDisplay) - 1; + if (lastRequest == LastKnownRequestProcessed(aDisplay)) return; + + XSync(aDisplay, X11False); +} + +} // namespace mozilla diff --git a/gfx/src/X11Util.h b/gfx/src/X11Util.h new file mode 100644 index 0000000000..9f41882002 --- /dev/null +++ b/gfx/src/X11Util.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_X11Util_h +#define mozilla_X11Util_h + +// Utilities common to all X clients, regardless of UI toolkit. + +#if defined(MOZ_WIDGET_GTK) +# include <gdk/gdk.h> +# include <gdk/gdkx.h> +# include "mozilla/WidgetUtilsGtk.h" +# include "X11UndefineNone.h" +#else +# error Unknown toolkit +#endif + +#include <string.h> // for memset + +namespace mozilla { + +/** + * Return the default X Display created and used by the UI toolkit. + */ +inline Display* DefaultXDisplay() { +#if defined(MOZ_WIDGET_GTK) + GdkDisplay* gdkDisplay = gdk_display_get_default(); + if (mozilla::widget::GdkIsX11Display(gdkDisplay)) { + return GDK_DISPLAY_XDISPLAY(gdkDisplay); + } +#endif + return nullptr; +} + +/** + * Sets *aVisual to point to aDisplay's Visual struct corresponding to + * aVisualID, and *aDepth to its depth. When aVisualID is None, these are set + * to nullptr and 0 respectively. Both out-parameter pointers are assumed + * non-nullptr. + */ +void FindVisualAndDepth(Display* aDisplay, VisualID aVisualID, Visual** aVisual, + int* aDepth); + +/** + * Ensure that all X requests have been processed. + * + * This is similar to XSync, but doesn't need a round trip if the previous + * request was synchronous or if events have been received since the last + * request. Subsequent FinishX calls will be noops if there have been no + * intermediate requests. + */ + +void FinishX(Display* aDisplay); + +} // namespace mozilla + +#endif // mozilla_X11Util_h diff --git a/gfx/src/components.conf b/gfx/src/components.conf new file mode 100644 index 0000000000..d1c5d69351 --- /dev/null +++ b/gfx/src/components.conf @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{a6cf9115-15b3-11d2-932e-00805f8add32}', + 'contract_ids': ['@mozilla.org/gfx/fontenumerator;1'], + 'type': 'nsThebesFontEnumerator', + 'headers': ['/gfx/src/nsThebesFontEnumerator.h'], + }, +] diff --git a/gfx/src/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp new file mode 100644 index 0000000000..d4b19f7b05 --- /dev/null +++ b/gfx/src/gfxCrashReporterUtils.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxCrashReporterUtils.h" +#include <string.h> // for strcmp +#include "mozilla/SchedulerGroup.h" // for SchedulerGroup +#include "mozilla/Services.h" // for GetObserverService +#include "mozilla/StaticMutex.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "MainThreadUtils.h" // for NS_IsMainThread +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsError.h" // for NS_OK, NS_FAILED, nsresult +#include "nsExceptionHandler.h" // for AppendAppNotesToCrashReport +#include "nsIObserver.h" // for nsIObserver, etc +#include "nsIObserverService.h" // for nsIObserverService +#include "nsIRunnable.h" // for nsIRunnable +#include "nsISupports.h" +#include "nsThreadUtils.h" // for Runnable +#include "nsTArray.h" // for nsTArray +#include "nscore.h" // for NS_IMETHOD, NS_IMETHODIMP, etc + +namespace mozilla { + +static nsTArray<nsCString>* gFeaturesAlreadyReported = nullptr; +static StaticMutex gFeaturesAlreadyReportedMutex MOZ_UNANNOTATED; + +class ObserverToDestroyFeaturesAlreadyReported final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + ObserverToDestroyFeaturesAlreadyReported() = default; + + private: + virtual ~ObserverToDestroyFeaturesAlreadyReported() = default; +}; + +NS_IMPL_ISUPPORTS(ObserverToDestroyFeaturesAlreadyReported, nsIObserver) + +NS_IMETHODIMP +ObserverToDestroyFeaturesAlreadyReported::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "xpcom-shutdown")) { + StaticMutexAutoLock al(gFeaturesAlreadyReportedMutex); + if (gFeaturesAlreadyReported) { + delete gFeaturesAlreadyReported; + gFeaturesAlreadyReported = nullptr; + } + } + return NS_OK; +} + +class RegisterObserverRunnable : public Runnable { + public: + RegisterObserverRunnable() : Runnable("RegisterObserverRunnable") {} + NS_IMETHOD Run() override { + // LeakLog made me do this. Basically, I just wanted + // gFeaturesAlreadyReported to be a static nsTArray<nsCString>, and LeakLog + // was complaining about leaks like this: + // leaked 1 instance of nsTArray_base with size 8 bytes + // leaked 7 instances of nsStringBuffer with size 8 bytes each (56 bytes + // total) + // So this is a work-around using a pointer, and using a nsIObserver to + // deallocate on xpcom shutdown. Yay for fighting bloat. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) return NS_OK; + RefPtr<ObserverToDestroyFeaturesAlreadyReported> observer = + new ObserverToDestroyFeaturesAlreadyReported; + observerService->AddObserver(observer, "xpcom-shutdown", false); + return NS_OK; + } +}; + +class AppendAppNotesRunnable : public Runnable { + public: + explicit AppendAppNotesRunnable(const nsACString& aFeatureStr) + : Runnable("AppendAppNotesRunnable"), mFeatureString(aFeatureStr) {} + + NS_IMETHOD Run() override { + CrashReporter::AppendAppNotesToCrashReport(mFeatureString); + return NS_OK; + } + + private: + nsAutoCString mFeatureString; +}; + +void ScopedGfxFeatureReporter::WriteAppNote(char statusChar, + int32_t statusNumber) { + StaticMutexAutoLock al(gFeaturesAlreadyReportedMutex); + + if (!gFeaturesAlreadyReported) { + gFeaturesAlreadyReported = new nsTArray<nsCString>; + nsCOMPtr<nsIRunnable> r = new RegisterObserverRunnable(); + SchedulerGroup::Dispatch(r.forget()); + } + + nsAutoCString featureString; + if (statusNumber == 0) { + featureString.AppendPrintf("%s%c ", mFeature, statusChar); + } else { + featureString.AppendPrintf("%s%c%d ", mFeature, statusChar, statusNumber); + } + + if (!gFeaturesAlreadyReported->Contains(featureString)) { + gFeaturesAlreadyReported->AppendElement(featureString); + AppNote(featureString); + } +} + +void ScopedGfxFeatureReporter::AppNote(const nsACString& aMessage) { + if (NS_IsMainThread()) { + CrashReporter::AppendAppNotesToCrashReport(aMessage); + } else { + nsCOMPtr<nsIRunnable> r = new AppendAppNotesRunnable(aMessage); + NS_DispatchToMainThread(r.forget()); + } +} + +} // end namespace mozilla diff --git a/gfx/src/gfxCrashReporterUtils.h b/gfx/src/gfxCrashReporterUtils.h new file mode 100644 index 0000000000..30b670df61 --- /dev/null +++ b/gfx/src/gfxCrashReporterUtils.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gfxCrashReporterUtils_h__ +#define gfxCrashReporterUtils_h__ + +#include "nsString.h" + +namespace mozilla { + +/** \class ScopedGfxFeatureReporter + * + * On creation, adds "FeatureName?" to AppNotes + * On destruction, adds "FeatureName-", or "FeatureName+" if you called + * SetSuccessful(). + * + * Any such string is added at most once to AppNotes, and is subsequently + * skipped. + * + * This ScopedGfxFeatureReporter class is designed to be fool-proof to use in + * functions that have many exit points. We don't want to encourage having + * function with many exit points. It just happens that our graphics features + * initialization functions are like that. + */ +class ScopedGfxFeatureReporter { + public: + explicit ScopedGfxFeatureReporter(const char* aFeature, bool aForce = false) + : mFeature(aFeature), mStatusChar('-'), mStatusNumber(0) { + WriteAppNote(aForce ? '!' : '?', 0); + } + ~ScopedGfxFeatureReporter() { WriteAppNote(mStatusChar, mStatusNumber); } + void SetSuccessful() { mStatusChar = '+'; } + void SetSuccessful(int32_t aNumber) { + mStatusChar = '+'; + mStatusNumber = aNumber; + } + + static void AppNote(const nsACString& aMessage); + + class AppNoteWritingRunnable; + + protected: + const char* mFeature; + char mStatusChar; + int32_t mStatusNumber; + + private: + void WriteAppNote(char statusChar, int32_t statusNumber); +}; + +} // end namespace mozilla + +#endif // gfxCrashReporterUtils_h__ diff --git a/gfx/src/gfxTelemetry.cpp b/gfx/src/gfxTelemetry.cpp new file mode 100644 index 0000000000..f434164c40 --- /dev/null +++ b/gfx/src/gfxTelemetry.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gfxTelemetry.h" + +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace gfx { + +const char* FeatureStatusToString(FeatureStatus aStatus) { + switch (aStatus) { + case FeatureStatus::Unused: + return "unused"; + case FeatureStatus::Unavailable: + return "unavailable"; + case FeatureStatus::UnavailableInSafeMode: + return "unavailable-in-safe-mode"; + case FeatureStatus::UnavailableNoGpuProcess: + return "unavailable-no-gpu-process"; + case FeatureStatus::UnavailableNoHwCompositing: + return "unavailable-no-hw-compositing"; + case FeatureStatus::UnavailableNotBuilt: + return "unavailable-not-built"; + case FeatureStatus::UnavailableNoAngle: + return "unavailable-no-angle"; + case FeatureStatus::UnavailableNoWebRender: + return "unavailable-no-webrender"; + case FeatureStatus::CrashedInHandler: + return "crashed"; + case FeatureStatus::Blocked: + return "blocked"; + case FeatureStatus::BlockedDeviceUnknown: + return "blocked-device-unknown"; + case FeatureStatus::BlockedDeviceTooOld: + return "blocked-device-too-old"; + case FeatureStatus::BlockedVendorUnsupported: + return "blocked-vendor-unsupported"; + case FeatureStatus::BlockedHasBattery: + return "blocked-has-battery"; + case FeatureStatus::BlockedScreenTooLarge: + return "blocked-screen-too-large"; + case FeatureStatus::BlockedScreenUnknown: + return "blocked-screen-unknown"; + case FeatureStatus::BlockedNoGfxInfo: + return "blocked-no-gfx-info"; + case FeatureStatus::BlockedOverride: + return "blocked-override"; + case FeatureStatus::BlockedReleaseChannelIntel: + return "blocked-release-channel-intel"; + case FeatureStatus::BlockedReleaseChannelAMD: + return "blocked-release-channel-amd"; + case FeatureStatus::BlockedReleaseChannelNvidia: + return "blocked-release-channel-nvidia"; + case FeatureStatus::BlockedReleaseChannelBattery: + return "blocked-release-channel-battery"; + case FeatureStatus::BlockedReleaseChannelAndroid: + return "blocked-release-channel-android"; + case FeatureStatus::Denied: + return "denied"; + case FeatureStatus::Blocklisted: + return "blocklisted"; + case FeatureStatus::OptIn: + return "opt-in"; + case FeatureStatus::Failed: + return "failed"; + case FeatureStatus::Disabled: + return "disabled"; + case FeatureStatus::Available: + return "available"; + case FeatureStatus::ForceEnabled: + return "force_enabled"; + case FeatureStatus::CrashedOnStartup: + return "crashed_on_startup"; + case FeatureStatus::Broken: + return "broken"; + default: + MOZ_ASSERT_UNREACHABLE("missing status case"); + return "unknown"; + } +} + +bool IsFeatureStatusFailure(FeatureStatus aStatus) { + return !(aStatus == FeatureStatus::Unused || + aStatus == FeatureStatus::Available || + aStatus == FeatureStatus::ForceEnabled); +} + +bool IsFeatureStatusSuccess(FeatureStatus aStatus) { + return aStatus == FeatureStatus::Available || + aStatus == FeatureStatus::ForceEnabled; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/src/gfxTelemetry.h b/gfx/src/gfxTelemetry.h new file mode 100644 index 0000000000..37d603674a --- /dev/null +++ b/gfx/src/gfxTelemetry.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef gfx_src_gfxTelemetry_h__ +#define gfx_src_gfxTelemetry_h__ + +#include <cstdint> + +namespace mozilla { +namespace gfx { + +// Describes the status of a graphics feature, in terms of whether or not we've +// attempted to initialize the feature, and if so, whether or not it succeeded +// (and if not, why). +enum class FeatureStatus { + // This feature has not been requested. + Unused, + + // This feature is unavailable due to Safe Mode, not being included with + // the operating system, or a dependent feature being disabled. + Unavailable, + UnavailableInSafeMode, + UnavailableNoGpuProcess, + UnavailableNoHwCompositing, + UnavailableNotBuilt, + UnavailableNoAngle, + UnavailableNoWebRender, + + // This feature crashed immediately when we tried to initialize it, but we + // were able to recover via SEH (or something similar). + CrashedInHandler, + + // This feature was blocked for reasons outside the blocklist, such as a + // runtime test failing. + Blocked, + BlockedDeviceUnknown, + BlockedDeviceTooOld, + BlockedVendorUnsupported, + BlockedHasBattery, + BlockedScreenTooLarge, + BlockedScreenUnknown, + BlockedNoGfxInfo, + BlockedOverride, + BlockedReleaseChannelIntel, + BlockedReleaseChannelAMD, + BlockedReleaseChannelNvidia, + BlockedReleaseChannelBattery, + BlockedReleaseChannelAndroid, + + // This feature has been blocked by the allowlist. + Denied, + + // This feature has been blocked by the graphics blocklist. + Blocklisted, + + // This feature is disabled by default, and so activation isn't attempted + // unless something explicitly enables it. + OptIn, + + // This feature was attempted but failed to activate. + Failed, + + // This feature was explicitly disabled by the user. + Disabled, + + // This feature is available for use. + Available, + + // This feature was explicitly force-enabled by the user. + ForceEnabled, + + // This feature was disabled due to the startup crash guard. + CrashedOnStartup, + + // This feature was attempted but later determined to be broken. + Broken, + + // Add new entries above here. + LAST +}; + +const char* FeatureStatusToString(FeatureStatus aStatus); +bool IsFeatureStatusFailure(FeatureStatus aStatus); +bool IsFeatureStatusSuccess(FeatureStatus aStatus); + +enum class TelemetryDeviceCode : uint32_t { Content = 0, Image = 1, D2D1 = 2 }; + +} // namespace gfx +} // namespace mozilla + +#endif // gfx_src_gfxTelemetry_h__ diff --git a/gfx/src/moz.build b/gfx/src/moz.build new file mode 100644 index 0000000000..dcfc670704 --- /dev/null +++ b/gfx/src/moz.build @@ -0,0 +1,96 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "nsIFontEnumerator.idl", +] + +XPIDL_MODULE = "gfx" + +DEFINES["MOZ_APP_VERSION"] = '"%s"' % CONFIG["MOZ_APP_VERSION"] + +EXPORTS += [ + "DriverCrashGuard.h", + "FilterDescription.h", + "FilterSupport.h", + "gfxCrashReporterUtils.h", + "gfxTelemetry.h", + "nsBoundingMetrics.h", + "nsColor.h", + "nsCoord.h", + "nsDeviceContext.h", + "nsFont.h", + "nsFontCache.h", + "nsFontMetrics.h", + "nsGfxCIID.h", + "nsITheme.h", + "nsMargin.h", + "nsPoint.h", + "nsRect.h", + "nsRectAbsolute.h", + "nsRegion.h", + "nsRegionFwd.h", + "nsSize.h", + "nsTransform2D.h", + "RegionBuilder.h", + "X11UndefineNone.h", +] + +EXPORTS.mozilla += [ + "AppUnits.h", + "ArrayView.h", + "FontPropertyTypes.h", + "RelativeLuminanceUtils.h", +] + +EXPORTS.mozilla.gfx += [ + "AAStroke.h", + "CompositorHitTestInfo.h", + "WPFGpuRaster.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", + "nsFontCache.cpp", + "nsFontMetrics.cpp", + "nsRect.cpp", + "nsRegion.cpp", + "nsThebesFontEnumerator.cpp", + "nsTransform2D.cpp", +] + +# nsDeviceContext.cpp cannot be built in unified mode because it pulls in OS X system headers. +SOURCES += [ + "nsDeviceContext.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/dom/ipc", # for ContentChild.h + "/gfx/cairo/cairo/src", +] + +FINAL_LIBRARY = "xul" + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"] diff --git a/gfx/src/nsBoundingMetrics.h b/gfx/src/nsBoundingMetrics.h new file mode 100644 index 0000000000..e937dc1870 --- /dev/null +++ b/gfx/src/nsBoundingMetrics.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsBoundingMetrics_h +#define __nsBoundingMetrics_h + +#include "nsCoord.h" +#include <algorithm> + +/* Struct used for accurate measurements of a string, in order to + * allow precise positioning when processing MathML. This is in its + * own header file because some very-widely-included headers need it + * but not the rest of nsFontMetrics, or vice versa. + */ + +struct nsBoundingMetrics { + /////////// + // Metrics that _exactly_ enclose the text: + + // The character coordinate system is the one used on X Windows: + // 1. The origin is located at the intersection of the baseline + // with the left of the character's cell. + // 2. All horizontal bearings are oriented from left to right. + // 2. All horizontal bearings are oriented from left to right. + // 3. The ascent is oriented from bottom to top (being 0 at the orgin). + // 4. The descent is oriented from top to bottom (being 0 at the origin). + + // Note that Win32/Mac/PostScript use a different convention for + // the descent (all vertical measurements are oriented from bottom + // to top on these palatforms). Make sure to flip the sign of the + // descent on these platforms for cross-platform compatibility. + + // Any of the following member variables listed here can have + // positive or negative value. + + nscoord leftBearing; + /* The horizontal distance from the origin of the drawing + operation to the left-most part of the drawn string. */ + + nscoord rightBearing; + /* The horizontal distance from the origin of the drawing + operation to the right-most part of the drawn string. + The _exact_ width of the string is therefore: + rightBearing - leftBearing */ + + nscoord ascent; + /* The vertical distance from the origin of the drawing + operation to the top-most part of the drawn string. */ + + nscoord descent; + /* The vertical distance from the origin of the drawing + operation to the bottom-most part of the drawn string. + The _exact_ height of the string is therefore: + ascent + descent */ + + nscoord width; + /* The horizontal distance from the origin of the drawing + operation to the correct origin for drawing another string + to follow the current one. Depending on the font, this + could be greater than or less than the right bearing. */ + + nsBoundingMetrics() + : leftBearing(0), rightBearing(0), ascent(0), descent(0), width(0) {} + + void operator+=(const nsBoundingMetrics& bm) { + if (ascent + descent == 0 && rightBearing - leftBearing == 0) { + ascent = bm.ascent; + descent = bm.descent; + leftBearing = width + bm.leftBearing; + rightBearing = width + bm.rightBearing; + } else { + if (ascent < bm.ascent) ascent = bm.ascent; + if (descent < bm.descent) descent = bm.descent; + leftBearing = std::min(leftBearing, width + bm.leftBearing); + rightBearing = std::max(rightBearing, width + bm.rightBearing); + } + width += bm.width; + } +}; + +#endif // __nsBoundingMetrics_h diff --git a/gfx/src/nsColor.cpp b/gfx/src/nsColor.cpp new file mode 100644 index 0000000000..82a86c28db --- /dev/null +++ b/gfx/src/nsColor.cpp @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/MathAlgorithms.h" + +#include "nsColor.h" +#include <sys/types.h> // for int32_t +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsStaticNameTable.h" +#include "nsString.h" // for nsAutoCString, nsString, etc +#include "nscore.h" // for nsAString, etc +#include "prtypes.h" // for PR_BEGIN_MACRO, etc + +using namespace mozilla; + +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; +} + +// Fast approximate division by 255. It has the property that +// for all 0 <= n <= 255*255, FAST_DIVIDE_BY_255(n) == n/255. +// But it only uses two adds and two shifts instead of an +// integer division (which is expensive on many processors). +// +// equivalent to target=v/255 +#define FAST_DIVIDE_BY_255(target, v) \ + PR_BEGIN_MACRO \ + unsigned tmp_ = v; \ + target = ((tmp_ << 8) + tmp_ + 255) >> 16; \ + PR_END_MACRO + +// Macro to blend two colors +// +// equivalent to target = (bg*(255-fgalpha) + fg*fgalpha)/255 +#define MOZ_BLEND(target, bg, fg, fgalpha) \ + FAST_DIVIDE_BY_255(target, (bg) * (255 - fgalpha) + (fg) * (fgalpha)) + +nscolor NS_ComposeColors(nscolor aBG, nscolor aFG) { + // This function uses colors that are non premultiplied alpha. + int r, g, b, a; + + int bgAlpha = NS_GET_A(aBG); + int fgAlpha = NS_GET_A(aFG); + + // Compute the final alpha of the blended color + // a = fgAlpha + bgAlpha*(255 - fgAlpha)/255; + FAST_DIVIDE_BY_255(a, bgAlpha * (255 - fgAlpha)); + a = fgAlpha + a; + int blendAlpha; + if (a == 0) { + // In this case the blended color is totally trasparent, + // we preserve the color information of the foreground color. + blendAlpha = 255; + } else { + blendAlpha = (fgAlpha * 255) / a; + } + MOZ_BLEND(r, NS_GET_R(aBG), NS_GET_R(aFG), blendAlpha); + MOZ_BLEND(g, NS_GET_G(aBG), NS_GET_G(aFG), blendAlpha); + MOZ_BLEND(b, NS_GET_B(aBG), NS_GET_B(aFG), blendAlpha); + + return NS_RGBA(r, g, b, a); +} diff --git a/gfx/src/nsColor.h b/gfx/src/nsColor.h new file mode 100644 index 0000000000..3525d53d31 --- /dev/null +++ b/gfx/src/nsColor.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsColor_h___ +#define nsColor_h___ + +#include <stdint.h> // for uint8_t, uint32_t +#include "nsCoord.h" // for NSToIntRound +#include "nsStringFwd.h" + +// A color is a 32 bit unsigned integer with four components: R, G, B +// and A. +typedef uint32_t nscolor; + +// Make a color out of r,g,b values. This assumes that the r,g,b values are +// properly constrained to 0-255. This also assumes that a is 255. +#define NS_RGB(_r, _g, _b) \ + ((nscolor)((255 << 24) | ((_b) << 16) | ((_g) << 8) | (_r))) + +// Make a color out of r,g,b,a values. This assumes that the r,g,b,a +// values are properly constrained to 0-255. +#define NS_RGBA(_r, _g, _b, _a) \ + ((nscolor)(((_a) << 24) | ((_b) << 16) | ((_g) << 8) | (_r))) + +// Extract color components from nscolor +#define NS_GET_R(_rgba) ((uint8_t)((_rgba) & 0xff)) +#define NS_GET_G(_rgba) ((uint8_t)(((_rgba) >> 8) & 0xff)) +#define NS_GET_B(_rgba) ((uint8_t)(((_rgba) >> 16) & 0xff)) +#define NS_GET_A(_rgba) ((uint8_t)(((_rgba) >> 24) & 0xff)) + +namespace mozilla { + +template <typename T> +inline uint8_t ClampColor(T aColor) { + if (aColor >= 255) { + return 255; + } + if (aColor <= 0) { + return 0; + } + return NSToIntRound(aColor); +} + +} // namespace mozilla + +enum class nsHexColorType : uint8_t { + NoAlpha, // 3 or 6 digit hex colors only + AllowAlpha, // 3, 4, 6, or 8 digit hex colors +}; + +// Translate a hex string to a color. Return true if it parses ok, +// otherwise return false. +// This accepts the number of digits specified by aType. +bool NS_HexToRGBA(const nsAString& aBuf, nsHexColorType aType, + nscolor* aResult); + +// Compose one NS_RGB color onto another. The result is what +// you get if you draw aFG on top of aBG with operator OVER. +nscolor NS_ComposeColors(nscolor aBG, nscolor aFG); + +namespace mozilla { + +inline uint32_t RoundingDivideBy255(uint32_t n) { + // There is an approximate alternative: ((n << 8) + n + 32896) >> 16 + // But that is actually slower than this simple expression on a modern + // machine with a modern compiler. + return (n + 127) / 255; +} + +} // namespace mozilla + +// Translate a hex string to a color. Return true if it parses ok, +// otherwise return false. +// This version accepts 1 to 9 digits (missing digits are 0) +bool NS_LooseHexToRGB(const nsString& aBuf, nscolor* aResult); + +// There is no function to translate a color to a hex string, because +// the hex-string syntax does not support transparency. + +#endif /* nsColor_h___ */ diff --git a/gfx/src/nsCoord.h b/gfx/src/nsCoord.h new file mode 100644 index 0000000000..6c2cd62be1 --- /dev/null +++ b/gfx/src/nsCoord.h @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSCOORD_H +#define NSCOORD_H + +#include <algorithm> +#include <cstdint> +#include <cstdlib> +#include <math.h> + +#include "mozilla/Assertions.h" +#include "mozilla/gfx/Coord.h" +#include "nsMathUtils.h" + +/* + * Basic type used for the geometry classes. + * + * Normally all coordinates are maintained in an app unit coordinate + * space. An app unit is 1/60th of a CSS device pixel, which is, in turn + * an integer number of device pixels, such at the CSS DPI is as close to + * 96dpi as possible. + */ + +using nscoord = int32_t; +inline constexpr nscoord nscoord_MAX = (1 << 30) - 1; +inline constexpr nscoord nscoord_MIN = -nscoord_MAX; + +namespace mozilla { +struct AppUnit {}; + +// Declare AppUnit as a coordinate system tag. +template <> +struct IsPixel<AppUnit> : std::true_type {}; + +namespace detail { +template <typename Rep> +struct AuCoordImpl : public gfx::IntCoordTyped<AppUnit, Rep> { + using Super = gfx::IntCoordTyped<AppUnit, Rep>; + + constexpr AuCoordImpl() : Super() {} + constexpr MOZ_IMPLICIT AuCoordImpl(Rep aValue) : Super(aValue) {} + constexpr MOZ_IMPLICIT AuCoordImpl(Super aValue) : Super(aValue) {} + + template <typename F> + static AuCoordImpl FromRound(F aValue) { + // Note: aValue is *not* rounding to nearest integer if it is negative. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 + return AuCoordImpl(std::floor(aValue + 0.5f)); + } + + template <typename F> + static AuCoordImpl FromTruncate(F aValue) { + return AuCoordImpl(std::trunc(aValue)); + } + + template <typename F> + static AuCoordImpl FromCeil(F aValue) { + return AuCoordImpl(std::ceil(aValue)); + } + + template <typename F> + static AuCoordImpl FromFloor(F aValue) { + return AuCoordImpl(std::floor(aValue)); + } + + // Note: this returns the result of the operation, without modifying the + // original value. + [[nodiscard]] AuCoordImpl ToMinMaxClamped() const { + return std::clamp(this->value, kMin, kMax); + } + + static constexpr Rep kMax = nscoord_MAX; + static constexpr Rep kMin = nscoord_MIN; +}; +} // namespace detail + +using AuCoord = detail::AuCoordImpl<int32_t>; +using AuCoord64 = detail::AuCoordImpl<int64_t>; + +} // namespace mozilla + +/** + * Divide aSpace by aN. Assign the resulting quotient to aQuotient and + * return the remainder. + */ +inline nscoord NSCoordDivRem(nscoord aSpace, size_t aN, nscoord* aQuotient) { + div_t result = div(aSpace, aN); + *aQuotient = nscoord(result.quot); + return nscoord(result.rem); +} + +inline nscoord NSCoordMulDiv(nscoord aMult1, nscoord aMult2, nscoord aDiv) { + return (int64_t(aMult1) * int64_t(aMult2) / int64_t(aDiv)); +} + +inline nscoord NSToCoordRound(float aValue) { +#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \ + !defined(__clang__) + return NS_lroundup30(aValue); +#else + return nscoord(floorf(aValue + 0.5f)); +#endif /* XP_WIN && _M_IX86 && !__GNUC__ */ +} + +inline nscoord NSToCoordRound(double aValue) { +#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \ + !defined(__clang__) + return NS_lroundup30((float)aValue); +#else + return nscoord(floor(aValue + 0.5f)); +#endif /* XP_WIN && _M_IX86 && !__GNUC__ */ +} + +inline nscoord NSToCoordRoundWithClamp(float aValue) { + // Bounds-check before converting out of float, to avoid overflow + if (aValue >= float(nscoord_MAX)) { + return nscoord_MAX; + } + if (aValue <= float(nscoord_MIN)) { + return nscoord_MIN; + } + return NSToCoordRound(aValue); +} + +inline nscoord NSToCoordRoundWithClamp(double aValue) { + // Bounds-check before converting out of double, to avoid overflow + if (aValue >= double(nscoord_MAX)) { + return nscoord_MAX; + } + if (aValue <= double(nscoord_MIN)) { + return nscoord_MIN; + } + 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) { + if (requireNotNegative) { + MOZ_ASSERT(aScale >= 0.0f, + "negative scaling factors must be handled manually"); + } + float product = aCoord * aScale; + if (requireNotNegative ? aCoord > 0 : (aCoord > 0) == (aScale > 0)) + return NSToCoordRoundWithClamp( + std::min<float>((float)nscoord_MAX, product)); + return NSToCoordRoundWithClamp(std::max<float>((float)nscoord_MIN, product)); +} + +/** + * 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. + */ +inline nscoord NSCoordSaturatingAdd(nscoord a, nscoord b) { + 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); + } +} + +/** + * 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 + */ +inline nscoord NSCoordSaturatingSubtract(nscoord a, nscoord b, + nscoord infMinusInfResult) { + if (b == nscoord_MAX) { + if (a == nscoord_MAX) { + // case (a) + return infMinusInfResult; + } else { + // case (b) + return 0; + } + } 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); + } + } +} + +inline float NSCoordToFloat(nscoord aCoord) { 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) { + // Bounds-check before converting out of float, to avoid overflow + if (aValue >= float(nscoord_MAX)) { + return nscoord_MAX; + } + if (aValue <= float(nscoord_MIN)) { + return nscoord_MIN; + } + 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) { + // Bounds-check before converting out of double, to avoid overflow + if (aValue >= nscoord_MAX) { + return nscoord_MAX; + } + if (aValue <= nscoord_MIN) { + return nscoord_MIN; + } + 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) { + // Bounds-check before converting out of float, to avoid overflow + if (aValue >= float(nscoord_MAX)) { + return nscoord_MAX; + } + if (aValue <= float(nscoord_MIN)) { + return nscoord_MIN; + } + return NSToCoordTrunc(aValue); +} + +inline nscoord NSToCoordTruncClamped(double aValue) { + // Bounds-check before converting out of double, to avoid overflow + if (aValue >= float(nscoord_MAX)) { + return nscoord_MAX; + } + if (aValue <= float(nscoord_MIN)) { + return nscoord_MIN; + } + 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; + return r; +} + +inline float NSAppUnitsToFloatPixels(nscoord aAppUnits, + float aAppUnitsPerPixel) { + return (float(aAppUnits) / aAppUnitsPerPixel); +} + +inline double NSAppUnitsToDoublePixels(nscoord aAppUnits, + double aAppUnitsPerPixel) { + return (double(aAppUnits) / aAppUnitsPerPixel); +} + +inline int32_t NSAppUnitsToIntPixels(nscoord aAppUnits, + float aAppUnitsPerPixel) { + return NSToIntRound(float(aAppUnits) / aAppUnitsPerPixel); +} + +inline float NSCoordScale(nscoord aCoord, int32_t aFromAPP, int32_t aToAPP) { + return (NSCoordToFloat(aCoord) * aToAPP) / aFromAPP; +} + +/// handy constants +#define TWIPS_PER_POINT_INT 20 +#define TWIPS_PER_POINT_FLOAT 20.0f +#define POINTS_PER_INCH_INT 72 +#define POINTS_PER_INCH_FLOAT 72.0f +#define CM_PER_INCH_FLOAT 2.54f +#define MM_PER_INCH_FLOAT 25.4f + +/* + * Twips/unit conversions + */ +inline float NSUnitsToTwips(float aValue, float aPointsPerUnit) { + return aValue * aPointsPerUnit * TWIPS_PER_POINT_FLOAT; +} + +inline float NSTwipsToUnits(float aTwips, float aUnitsPerPoint) { + return (aTwips * (aUnitsPerPoint / TWIPS_PER_POINT_FLOAT)); +} + +/// Unit conversion macros +//@{ +#define NS_POINTS_TO_TWIPS(x) NSUnitsToTwips((x), 1.0f) +#define NS_INCHES_TO_TWIPS(x) \ + NSUnitsToTwips((x), POINTS_PER_INCH_FLOAT) // 72 points per inch + +#define NS_MILLIMETERS_TO_TWIPS(x) \ + NSUnitsToTwips((x), (POINTS_PER_INCH_FLOAT * 0.03937f)) + +#define NS_POINTS_TO_INT_TWIPS(x) NSToIntRound(NS_POINTS_TO_TWIPS(x)) +#define NS_INCHES_TO_INT_TWIPS(x) NSToIntRound(NS_INCHES_TO_TWIPS(x)) + +#define NS_TWIPS_TO_INCHES(x) NSTwipsToUnits((x), 1.0f / POINTS_PER_INCH_FLOAT) + +#define NS_TWIPS_TO_MILLIMETERS(x) \ + NSTwipsToUnits((x), 1.0f / (POINTS_PER_INCH_FLOAT * 0.03937f)) +//@} + +#endif /* NSCOORD_H */ diff --git a/gfx/src/nsDeviceContext.cpp b/gfx/src/nsDeviceContext.cpp new file mode 100644 index 0000000000..28b2c34652 --- /dev/null +++ b/gfx/src/nsDeviceContext.cpp @@ -0,0 +1,472 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 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 "gfxContext.h" +#include "gfxImageSurface.h" // for gfxImageSurface +#include "gfxPoint.h" // for gfxSize +#include "gfxTextRun.h" // for gfxFontGroup +#include "mozilla/LookAndFeel.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/PrintTarget.h" +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Services.h" // for GetObserverService +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Try.h" // for MOZ_TRY +#include "mozilla/mozalloc.h" // for operator new +#include "mozilla/widget/Screen.h" // for Screen +#include "nsCRT.h" // for nsCRT +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsFont.h" // for nsFont +#include "nsFontCache.h" // for nsFontCache +#include "nsFontMetrics.h" // for nsFontMetrics +#include "nsAtom.h" // for nsAtom, NS_Atomize +#include "nsID.h" +#include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec +#include "nsLanguageAtomService.h" // for nsLanguageAtomService +#include "nsIObserver.h" // for nsIObserver, etc +#include "nsIObserverService.h" // for nsIObserverService +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW +#include "nsRect.h" // for nsRect +#include "nsServiceManagerUtils.h" // for do_GetService +#include "nsString.h" // for nsDependentString +#include "nsTArray.h" // for nsTArray, nsTArray_Impl +#include "nsThreadUtils.h" // for NS_IsMainThread +#include "mozilla/gfx/Logging.h" +#include "mozilla/widget/ScreenManager.h" // for ScreenManager + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::widget::ScreenManager; + +nsDeviceContext::nsDeviceContext() + : mWidth(0), + mHeight(0), + mAppUnitsPerDevPixel(-1), + mAppUnitsPerDevPixelAtUnitFullZoom(-1), + mAppUnitsPerPhysicalInch(-1), + mFullZoom(1.0f), + mPrintingScale(1.0f), + mPrintingTranslate(gfxPoint(0, 0)), + mIsCurrentlyPrintingDoc(false) { + MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread"); +} + +nsDeviceContext::~nsDeviceContext() = default; + +void nsDeviceContext::SetDPI() { + float dpi; + + // Use the printing DC to determine DPI values, if we have one. + if (mDeviceContextSpec) { + dpi = mDeviceContextSpec->GetDPI(); + mPrintingScale = mDeviceContextSpec->GetPrintingScale(); + mPrintingTranslate = mDeviceContextSpec->GetPrintingTranslate(); + mAppUnitsPerDevPixelAtUnitFullZoom = + NS_lround((AppUnitsPerCSSPixel() * 96) / dpi); + } else { + // 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 = StaticPrefs::layout_css_dpi(); + if (prefDPI > 0) { + dpi = prefDPI; + } else if (mWidget) { + dpi = mWidget->GetDPI(); + MOZ_ASSERT(dpi > 0); + if (prefDPI < 0) { + dpi = std::max(96.0f, dpi); + } + } else { + dpi = 96.0f; + } + + CSSToLayoutDeviceScale scale = + mWidget ? mWidget->GetDefaultScale() : CSSToLayoutDeviceScale(1.0); + MOZ_ASSERT(scale.scale > 0.0); + mAppUnitsPerDevPixelAtUnitFullZoom = + std::max(1, NS_lround(AppUnitsPerCSSPixel() / scale.scale)); + } + + NS_ASSERTION(dpi != -1.0, "no dpi set"); + + mAppUnitsPerPhysicalInch = + NS_lround(dpi * mAppUnitsPerDevPixelAtUnitFullZoom); + UpdateAppUnitsForFullZoom(); +} + +void nsDeviceContext::Init(nsIWidget* aWidget) { + if (mIsInitialized && mWidget == aWidget) { + return; + } + + // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument + // re-initializes nsDeviceContext objects. We can only assert in + // InitForPrinting (below). + mIsInitialized = true; + + mWidget = aWidget; + SetDPI(); +} + +// XXX This is only for printing. We should make that obvious in the name. +UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContext() { + return CreateRenderingContextCommon(/* not a reference context */ false); +} + +UniquePtr<gfxContext> nsDeviceContext::CreateReferenceRenderingContext() { + return CreateRenderingContextCommon(/* a reference context */ true); +} + +UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContextCommon( + bool aWantReferenceContext) { + MOZ_ASSERT(IsPrinterContext()); + MOZ_ASSERT(mWidth > 0 && mHeight > 0); + + if (NS_WARN_IF(!mPrintTarget)) { + // Printing canceled already. + return nullptr; + } + + RefPtr<gfx::DrawTarget> dt; + if (aWantReferenceContext) { + dt = mPrintTarget->GetReferenceDrawTarget(); + } else { + // This will be null if printing a page from the parent process. + RefPtr<DrawEventRecorder> recorder; + mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder)); + dt = mPrintTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote << "Failed to create draw target in device context sized " + << mWidth << "x" << mHeight << " and pointer " + << hexa(mPrintTarget); + return nullptr; + } + + dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr); + + auto pContext = MakeUnique<gfxContext>(dt); + + gfxMatrix transform; + transform.PreTranslate(mPrintingTranslate); + transform.PreScale(mPrintingScale, mPrintingScale); + pContext->SetMatrixDouble(transform); + return pContext; +} + +uint32_t nsDeviceContext::GetDepth() { + RefPtr<widget::Screen> screen = FindScreen(); + if (!screen) { + ScreenManager& screenManager = ScreenManager::GetSingleton(); + screen = screenManager.GetPrimaryScreen(); + MOZ_ASSERT(screen); + } + int32_t depth = 0; + screen->GetColorDepth(&depth); + return uint32_t(depth); +} + +dom::ScreenColorGamut nsDeviceContext::GetColorGamut() { + RefPtr<widget::Screen> screen = FindScreen(); + if (!screen) { + auto& screenManager = ScreenManager::GetSingleton(); + screen = screenManager.GetPrimaryScreen(); + MOZ_ASSERT(screen); + } + dom::ScreenColorGamut colorGamut; + screen->GetColorGamut(&colorGamut); + return colorGamut; +} + +hal::ScreenOrientation nsDeviceContext::GetScreenOrientationType() { + RefPtr<widget::Screen> screen = FindScreen(); + if (!screen) { + auto& screenManager = ScreenManager::GetSingleton(); + screen = screenManager.GetPrimaryScreen(); + MOZ_ASSERT(screen); + } + return screen->GetOrientationType(); +} + +uint16_t nsDeviceContext::GetScreenOrientationAngle() { + RefPtr<widget::Screen> screen = FindScreen(); + if (!screen) { + auto& screenManager = ScreenManager::GetSingleton(); + screen = screenManager.GetPrimaryScreen(); + MOZ_ASSERT(screen); + } + return screen->GetOrientationAngle(); +} + +nsresult nsDeviceContext::GetDeviceSurfaceDimensions(nscoord& aWidth, + nscoord& aHeight) { + if (IsPrinterContext()) { + aWidth = mWidth; + aHeight = mHeight; + } else { + nsRect area; + ComputeFullAreaUsingScreen(&area); + aWidth = area.Width(); + aHeight = area.Height(); + } + + return NS_OK; +} + +nsresult nsDeviceContext::GetRect(nsRect& aRect) { + if (IsPrinterContext()) { + aRect.SetRect(0, 0, mWidth, mHeight); + } else + ComputeFullAreaUsingScreen(&aRect); + + return NS_OK; +} + +nsresult nsDeviceContext::GetClientRect(nsRect& aRect) { + if (IsPrinterContext()) { + aRect.SetRect(0, 0, mWidth, mHeight); + } else + ComputeClientRectUsingScreen(&aRect); + + return NS_OK; +} + +nsresult nsDeviceContext::InitForPrinting(nsIDeviceContextSpec* aDevice) { + NS_ENSURE_ARG_POINTER(aDevice); + + MOZ_ASSERT(!mIsInitialized, + "Only initialize once, immediately after construction"); + + // We don't set mIsInitialized here. The Init() call below does that. + + mPrintTarget = aDevice->MakePrintTarget(); + if (!mPrintTarget) { + return NS_ERROR_FAILURE; + } + + mDeviceContextSpec = aDevice; + + Init(nullptr); + + if (!CalcPrintingSize()) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsDeviceContext::BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc, + "Mismatched BeginDocument/EndDocument calls"); + AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, + "nsDeviceContext::BeginDocument"_ns); + + nsresult rv = mPrintTarget->BeginPrinting(aTitle, aPrintToFileName, + aStartPage, aEndPage); + + if (NS_SUCCEEDED(rv)) { + if (mDeviceContextSpec) { + rv = mDeviceContextSpec->BeginDocument(aTitle, aPrintToFileName, + aStartPage, aEndPage); + } + mIsCurrentlyPrintingDoc = true; + } + + // Warn about any failure (except user cancelling): + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_ABORT, + "nsDeviceContext::BeginDocument failed"); + + return rv; +} + +RefPtr<PrintEndDocumentPromise> nsDeviceContext::EndDocument() { + MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc, + "Mismatched BeginDocument/EndDocument calls"); + MOZ_DIAGNOSTIC_ASSERT(mPrintTarget); + AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, + "nsDeviceContext::EndDocument"_ns); + + mIsCurrentlyPrintingDoc = false; + + if (mPrintTarget) { + auto result = mPrintTarget->EndPrinting(); + if (NS_FAILED(result)) { + return PrintEndDocumentPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + mPrintTarget->Finish(); + mPrintTarget = nullptr; + } + + if (mDeviceContextSpec) { + return mDeviceContextSpec->EndDocument(); + } + + return PrintEndDocumentPromise::CreateAndResolve(true, __func__); +} + +nsresult nsDeviceContext::AbortDocument() { + MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc, + "Mismatched BeginDocument/EndDocument calls"); + AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, + "nsDeviceContext::AbortDocument"_ns); + + nsresult rv = mPrintTarget->AbortPrinting(); + mIsCurrentlyPrintingDoc = false; + + if (mDeviceContextSpec) { + Unused << mDeviceContextSpec->EndDocument(); + } + + mPrintTarget = nullptr; + + return rv; +} + +nsresult nsDeviceContext::BeginPage(const IntSize& aSizeInPoints) { + MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc || mPrintTarget, + "What nulled out our print target while printing?"); + AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, + "nsDeviceContext::BeginPage"_ns); + + if (mDeviceContextSpec) { + MOZ_TRY(mDeviceContextSpec->BeginPage(aSizeInPoints)); + } + if (mPrintTarget) { + MOZ_TRY(mPrintTarget->BeginPage(aSizeInPoints)); + } + return NS_OK; +} + +nsresult nsDeviceContext::EndPage() { + MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc || mPrintTarget, + "What nulled out our print target while printing?"); + AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, + "nsDeviceContext::EndPage"_ns); + + if (mPrintTarget) { + MOZ_TRY(mPrintTarget->EndPage()); + } + if (mDeviceContextSpec) { + MOZ_TRY(mDeviceContextSpec->EndPage()); + } + return NS_OK; +} + +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. + if (RefPtr<widget::Screen> screen = FindScreen()) { + *outRect = LayoutDeviceIntRect::ToAppUnits(screen->GetAvailRect(), + 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. + if (RefPtr<widget::Screen> screen = FindScreen()) { + *outRect = LayoutDeviceIntRect::ToAppUnits(screen->GetRect(), + AppUnitsPerDevPixel()); + mWidth = outRect->Width(); + mHeight = outRect->Height(); + } +} + +// +// FindScreen +// +// Determines which screen intersects the largest area of the given surface. +// +already_AddRefed<widget::Screen> nsDeviceContext::FindScreen() { + if (!mWidget) { + return nullptr; + } + + CheckDPIChange(); + + if (RefPtr<widget::Screen> screen = mWidget->GetWidgetScreen()) { + return screen.forget(); + } + + ScreenManager& screenManager = ScreenManager::GetSingleton(); + return screenManager.GetPrimaryScreen(); +} + +bool nsDeviceContext::CalcPrintingSize() { + gfxSize size(mPrintTarget->GetSize()); + // For printing, CSS inches and physical inches are identical + // so it doesn't matter which we use here + mWidth = NSToCoordRound(size.width * AppUnitsPerPhysicalInch() / + POINTS_PER_INCH_FLOAT); + mHeight = NSToCoordRound(size.height * AppUnitsPerPhysicalInch() / + POINTS_PER_INCH_FLOAT); + + return (mWidth > 0 && mHeight > 0); +} + +bool nsDeviceContext::CheckDPIChange() { + int32_t oldDevPixels = mAppUnitsPerDevPixelAtUnitFullZoom; + int32_t oldInches = mAppUnitsPerPhysicalInch; + + SetDPI(); + + return oldDevPixels != mAppUnitsPerDevPixelAtUnitFullZoom || + oldInches != mAppUnitsPerPhysicalInch; +} + +bool nsDeviceContext::SetFullZoom(float aScale) { + if (aScale <= 0) { + MOZ_ASSERT_UNREACHABLE("Invalid full zoom value"); + return false; + } + int32_t oldAppUnitsPerDevPixel = mAppUnitsPerDevPixel; + mFullZoom = aScale; + UpdateAppUnitsForFullZoom(); + return oldAppUnitsPerDevPixel != mAppUnitsPerDevPixel; +} + +static int32_t ApplyFullZoom(int32_t aUnzoomedAppUnits, float aFullZoom) { + if (aFullZoom == 1.0f) { + return aUnzoomedAppUnits; + } + return std::max(1, NSToIntRound(float(aUnzoomedAppUnits) / aFullZoom)); +} + +int32_t nsDeviceContext::AppUnitsPerDevPixelInTopLevelChromePage() const { + // The only zoom that applies to chrome pages is the system zoom, if any. + return ApplyFullZoom(mAppUnitsPerDevPixelAtUnitFullZoom, + LookAndFeel::SystemZoomSettings().mFullZoom); +} + +void nsDeviceContext::UpdateAppUnitsForFullZoom() { + mAppUnitsPerDevPixel = + ApplyFullZoom(mAppUnitsPerDevPixelAtUnitFullZoom, mFullZoom); + // adjust mFullZoom to reflect appunit rounding + mFullZoom = float(mAppUnitsPerDevPixelAtUnitFullZoom) / mAppUnitsPerDevPixel; +} + +DesktopToLayoutDeviceScale nsDeviceContext::GetDesktopToDeviceScale() { + if (RefPtr<widget::Screen> screen = FindScreen()) { + return screen->GetDesktopToLayoutDeviceScale(); + } + return DesktopToLayoutDeviceScale(1.0); +} diff --git a/gfx/src/nsDeviceContext.h b/gfx/src/nsDeviceContext.h new file mode 100644 index 0000000000..5238c1f71d --- /dev/null +++ b/gfx/src/nsDeviceContext.h @@ -0,0 +1,311 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NS_DEVICECONTEXT_H_ +#define _NS_DEVICECONTEXT_H_ + +#include <stdint.h> // for uint32_t +#include <sys/types.h> // for int32_t +#include "gfxTypes.h" // for gfxFloat +#include "gfxFont.h" // for gfxFont::Orientation +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/RefPtr.h" // for RefPtr +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsCoord.h" // for nscoord +#include "nsError.h" // for nsresult +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsMathUtils.h" // for NS_round +#include "nscore.h" // for char16_t, nsAString +#include "mozilla/AppUnits.h" // for AppUnits +#include "nsFontMetrics.h" // for nsFontMetrics::Params +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/PrintTarget.h" // for PrintTarget::PageDoneCallback +#include "mozilla/gfx/PrintPromise.h" + +class gfxContext; +class gfxTextPerfMetrics; +class gfxUserFontSet; +struct nsFont; +class nsAtom; +class nsIDeviceContextSpec; +class nsIScreen; +class nsIScreenManager; +class nsIWidget; +struct nsRect; + +namespace mozilla { +namespace dom { +enum class ScreenColorGamut : uint8_t; +} // namespace dom +namespace hal { +enum class ScreenOrientation : uint32_t; +} // namespace hal +namespace widget { +class Screen; +} // namespace widget +} // namespace mozilla + +class nsDeviceContext final { + public: + using IntSize = mozilla::gfx::IntSize; + using PrintTarget = mozilla::gfx::PrintTarget; + + nsDeviceContext(); + + NS_INLINE_DECL_REFCOUNTING(nsDeviceContext) + + /** + * Initialize the device context from a widget + * @param aWidget a widget to initialize the device context from + */ + void 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) + */ + mozilla::UniquePtr<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. + */ + mozilla::UniquePtr<gfxContext> CreateReferenceRenderingContext(); + + /** + * Gets the number of app units in one device pixel; this number + * is usually a factor of AppUnitsPerCSSPixel(), although that is + * not guaranteed. + */ + int32_t AppUnitsPerDevPixel() const { return mAppUnitsPerDevPixel; } + + /** + * Convert device pixels which is used for gfx/thebes to nearest + * (rounded) app units + */ + nscoord GfxUnitsToAppUnits(gfxFloat aGfxUnits) const { + return nscoord(NS_round(aGfxUnits * AppUnitsPerDevPixel())); + } + + /** + * Convert app units to device pixels which is used for gfx/thebes. + */ + gfxFloat AppUnitsToGfxUnits(nscoord aAppUnits) const { + return gfxFloat(aAppUnits) / AppUnitsPerDevPixel(); + } + + /** + * Gets the number of app units in one physical inch; this is the + * device's DPI times AppUnitsPerDevPixel(). + */ + int32_t AppUnitsPerPhysicalInch() const { return mAppUnitsPerPhysicalInch; } + + /** + * Get the ratio of app units to dev pixels that would be used at unit + * (100%) full zoom. + */ + int32_t AppUnitsPerDevPixelAtUnitFullZoom() const { + return mAppUnitsPerDevPixelAtUnitFullZoom; + } + + /** + * Get the ratio of app units to dev pixels that would be used in a top-level + * chrome page such as browser.xhtml. + */ + int32_t AppUnitsPerDevPixelInTopLevelChromePage() const; + + /** + * Return the bit depth of the device. + */ + uint32_t GetDepth(); + + /** + * Return the color gamut of the device. + */ + mozilla::dom::ScreenColorGamut GetColorGamut(); + + /** + * Return the orientation type of the device. + * If not screen device, return primary screen's value + */ + mozilla::hal::ScreenOrientation GetScreenOrientationType(); + + /** + * Return the orientation angle of the device. + * If not screen device, return primary screen's value + */ + uint16_t GetScreenOrientationAngle(); + + /** + * Get the size of the displayable area of the output device + * in app units. + * @param aWidth out parameter for width + * @param aHeight out parameter for height + * @return error status + */ + nsresult GetDeviceSurfaceDimensions(nscoord& aWidth, nscoord& aHeight); + + /** + * Get the size of the content area of the output device in app + * units. This corresponds on a screen device, for instance, to + * the entire screen. + * @param aRect out parameter for full rect. Position (x,y) will + * be (0,0) or relative to the primary monitor if + * this is not the primary. + * @return error status + */ + nsresult GetRect(nsRect& aRect); + + /** + * Get the size of the content area of the output device in app + * units. This corresponds on a screen device, for instance, to + * the area reported by GetDeviceSurfaceDimensions, minus the + * taskbar (Windows) or menubar (Macintosh). + * @param aRect out parameter for client rect. Position (x,y) will + * be (0,0) adjusted for any upper/left non-client + * space if present or relative to the primary + * monitor if this is not the primary. + * @return error status + */ + nsresult GetClientRect(nsRect& aRect); + + /** + * Returns true if we're currently between BeginDocument() and + * EndDocument() calls. + */ + bool IsCurrentlyPrintingDocument() const { return mIsCurrentlyPrintingDoc; } + + /** + * Inform the output device that output of a document is beginning + * Used for print related device contexts. Must be matched 1:1 with + * EndDocument() or AbortDocument(). + * + * @param aTitle - title of Document + * @param aPrintToFileName - name of file to print to, if empty then don't + * print to file + * @param aStartPage - starting page number (must be greater than zero) + * @param aEndPage - ending page number (must be less than or + * equal to number of pages) + * + * @return error status + */ + nsresult BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, int32_t aStartPage, + int32_t aEndPage); + + /** + * Inform the output device that output of a document is ending. + * Used for print related device contexts. Must be matched 1:1 with + * BeginDocument() + * @return Promise that can be chained once the operation is complete. + */ + RefPtr<mozilla::gfx::PrintEndDocumentPromise> 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. + * + * @param aSizeInPoints - The physical dimensions of the page in points. + * Currently only supported (used) by print-to-PDF + * print targets, and then only to switch the + * orientation for a specific page (arbitrary page + * sizes are not supported by the Core Graphics print- + * to-PDF APIs, for example). + * + * @return error status + */ + nsresult BeginPage(const IntSize& aSizeInPoints); + + /** + * 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. + * @return whether there was actually a change in the DPI (whether + * AppUnitsPerDevPixel() or AppUnitsPerPhysicalInch() + * changed) + */ + bool CheckDPIChange(); + + /** + * 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() const { return !!mPrintTarget; } + + mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale(); + + private: + // Private destructor, to discourage deletion outside of Release(): + ~nsDeviceContext(); + + /** + * Implementation shared by CreateRenderingContext and + * CreateReferenceRenderingContext. + */ + mozilla::UniquePtr<gfxContext> CreateRenderingContextCommon( + bool aWantReferenceContext); + + void SetDPI(); + void ComputeClientRectUsingScreen(nsRect* outRect); + void ComputeFullAreaUsingScreen(nsRect* outRect); + already_AddRefed<mozilla::widget::Screen> FindScreen(); + + // Return false if the surface is not right + bool CalcPrintingSize(); + void UpdateAppUnitsForFullZoom(); + + nscoord mWidth; + nscoord mHeight; + int32_t mAppUnitsPerDevPixel; + int32_t mAppUnitsPerDevPixelAtUnitFullZoom; + int32_t mAppUnitsPerPhysicalInch; + float mFullZoom; + float mPrintingScale; + gfxPoint mPrintingTranslate; + + nsCOMPtr<nsIWidget> mWidget; + nsCOMPtr<nsIDeviceContextSpec> mDeviceContextSpec; + RefPtr<PrintTarget> mPrintTarget; + bool mIsCurrentlyPrintingDoc; + bool mIsInitialized = false; +}; + +#endif /* _NS_DEVICECONTEXT_H_ */ diff --git a/gfx/src/nsFont.cpp b/gfx/src/nsFont.cpp new file mode 100644 index 0000000000..baec648ddb --- /dev/null +++ b/gfx/src/nsFont.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsFont.h" +#include "gfxFont.h" // for gfxFontStyle +#include "gfxFontFeatures.h" // for gfxFontFeature, etc +#include "gfxFontUtils.h" // for TRUETYPE_TAG +#include "mozilla/ServoStyleConstsInlines.h" +#include "nsCRT.h" // for nsCRT +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupports.h" +#include "nsUnicharUtils.h" +#include "nscore.h" // for char16_t +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla; + +nsFont::nsFont(const StyleFontFamily& aFamily, mozilla::Length aSize) + : family(aFamily), size(aSize) {} + +nsFont::nsFont(StyleGenericFontFamily aGenericType, mozilla::Length aSize) + : family(*Servo_FontFamily_Generic(aGenericType)), size(aSize) {} + +nsFont::nsFont(const nsFont& aOther) = default; + +nsFont::~nsFont() = default; + +nsFont& nsFont::operator=(const nsFont&) = default; + +bool nsFont::Equals(const nsFont& aOther) const { + return CalcDifference(aOther) == MaxDifference::eNone; +} + +nsFont::MaxDifference nsFont::CalcDifference(const nsFont& aOther) const { + if ((style != aOther.style) || (weight != aOther.weight) || + (stretch != aOther.stretch) || (size != aOther.size) || + (sizeAdjust != aOther.sizeAdjust) || (family != aOther.family) || + (kerning != aOther.kerning) || (opticalSizing != aOther.opticalSizing) || + (synthesisWeight != aOther.synthesisWeight) || + (synthesisStyle != aOther.synthesisStyle) || + (synthesisSmallCaps != aOther.synthesisSmallCaps) || + (synthesisPosition != aOther.synthesisPosition) || + (fontFeatureSettings != aOther.fontFeatureSettings) || + (fontVariationSettings != aOther.fontVariationSettings) || + (languageOverride != aOther.languageOverride) || + (variantAlternates != aOther.variantAlternates) || + (variantCaps != aOther.variantCaps) || + (variantEastAsian != aOther.variantEastAsian) || + (variantLigatures != aOther.variantLigatures) || + (variantNumeric != aOther.variantNumeric) || + (variantPosition != aOther.variantPosition) || + (variantWidth != aOther.variantWidth) || + (variantEmoji != aOther.variantEmoji)) { + return MaxDifference::eLayoutAffecting; + } + + if (smoothing != aOther.smoothing) { + return MaxDifference::eVisual; + } + + return MaxDifference::eNone; +} + +// mapping from bitflag to font feature tag/value pair +// +// these need to be kept in sync with the constants listed +// in gfxFontConstants.h (e.g. NS_FONT_VARIANT_EAST_ASIAN_JIS78) + +// NS_FONT_VARIANT_EAST_ASIAN_xxx values +const gfxFontFeature eastAsianDefaults[] = { + {TRUETYPE_TAG('j', 'p', '7', '8'), 1}, + {TRUETYPE_TAG('j', 'p', '8', '3'), 1}, + {TRUETYPE_TAG('j', 'p', '9', '0'), 1}, + {TRUETYPE_TAG('j', 'p', '0', '4'), 1}, + {TRUETYPE_TAG('s', 'm', 'p', 'l'), 1}, + {TRUETYPE_TAG('t', 'r', 'a', 'd'), 1}, + {TRUETYPE_TAG('f', 'w', 'i', 'd'), 1}, + {TRUETYPE_TAG('p', 'w', 'i', 'd'), 1}, + {TRUETYPE_TAG('r', 'u', 'b', 'y'), 1}}; + +static_assert(MOZ_ARRAY_LENGTH(eastAsianDefaults) == + NS_FONT_VARIANT_EAST_ASIAN_COUNT, + "eastAsianDefaults[] should be correct"); + +// NS_FONT_VARIANT_LIGATURES_xxx values +const gfxFontFeature ligDefaults[] = { + {TRUETYPE_TAG('l', 'i', 'g', 'a'), 0}, // none value means all off + {TRUETYPE_TAG('l', 'i', 'g', 'a'), 1}, + {TRUETYPE_TAG('l', 'i', 'g', 'a'), 0}, + {TRUETYPE_TAG('d', 'l', 'i', 'g'), 1}, + {TRUETYPE_TAG('d', 'l', 'i', 'g'), 0}, + {TRUETYPE_TAG('h', 'l', 'i', 'g'), 1}, + {TRUETYPE_TAG('h', 'l', 'i', 'g'), 0}, + {TRUETYPE_TAG('c', 'a', 'l', 't'), 1}, + {TRUETYPE_TAG('c', 'a', 'l', 't'), 0}}; + +static_assert(MOZ_ARRAY_LENGTH(ligDefaults) == NS_FONT_VARIANT_LIGATURES_COUNT, + "ligDefaults[] should be correct"); + +// NS_FONT_VARIANT_NUMERIC_xxx values +const gfxFontFeature numericDefaults[] = { + {TRUETYPE_TAG('l', 'n', 'u', 'm'), 1}, + {TRUETYPE_TAG('o', 'n', 'u', 'm'), 1}, + {TRUETYPE_TAG('p', 'n', 'u', 'm'), 1}, + {TRUETYPE_TAG('t', 'n', 'u', 'm'), 1}, + {TRUETYPE_TAG('f', 'r', 'a', 'c'), 1}, + {TRUETYPE_TAG('a', 'f', 'r', 'c'), 1}, + {TRUETYPE_TAG('z', 'e', 'r', 'o'), 1}, + {TRUETYPE_TAG('o', 'r', 'd', 'n'), 1}}; + +static_assert(MOZ_ARRAY_LENGTH(numericDefaults) == + NS_FONT_VARIANT_NUMERIC_COUNT, + "numericDefaults[] should be correct"); + +static void AddFontFeaturesBitmask(uint32_t aValue, uint32_t aMin, + uint32_t aMax, + const gfxFontFeature aFeatureDefaults[], + nsTArray<gfxFontFeature>& aFeaturesOut) + +{ + uint32_t i, m; + + for (i = 0, m = aMin; m <= aMax; i++, m <<= 1) { + if (m & aValue) { + const gfxFontFeature& feature = aFeatureDefaults[i]; + aFeaturesOut.AppendElement(feature); + } + } +} + +static uint32_t FontFeatureTagForVariantWidth(uint32_t aVariantWidth) { + switch (aVariantWidth) { + case NS_FONT_VARIANT_WIDTH_FULL: + return TRUETYPE_TAG('f', 'w', 'i', 'd'); + case NS_FONT_VARIANT_WIDTH_HALF: + return TRUETYPE_TAG('h', 'w', 'i', 'd'); + case NS_FONT_VARIANT_WIDTH_THIRD: + return TRUETYPE_TAG('t', 'w', 'i', 'd'); + case NS_FONT_VARIANT_WIDTH_QUARTER: + return TRUETYPE_TAG('q', 'w', 'i', 'd'); + default: + return 0; + } +} + +void nsFont::AddFontFeaturesToStyle(gfxFontStyle* aStyle, + bool aVertical) const { + // add in font-variant features + gfxFontFeature setting; + + // -- kerning + setting.mTag = aVertical ? TRUETYPE_TAG('v', 'k', 'r', 'n') + : TRUETYPE_TAG('k', 'e', 'r', 'n'); + switch (kerning) { + case NS_FONT_KERNING_NONE: + setting.mValue = 0; + aStyle->featureSettings.AppendElement(setting); + break; + case NS_FONT_KERNING_NORMAL: + setting.mValue = 1; + aStyle->featureSettings.AppendElement(setting); + break; + default: + // auto case implies use user agent default + break; + } + + // -- alternates + // + // NOTE(emilio): We handle historical-forms here because it doesn't depend on + // other values set by @font-face and thus may be less expensive to do here + // than after font-matching. + for (auto& alternate : variantAlternates.AsSpan()) { + if (alternate.IsHistoricalForms()) { + setting.mValue = 1; + setting.mTag = TRUETYPE_TAG('h', 'i', 's', 't'); + aStyle->featureSettings.AppendElement(setting); + break; + } + } + + // -- copy font-specific alternate info into style + // (this will be resolved after font-matching occurs) + aStyle->variantAlternates = variantAlternates; + + // -- caps + aStyle->variantCaps = variantCaps; + + // -- east-asian + if (variantEastAsian) { + AddFontFeaturesBitmask(variantEastAsian, NS_FONT_VARIANT_EAST_ASIAN_JIS78, + NS_FONT_VARIANT_EAST_ASIAN_RUBY, eastAsianDefaults, + aStyle->featureSettings); + } + + // -- ligatures + if (variantLigatures) { + AddFontFeaturesBitmask(variantLigatures, NS_FONT_VARIANT_LIGATURES_NONE, + NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL, ligDefaults, + aStyle->featureSettings); + + if (variantLigatures & NS_FONT_VARIANT_LIGATURES_COMMON) { + // liga already enabled, need to enable clig also + setting.mTag = TRUETYPE_TAG('c', 'l', 'i', 'g'); + setting.mValue = 1; + aStyle->featureSettings.AppendElement(setting); + } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NO_COMMON) { + // liga already disabled, need to disable clig also + setting.mTag = TRUETYPE_TAG('c', 'l', 'i', 'g'); + setting.mValue = 0; + aStyle->featureSettings.AppendElement(setting); + } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NONE) { + // liga already disabled, need to disable dlig, hlig, calt, clig + setting.mValue = 0; + setting.mTag = TRUETYPE_TAG('d', 'l', 'i', 'g'); + aStyle->featureSettings.AppendElement(setting); + setting.mTag = TRUETYPE_TAG('h', 'l', 'i', 'g'); + aStyle->featureSettings.AppendElement(setting); + setting.mTag = TRUETYPE_TAG('c', 'a', 'l', 't'); + aStyle->featureSettings.AppendElement(setting); + setting.mTag = TRUETYPE_TAG('c', 'l', 'i', 'g'); + aStyle->featureSettings.AppendElement(setting); + } + } + + // -- numeric + if (variantNumeric) { + AddFontFeaturesBitmask(variantNumeric, NS_FONT_VARIANT_NUMERIC_LINING, + NS_FONT_VARIANT_NUMERIC_ORDINAL, numericDefaults, + aStyle->featureSettings); + } + + // -- position + aStyle->variantSubSuper = variantPosition; + + // -- width + setting.mTag = FontFeatureTagForVariantWidth(variantWidth); + if (setting.mTag) { + setting.mValue = 1; + aStyle->featureSettings.AppendElement(setting); + } + + // indicate common-path case when neither variantCaps or variantSubSuper are + // set + aStyle->noFallbackVariantFeatures = + (aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL) && + (variantPosition == NS_FONT_VARIANT_POSITION_NORMAL); + + // If the feature list is not empty, we insert a "fake" feature with tag=0 + // as delimiter between the above "high-level" features from font-variant-* + // etc and those coming from the low-level font-feature-settings property. + // This will allow us to distinguish high- and low-level settings when it + // comes to potentially disabling ligatures because of letter-spacing. + if (!aStyle->featureSettings.IsEmpty() || !fontFeatureSettings.IsEmpty()) { + aStyle->featureSettings.AppendElement(gfxFontFeature{0, 0}); + } + + // add in features from font-feature-settings + aStyle->featureSettings.AppendElements(fontFeatureSettings); + + // enable grayscale antialiasing for text + if (smoothing == NS_FONT_SMOOTHING_GRAYSCALE) { + aStyle->useGrayscaleAntialiasing = true; + } +} + +void nsFont::AddFontVariationsToStyle(gfxFontStyle* aStyle) const { + // If auto optical sizing is enabled, and if there's no 'opsz' axis in + // fontVariationSettings, then set the automatic value on the style. + class VariationTagComparator { + public: + bool Equals(const gfxFontVariation& aVariation, uint32_t aTag) const { + return aVariation.mTag == aTag; + } + }; + const uint32_t kTagOpsz = TRUETYPE_TAG('o', 'p', 's', 'z'); + if (opticalSizing == NS_FONT_OPTICAL_SIZING_AUTO && + !fontVariationSettings.Contains(kTagOpsz, VariationTagComparator())) { + aStyle->autoOpticalSize = size.ToCSSPixels(); + } + + // Add in arbitrary values from font-variation-settings + aStyle->variationSettings.AppendElements(fontVariationSettings); +} diff --git a/gfx/src/nsFont.h b/gfx/src/nsFont.h new file mode 100644 index 0000000000..1557896663 --- /dev/null +++ b/gfx/src/nsFont.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFont_h___ +#define nsFont_h___ + +#include <cstdint> +#include "gfxFontConstants.h" // for NS_FONT_KERNING_AUTO, etc +#include "gfxFontVariations.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/StyleColorInlines.h" // for StyleAbsoluteColor +#include "nsTArray.h" // for nsTArray + +struct gfxFontFeature; +struct gfxFontStyle; + +// Font structure. +struct nsFont final { + typedef mozilla::FontStretch FontStretch; + typedef mozilla::FontSlantStyle FontSlantStyle; + typedef mozilla::FontWeight FontWeight; + + // List of font families, either named or generic. + mozilla::StyleFontFamily family; + + // Font features from CSS font-feature-settings + CopyableTArray<gfxFontFeature> fontFeatureSettings; + + // Font variations from CSS font-variation-settings + CopyableTArray<gfxFontVariation> fontVariationSettings; + + // The logical size of the font, in CSS Pixels + mozilla::NonNegativeLength size{0}; + + // The aspect-value (ie., the ratio actualsize:actualxheight) that any + // actual physical font created from this font structure must have when + // rendering or measuring a string. The value must be nonnegative. + mozilla::StyleFontSizeAdjust sizeAdjust = + mozilla::StyleFontSizeAdjust::None(); + + // Language system tag, to override document language; + // this is an OpenType "language system" tag represented as a 32-bit integer + // (see http://www.microsoft.com/typography/otspec/languagetags.htm). + uint32_t languageOverride = 0; + + // Font-selection/rendering properties corresponding to CSS font-style, + // font-weight, font-stretch. These are all 16-bit types. + FontSlantStyle style = FontSlantStyle::NORMAL; + FontWeight weight = FontWeight::NORMAL; + FontStretch stretch = FontStretch::NORMAL; + + // Some font-variant-alternates property values require + // font-specific settings defined via @font-feature-values rules. + // These are resolved *after* font matching occurs. + mozilla::StyleFontVariantAlternates variantAlternates; + + // Variant subproperties + uint16_t variantLigatures = NS_FONT_VARIANT_LIGATURES_NORMAL; + uint16_t variantEastAsian = NS_FONT_VARIANT_EAST_ASIAN_NORMAL; + + uint8_t variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; + uint8_t variantNumeric = NS_FONT_VARIANT_NUMERIC_NORMAL; + uint8_t variantPosition = NS_FONT_VARIANT_POSITION_NORMAL; + uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL; + StyleFontVariantEmoji variantEmoji = StyleFontVariantEmoji::Normal; + + // Smoothing - controls subpixel-antialiasing (currently OSX only) + uint8_t smoothing = NS_FONT_SMOOTHING_AUTO; + + // Kerning + uint8_t kerning = NS_FONT_KERNING_AUTO; + + // Whether automatic optical sizing should be applied to variation fonts + // that include an 'opsz' axis + uint8_t opticalSizing = NS_FONT_OPTICAL_SIZING_AUTO; + + // Synthesis setting, controls use of fake bolding/italics/small-caps + mozilla::StyleFontSynthesis synthesisWeight = + mozilla::StyleFontSynthesis::Auto; + mozilla::StyleFontSynthesis synthesisStyle = + mozilla::StyleFontSynthesis::Auto; + mozilla::StyleFontSynthesis synthesisSmallCaps = + mozilla::StyleFontSynthesis::Auto; + mozilla::StyleFontSynthesis synthesisPosition = + mozilla::StyleFontSynthesis::Auto; + + // initialize the font with a fontlist + nsFont(const mozilla::StyleFontFamily&, mozilla::Length aSize); + + // initialize the font with a single generic + nsFont(mozilla::StyleGenericFontFamily, mozilla::Length aSize); + + // Make a copy of the given font + nsFont(const nsFont& aFont); + + // leave members uninitialized + nsFont() = default; + ~nsFont(); + + bool operator==(const nsFont& aOther) const { return Equals(aOther); } + + bool operator!=(const nsFont& aOther) const { return !Equals(aOther); } + + bool Equals(const nsFont& aOther) const; + + nsFont& operator=(const nsFont& aOther); + + enum class MaxDifference : uint8_t { eNone, eVisual, eLayoutAffecting }; + + MaxDifference CalcDifference(const nsFont& aOther) const; + + // Add featureSettings into style + void AddFontFeaturesToStyle(gfxFontStyle* aStyle, bool aVertical) const; + + void AddFontVariationsToStyle(gfxFontStyle* aStyle) const; +}; + +#define NS_FONT_VARIANT_NORMAL 0 +#define NS_FONT_VARIANT_SMALL_CAPS 1 + +#endif /* nsFont_h___ */ diff --git a/gfx/src/nsFontCache.cpp b/gfx/src/nsFontCache.cpp new file mode 100644 index 0000000000..856ae3a5c7 --- /dev/null +++ b/gfx/src/nsFontCache.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsFontCache.h" + +#include "gfxTextRun.h" +#include "mozilla/Services.h" +#include "mozilla/ServoUtils.h" +#include "nsCRT.h" + +#include "mozilla/dom/Document.h" +#include "nsPresContext.h" + +using mozilla::services::GetObserverService; + +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(nsPresContext* aContext) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + mContext = aContext; + // register as a memory-pressure observer to free font resources + // in low-memory situations. + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + obs->AddObserver(this, "memory-pressure", false); + } + + mLocaleLanguage = nsLanguageAtomService::GetService()->GetLocaleLanguage(); + if (!mLocaleLanguage) { + mLocaleLanguage = NS_Atomize("x-western"); + } +} + +void nsFontCache::Destroy() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obs = GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "memory-pressure"); + } + Flush(); +} + +NS_IMETHODIMP +nsFontCache::Observe(nsISupports*, const char* aTopic, const char16_t*) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + if (!nsCRT::strcmp(aTopic, "memory-pressure")) { + Compact(); + } + return NS_OK; +} + +already_AddRefed<nsFontMetrics> nsFontCache::GetMetricsFor( + const nsFont& aFont, const nsFontMetrics::Params& aParams) { + // We may eventually want to put an nsFontCache on canvas2d workers, but for + // now it is only used by the main-thread layout code and stylo. + mozilla::AssertIsMainThreadOrServoFontMetricsLocked(); + + nsAtom* language = aParams.language && !aParams.language->IsEmpty() + ? aParams.language + : mLocaleLanguage.get(); + + // First check our cache + // start from the end, which is where we put the most-recent-used element + const int32_t n = mFontMetrics.Length() - 1; + for (int32_t i = n; i >= 0; --i) { + nsFontMetrics* fm = mFontMetrics.Elements()[i]; + if (fm->Font().Equals(aFont) && + fm->GetUserFontSet() == aParams.userFontSet && + fm->Language() == language && + fm->Orientation() == aParams.orientation && + fm->ExplicitLanguage() == aParams.explicitLanguage) { + if (i != n) { + // promote it to the end of the cache + mFontMetrics.RemoveElementAtUnsafe(i); + mFontMetrics.AppendElement(fm); + } + fm->GetThebesFontGroup()->UpdateUserFonts(); + return do_AddRef(fm); + } + } + + if (!mReportedProbableFingerprinting) { + // We try to detect font fingerprinting attempts by recognizing a large + // number of cache misses in a short amount of time, which indicates the + // usage of an unreasonable amount of different fonts by the web page. + PRTime now = PR_Now(); + if (now - mLastCacheMiss > kFingerprintingTimeout) { + mCacheMisses = 0; + } + mCacheMisses++; + mLastCacheMiss = now; + if (NS_IsMainThread() && mCacheMisses > kFingerprintingCacheMissThreshold) { + mContext->Document()->RecordFontFingerprinting(); + mReportedProbableFingerprinting = true; + } + } + + // It's not in the cache. Get font metrics and then cache them. + // If the cache has reached its size limit, drop the older half of the + // entries; but if we're on a stylo thread (the usual case), we have + // to post a task back to the main thread to do the flush. + if (n >= kMaxCacheEntries - 1 && !mFlushPending) { + if (NS_IsMainThread()) { + Flush(mFontMetrics.Length() - kMaxCacheEntries / 2); + } else { + mFlushPending = true; + nsCOMPtr<nsIRunnable> flushTask = new FlushFontMetricsTask(this); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(flushTask)); + } + } + + nsFontMetrics::Params params = aParams; + params.language = language; + RefPtr<nsFontMetrics> fm = new nsFontMetrics(aFont, params, mContext); + // the mFontMetrics list has the "head" at the end, because append + // is cheaper than insert + mFontMetrics.AppendElement(do_AddRef(fm).take()); + return fm.forget(); +} + +void nsFontCache::UpdateUserFonts(gfxUserFontSet* aUserFontSet) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + for (nsFontMetrics* fm : mFontMetrics) { + gfxFontGroup* fg = fm->GetThebesFontGroup(); + if (fg->GetUserFontSet() == aUserFontSet) { + fg->UpdateUserFonts(); + } + } +} + +void nsFontCache::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + mFontMetrics.RemoveElement(aFontMetrics); +} + +void nsFontCache::Compact() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + // Need to loop backward because the running element can be removed on + // the way + for (int32_t i = mFontMetrics.Length() - 1; i >= 0; --i) { + nsFontMetrics* fm = mFontMetrics[i]; + nsFontMetrics* oldfm = fm; + // Destroy() isn't here because we want our device context to be + // notified + NS_RELEASE(fm); // this will reset fm to nullptr + // if the font is really gone, it would have called back in + // FontMetricsDeleted() and would have removed itself + if (mFontMetrics.IndexOf(oldfm) != mFontMetrics.NoIndex) { + // nope, the font is still there, so let's hold onto it too + NS_ADDREF(oldfm); + } + } +} + +// Flush the aFlushCount oldest entries, or all if (aFlushCount < 0) +void nsFontCache::Flush(int32_t aFlushCount) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + int32_t n = aFlushCount < 0 + ? mFontMetrics.Length() + : std::min<int32_t>(aFlushCount, mFontMetrics.Length()); + for (int32_t i = n - 1; i >= 0; --i) { + nsFontMetrics* fm = mFontMetrics[i]; + // Destroy() will unhook our device context from the fm so that we + // won't waste time in triggering the notification of + // FontMetricsDeleted() in the subsequent release + fm->Destroy(); + NS_RELEASE(fm); + } + mFontMetrics.RemoveElementsAt(0, n); + + mLastCacheMiss = 0; + mCacheMisses = 0; +} diff --git a/gfx/src/nsFontCache.h b/gfx/src/nsFontCache.h new file mode 100644 index 0000000000..794eb0fae0 --- /dev/null +++ b/gfx/src/nsFontCache.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NS_FONTCACHE_H_ +#define _NS_FONTCACHE_H_ + +#include <stdint.h> +#include <sys/types.h> +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsFontMetrics.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "prtime.h" + +class gfxUserFontSet; +class nsAtom; +class nsPresContext; +struct nsFont; + +class nsFontCache final : public nsIObserver { + public: + nsFontCache() : mContext(nullptr) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(nsPresContext* aContext); + void Destroy(); + + already_AddRefed<nsFontMetrics> GetMetricsFor( + const nsFont& aFont, const nsFontMetrics::Params& aParams); + + void FontMetricsDeleted(const nsFontMetrics* aFontMetrics); + void Compact(); + + // Flush aFlushCount oldest entries, or all if aFlushCount is negative + void Flush(int32_t aFlushCount = -1); + + void UpdateUserFonts(gfxUserFontSet* aUserFontSet); + + protected: + // If the array of cached entries is about to exceed this threshold, + // we'll discard the oldest ones so as to keep the size reasonable. + // In practice, the great majority of cache hits are among the last + // few entries; keeping thousands of older entries becomes counter- + // productive because it can then take too long to scan the cache. + static constexpr int32_t kMaxCacheEntries = 128; + + // Number of cache misses before we assume that a font fingerprinting attempt + // is being made. Usually fingerprinters will lookup the same font-family + // three times, as "sans-serif", "serif" and "monospace". + static constexpr int32_t kFingerprintingCacheMissThreshold = 3 * 20; + // We assume that fingerprinters will lookup a large number of fonts in a + // short amount of time. + static constexpr PRTime kFingerprintingTimeout = + PRTime(PR_USEC_PER_SEC) * 3; // 3 seconds + + static_assert(kFingerprintingCacheMissThreshold < kMaxCacheEntries); + + ~nsFontCache() = default; + + nsPresContext* mContext; // owner + RefPtr<nsAtom> mLocaleLanguage; + + // We may not flush older entries immediately the array reaches + // kMaxCacheEntries length, because this usually happens on a stylo + // thread where we can't safely delete metrics objects. So we allocate an + // oversized autoarray buffer here, so that we're unlikely to overflow + // it and need separate heap allocation before the flush happens on the + // main thread. + AutoTArray<nsFontMetrics*, kMaxCacheEntries * 2> mFontMetrics; + + bool mFlushPending = false; + + class FlushFontMetricsTask : public mozilla::Runnable { + public: + explicit FlushFontMetricsTask(nsFontCache* aCache) + : mozilla::Runnable("FlushFontMetricsTask"), mCache(aCache) {} + NS_IMETHOD Run() override { + // Partially flush the cache, leaving the kMaxCacheEntries/2 most + // recent entries. + mCache->Flush(mCache->mFontMetrics.Length() - kMaxCacheEntries / 2); + mCache->mFlushPending = false; + return NS_OK; + } + + private: + RefPtr<nsFontCache> mCache; + }; + + PRTime mLastCacheMiss = 0; + uint64_t mCacheMisses = 0; + bool mReportedProbableFingerprinting = false; +}; + +#endif /* _NS_FONTCACHE_H_ */ diff --git a/gfx/src/nsFontMetrics.cpp b/gfx/src/nsFontMetrics.cpp new file mode 100644 index 0000000000..93fd1264b2 --- /dev/null +++ b/gfx/src/nsFontMetrics.cpp @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsFontMetrics.h" +#include <math.h> // for floor, ceil +#include <algorithm> // for max +#include "gfxContext.h" // for gfxContext +#include "gfxFontConstants.h" // for NS_FONT_SYNTHESIS_* +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxPoint.h" // for gfxPoint +#include "gfxRect.h" // for gfxRect +#include "gfxTextRun.h" // for gfxFontGroup +#include "gfxTypes.h" // for gfxFloat +#include "nsAtom.h" // for nsAtom +#include "nsBoundingMetrics.h" // for nsBoundingMetrics +#include "nsDebug.h" // for NS_ERROR +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsMathUtils.h" // for NS_round +#include "nsPresContext.h" // for nsPresContext +#include "nsString.h" // for nsString +#include "nsStyleConsts.h" // for StyleHyphens::None +#include "mozilla/Assertions.h" // for MOZ_ASSERT +#include "mozilla/UniquePtr.h" // for UniquePtr + +class gfxUserFontSet; +using namespace mozilla; + +namespace { + +class AutoTextRun { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + AutoTextRun(const nsFontMetrics* aMetrics, DrawTarget* aDrawTarget, + const char* aString, uint32_t aLength) { + mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( + reinterpret_cast<const uint8_t*>(aString), aLength, aDrawTarget, + aMetrics->AppUnitsPerDevPixel(), ComputeFlags(aMetrics), + nsTextFrameUtils::Flags(), nullptr); + } + + AutoTextRun(const nsFontMetrics* aMetrics, DrawTarget* aDrawTarget, + const char16_t* aString, uint32_t aLength) { + mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( + aString, aLength, aDrawTarget, aMetrics->AppUnitsPerDevPixel(), + ComputeFlags(aMetrics), nsTextFrameUtils::Flags(), nullptr); + } + + gfxTextRun* get() const { return mTextRun.get(); } + gfxTextRun* operator->() const { return mTextRun.get(); } + + private: + static gfx::ShapedTextFlags ComputeFlags(const nsFontMetrics* aMetrics) { + gfx::ShapedTextFlags flags = gfx::ShapedTextFlags(); + if (aMetrics->GetTextRunRTL()) { + flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; + } + if (aMetrics->GetVertical()) { + switch (aMetrics->GetTextOrientation()) { + case StyleTextOrientation::Mixed: + flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED; + break; + case StyleTextOrientation::Upright: + flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + break; + case StyleTextOrientation::Sideways: + flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + } + return flags; + } + + RefPtr<gfxTextRun> mTextRun; +}; + +class StubPropertyProvider final : public gfxTextRun::PropertyProvider { + public: + void GetHyphenationBreaks( + gfxTextRun::Range aRange, + gfxTextRun::HyphenType* aBreakBefore) const override { + NS_ERROR( + "This shouldn't be called because we never call BreakAndMeasureText"); + } + mozilla::StyleHyphens GetHyphensOption() const override { + NS_ERROR( + "This shouldn't be called because we never call BreakAndMeasureText"); + return mozilla::StyleHyphens::None; + } + gfxFloat GetHyphenWidth() const override { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return 0; + } + already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() const override { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return nullptr; + } + uint32_t GetAppUnitsPerDevUnit() const override { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return 60; + } + void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) const override { + NS_ERROR("This shouldn't be called because we never enable spacing"); + } + gfx::ShapedTextFlags GetShapedTextFlags() const override { + NS_ERROR("This shouldn't be called because we never enable hyphens"); + return gfx::ShapedTextFlags(); + } +}; + +} // namespace + +nsFontMetrics::nsFontMetrics(const nsFont& aFont, const Params& aParams, + nsPresContext* aContext) + : mFont(aFont), + mLanguage(aParams.language), + mPresContext(aContext), + mP2A(aContext->DeviceContext()->AppUnitsPerDevPixel()), + mOrientation(aParams.orientation), + mExplicitLanguage(aParams.explicitLanguage), + mTextRunRTL(false), + mVertical(false), + mTextOrientation(mozilla::StyleTextOrientation::Mixed) { + gfxFontStyle style(aFont.style, aFont.weight, aFont.stretch, + gfxFloat(aFont.size.ToAppUnits()) / mP2A, aFont.sizeAdjust, + aFont.family.is_system_font, + aContext->DeviceContext()->IsPrinterContext(), + aFont.synthesisWeight == StyleFontSynthesis::Auto, + aFont.synthesisStyle == StyleFontSynthesis::Auto, + aFont.synthesisSmallCaps == StyleFontSynthesis::Auto, + aFont.synthesisPosition == StyleFontSynthesis::Auto, + aFont.languageOverride); + + aFont.AddFontFeaturesToStyle(&style, mOrientation == eVertical); + style.featureValueLookup = aParams.featureValueLookup; + + aFont.AddFontVariationsToStyle(&style); + + gfxFloat devToCssSize = gfxFloat(mP2A) / gfxFloat(AppUnitsPerCSSPixel()); + mFontGroup = new gfxFontGroup( + mPresContext, aFont.family.families, &style, mLanguage, mExplicitLanguage, + aParams.textPerf, aParams.userFontSet, devToCssSize, aFont.variantEmoji); +} + +nsFontMetrics::~nsFontMetrics() { + // Should not be dropped by stylo + MOZ_ASSERT(NS_IsMainThread()); + if (mPresContext) { + mPresContext->FontMetricsDeleted(this); + } +} + +void nsFontMetrics::Destroy() { mPresContext = nullptr; } + +// XXXTODO get rid of this macro +#define ROUND_TO_TWIPS(x) (nscoord) floor(((x) * mP2A) + 0.5) +#define CEIL_TO_TWIPS(x) (nscoord) ceil((x) * mP2A) + +static const gfxFont::Metrics& GetMetrics( + const nsFontMetrics* aFontMetrics, + nsFontMetrics::FontOrientation aOrientation) { + RefPtr<gfxFont> font = + aFontMetrics->GetThebesFontGroup()->GetFirstValidFont(); + return font->GetMetrics(aOrientation); +} + +static const gfxFont::Metrics& GetMetrics(const nsFontMetrics* aFontMetrics) { + return GetMetrics(aFontMetrics, aFontMetrics->Orientation()); +} + +nscoord nsFontMetrics::XHeight() const { + return ROUND_TO_TWIPS(GetMetrics(this).xHeight); +} + +nscoord nsFontMetrics::CapHeight() const { + return ROUND_TO_TWIPS(GetMetrics(this).capHeight); +} + +nscoord nsFontMetrics::SuperscriptOffset() const { + return ROUND_TO_TWIPS(GetMetrics(this).emHeight * + NS_FONT_SUPERSCRIPT_OFFSET_RATIO); +} + +nscoord nsFontMetrics::SubscriptOffset() const { + return ROUND_TO_TWIPS(GetMetrics(this).emHeight * + NS_FONT_SUBSCRIPT_OFFSET_RATIO); +} + +void nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) const { + aOffset = ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset); + aSize = ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize); +} + +void nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) const { + aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset()); + aSize = ROUND_TO_TWIPS(GetMetrics(this).underlineSize); +} + +// GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the +// text-decoration lines drawable area. See bug 421353. +// BE CAREFUL for rounding each values. The logic MUST be same as +// nsCSSRendering::GetTextDecorationRectInternal's. + +static gfxFloat ComputeMaxDescent(const gfxFont::Metrics& aMetrics, + gfxFontGroup* aFontGroup) { + gfxFloat offset = floor(-aFontGroup->GetUnderlineOffset() + 0.5); + gfxFloat size = NS_round(aMetrics.underlineSize); + gfxFloat minDescent = offset + size; + return floor(std::max(minDescent, aMetrics.maxDescent) + 0.5); +} + +static gfxFloat ComputeMaxAscent(const gfxFont::Metrics& aMetrics) { + return floor(aMetrics.maxAscent + 0.5); +} + +nscoord nsFontMetrics::InternalLeading() const { + return ROUND_TO_TWIPS(GetMetrics(this).internalLeading); +} + +nscoord nsFontMetrics::ExternalLeading() const { + return ROUND_TO_TWIPS(GetMetrics(this).externalLeading); +} + +nscoord nsFontMetrics::EmHeight() const { + return ROUND_TO_TWIPS(GetMetrics(this).emHeight); +} + +nscoord nsFontMetrics::EmAscent() const { + return ROUND_TO_TWIPS(GetMetrics(this).emAscent); +} + +nscoord nsFontMetrics::EmDescent() const { + return ROUND_TO_TWIPS(GetMetrics(this).emDescent); +} + +nscoord nsFontMetrics::MaxHeight() const { + return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) + + CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup)); +} + +nscoord nsFontMetrics::MaxAscent() const { + return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))); +} + +nscoord nsFontMetrics::MaxDescent() const { + return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup)); +} + +nscoord nsFontMetrics::MaxAdvance() const { + return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance); +} + +nscoord nsFontMetrics::AveCharWidth() const { + // Use CEIL instead of ROUND for consistency with GetMaxAdvance + return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth); +} + +nscoord nsFontMetrics::ZeroOrAveCharWidth() const { + return CEIL_TO_TWIPS(GetMetrics(this).ZeroOrAveCharWidth()); +} + +nscoord nsFontMetrics::SpaceWidth() const { + // For vertical text with mixed or sideways orientation, we want the + // width of a horizontal space (even if we're using vertical line-spacing + // metrics, as with "writing-mode:vertical-*;text-orientation:mixed"). + return CEIL_TO_TWIPS( + GetMetrics(this, + mVertical && mTextOrientation == StyleTextOrientation::Upright + ? eVertical + : eHorizontal) + .spaceWidth); +} + +int32_t nsFontMetrics::GetMaxStringLength() const { + const double x = 32767.0 / std::max(1.0, GetMetrics(this).maxAdvance); + int32_t len = (int32_t)floor(x); + return std::max(1, len); +} + +nscoord nsFontMetrics::GetWidth(const char* aString, uint32_t aLength, + DrawTarget* aDrawTarget) const { + if (aLength == 0) { + return 0; + } + if (aLength == 1 && aString[0] == ' ') { + return SpaceWidth(); + } + StubPropertyProvider provider; + AutoTextRun textRun(this, aDrawTarget, aString, aLength); + if (textRun.get()) { + return NSToCoordRound( + textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider)); + } + return 0; +} + +nscoord nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength, + DrawTarget* aDrawTarget) const { + if (aLength == 0) { + return 0; + } + if (aLength == 1 && aString[0] == ' ') { + return SpaceWidth(); + } + StubPropertyProvider provider; + AutoTextRun textRun(this, aDrawTarget, aString, aLength); + if (textRun.get()) { + return NSToCoordRound( + textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider)); + } + return 0; +} + +// Draw a string using this font handle on the surface passed in. +void nsFontMetrics::DrawString(const char* aString, uint32_t aLength, + nscoord aX, nscoord aY, + gfxContext* aContext) const { + if (aLength == 0) { + return; + } + StubPropertyProvider provider; + AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength); + if (!textRun.get()) { + return; + } + gfx::Point pt(aX, aY); + gfxTextRun::Range range(0, aLength); + if (mTextRunRTL) { + if (mVertical) { + pt.y += textRun->GetAdvanceWidth(range, &provider); + } else { + pt.x += textRun->GetAdvanceWidth(range, &provider); + } + } + mozilla::gfx::PaletteCache paletteCache; + gfxTextRun::DrawParams params(aContext, paletteCache); + params.provider = &provider; + textRun->Draw(range, pt, params); +} + +void nsFontMetrics::DrawString( + const char16_t* aString, uint32_t aLength, nscoord aX, nscoord aY, + gfxContext* aContext, DrawTarget* aTextRunConstructionDrawTarget) const { + if (aLength == 0) { + return; + } + StubPropertyProvider provider; + AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength); + if (!textRun.get()) { + return; + } + gfx::Point pt(aX, aY); + gfxTextRun::Range range(0, aLength); + if (mTextRunRTL) { + if (mVertical) { + pt.y += textRun->GetAdvanceWidth(range, &provider); + } else { + pt.x += textRun->GetAdvanceWidth(range, &provider); + } + } + mozilla::gfx::PaletteCache paletteCache; + gfxTextRun::DrawParams params(aContext, paletteCache); + params.provider = &provider; + textRun->Draw(range, pt, params); +} + +static nsBoundingMetrics GetTextBoundingMetrics( + const 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) const { + return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget, + gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS); +} + +nsBoundingMetrics nsFontMetrics::GetInkBoundsForInkOverflow( + const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) const { + return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget, + gfxFont::LOOSE_INK_EXTENTS); +} + +gfxUserFontSet* nsFontMetrics::GetUserFontSet() const { + return mFontGroup->GetUserFontSet(); +} diff --git a/gfx/src/nsFontMetrics.h b/gfx/src/nsFontMetrics.h new file mode 100644 index 0000000000..55a45f6be1 --- /dev/null +++ b/gfx/src/nsFontMetrics.h @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSFONTMETRICS__H__ +#define NSFONTMETRICS__H__ + +#include <stdint.h> // for uint32_t +#include <sys/types.h> // for int32_t +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/RefPtr.h" // for RefPtr +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsCoord.h" // for nscoord +#include "nsError.h" // for nsresult +#include "nsFont.h" // for nsFont +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsStyleConsts.h" +#include "nscore.h" // for char16_t + +class gfxContext; +class gfxFontGroup; +class gfxUserFontSet; +class gfxTextPerfMetrics; +class nsPresContext; +class nsAtom; +struct nsBoundingMetrics; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +/** + * Font metrics + * + * This class may be somewhat misnamed. A better name might be + * nsFontList. The style system uses the nsFont struct for various + * font properties, one of which is font-family, which can contain a + * *list* of font names. The nsFont struct is "realized" by asking the + * pres context to cough up an nsFontMetrics object, which contains + * a list of real font handles, one for each font mentioned in + * font-family (and for each fallback when we fall off the end of that + * list). + * + * The style system needs to have access to certain metrics, such as + * the em height (for the CSS "em" unit), and we use the first Western + * font's metrics for that purpose. The platform-specific + * implementations are expected to select non-Western fonts that "fit" + * reasonably well with the Western font that is loaded at Init time. + */ +class nsFontMetrics final { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + enum FontOrientation { eHorizontal, eVertical }; + + struct MOZ_STACK_CLASS Params { + nsAtom* language = nullptr; + bool explicitLanguage = false; + FontOrientation orientation = eHorizontal; + gfxUserFontSet* userFontSet = nullptr; + gfxTextPerfMetrics* textPerf = nullptr; + gfxFontFeatureValueSet* featureValueLookup = nullptr; + }; + + nsFontMetrics(const nsFont& aFont, const Params& aParams, + nsPresContext* aContext); + + // Used by stylo + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsFontMetrics) + + /** + * Destroy this font metrics. This breaks the association between + * the font metrics and the pres context. + */ + void Destroy(); + + /** + * Return the font's x-height. + */ + nscoord XHeight() const; + + /** + * Return the font's cap-height. + */ + nscoord CapHeight() const; + + /** + * 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() const; + + /** + * 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() const; + + /** + * 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) const; + + /** + * 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) const; + + /** + * Returns the amount of internal leading for the font. + * This is normally the difference between the max ascent + * and the em ascent. + */ + nscoord InternalLeading() const; + + /** + * 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() const; + + /** + * Returns the height of the em square. + * This is em ascent plus em descent. + */ + nscoord EmHeight() const; + + /** + * Returns the ascent part of the em square. + */ + nscoord EmAscent() const; + + /** + * Returns the descent part of the em square. + */ + nscoord EmDescent() const; + + /** + * Returns the height of the bounding box. + * This is max ascent plus max descent. + */ + nscoord MaxHeight() const; + + /** + * Returns the maximum distance characters in this font extend + * above the base line. + */ + nscoord MaxAscent() const; + + /** + * Returns the maximum distance characters in this font extend + * below the base line. + */ + nscoord MaxDescent() const; + + /** + * Returns the maximum character advance for the font. + */ + nscoord MaxAdvance() const; + + /** + * Returns the average character width + */ + nscoord AveCharWidth() const; + + /** + * Returns width of the zero character, or AveCharWidth if no zero present. + */ + nscoord ZeroOrAveCharWidth() const; + + /** + * Returns the often needed width of the space character + */ + nscoord SpaceWidth() const; + + /** + * Returns the font associated with these metrics. The return value + * is only defined after Init() has been called. + */ + const nsFont& Font() const { return mFont; } + + /** + * Returns the language associated with these metrics + */ + nsAtom* Language() const { return mLanguage; } + + /** + * Returns the orientation (horizontal/vertical) of these metrics. + */ + FontOrientation Orientation() const { return mOrientation; } + + int32_t GetMaxStringLength() const; + + // 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) const; + nscoord GetWidth(const char16_t* aString, uint32_t aLength, + DrawTarget* aDrawTarget) const; + + // Draw a string using this font handle on the surface passed in. + void DrawString(const char* aString, uint32_t aLength, nscoord aX, nscoord aY, + gfxContext* aContext) const; + void DrawString(const char16_t* aString, uint32_t aLength, nscoord aX, + nscoord aY, gfxContext* aContext, + DrawTarget* aTextRunConstructionDrawTarget) const; + + nsBoundingMetrics GetBoundingMetrics(const char16_t* aString, + uint32_t aLength, + DrawTarget* aDrawTarget) const; + + // Returns the LOOSE_INK_EXTENTS bounds of the text for determing the + // overflow area of the string. + nsBoundingMetrics GetInkBoundsForInkOverflow(const char16_t* aString, + uint32_t aLength, + DrawTarget* aDrawTarget) const; + + void SetTextRunRTL(bool aIsRTL) { mTextRunRTL = aIsRTL; } + bool GetTextRunRTL() const { return mTextRunRTL; } + + void SetVertical(bool aVertical) { mVertical = aVertical; } + bool GetVertical() const { return mVertical; } + + void SetTextOrientation(mozilla::StyleTextOrientation aTextOrientation) { + mTextOrientation = aTextOrientation; + } + mozilla::StyleTextOrientation GetTextOrientation() const { + return mTextOrientation; + } + + bool ExplicitLanguage() const { return mExplicitLanguage; } + + gfxFontGroup* GetThebesFontGroup() const { return mFontGroup; } + gfxUserFontSet* GetUserFontSet() const; + + int32_t AppUnitsPerDevPixel() const { return mP2A; } + + private: + // Private destructor, to discourage deletion outside of Release(): + ~nsFontMetrics(); + + const nsFont mFont; + RefPtr<gfxFontGroup> mFontGroup; + RefPtr<nsAtom> const mLanguage; + // Pointer to the pres context for which this fontMetrics object was + // created. + nsPresContext* MOZ_NON_OWNING_REF mPresContext; + const 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. + const FontOrientation mOrientation; + + // Whether mLanguage comes from explicit markup (in which case it should be + // used to tailor effects like case-conversion) or is an inferred/default + // value. + const bool mExplicitLanguage; + + // These fields may be set by clients to control the behavior of methods + // like GetWidth and DrawString according to the writing mode, direction + // and text-orientation desired. + bool mTextRunRTL; + bool mVertical; + mozilla::StyleTextOrientation mTextOrientation; +}; + +#endif /* NSFONTMETRICS__H__ */ diff --git a/gfx/src/nsGfxCIID.h b/gfx/src/nsGfxCIID.h new file mode 100644 index 0000000000..65f09ed47b --- /dev/null +++ b/gfx/src/nsGfxCIID.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGfxCIID_h__ +#define nsGfxCIID_h__ + +#define NS_FONT_ENUMERATOR_CID \ + { \ + 0xa6cf9115, 0x15b3, 0x11d2, { \ + 0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 \ + } \ + } + +#define NS_SCRIPTABLE_REGION_CID \ + { \ + 0xda5b130a, 0x1dd1, 0x11b2, { \ + 0xad, 0x47, 0xf4, 0x55, 0xb1, 0x81, 0x4a, 0x78 \ + } \ + } + +#define NS_GFX_INITIALIZATION_CID \ + { \ + 0x67c41576, 0x9664, 0x4ed5, { \ + 0x90, 0xc1, 0xf6, 0x68, 0x3f, 0xd5, 0x2c, 0x8f \ + } \ + } + +#endif diff --git a/gfx/src/nsIFontEnumerator.idl b/gfx/src/nsIFontEnumerator.idl new file mode 100644 index 0000000000..5c6177f39f --- /dev/null +++ b/gfx/src/nsIFontEnumerator.idl @@ -0,0 +1,72 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(924d98d9-3518-4cb4-8708-c74fe8e3ec3c)] +interface nsIFontEnumerator : nsISupports +{ + /** + * Return a sorted array of the names of all installed fonts. + * + * @return array of names + * @return void + */ + Array<AString> EnumerateAllFonts(); + + /** + * Return a sorted array of names of fonts that support the given language + * group and are suitable for use as the given CSS generic font. + * + * @param aLangGroup language group + * @param aGeneric CSS generic font + * @return array of names + * @return void + */ + Array<AString> EnumerateFonts(in string aLangGroup, in string aGeneric); + + /** + * Return a promise that resolves to a sorted array of the names of all + * installed fonts. + * + * @return Promise that resolves to Array + */ + [implicit_jscontext] + jsval EnumerateAllFontsAsync(); + + /** + * Return a promise that resolves to a sorted array of names of fonts + * that support the given language group and are suitable for use as the given + * CSS generic font. + * + * @param aLangGroup language group + * @param aGeneric CSS generic font + * @return Promise that resolves to Array + */ + [implicit_jscontext] + jsval EnumerateFontsAsync(in string aLangGroup, in string aGeneric); + + /** + @param aLangGroup language group + @return bool do we have a font for this language group + */ + void HaveFontFor(in string aLangGroup, [retval] out boolean aResult); + + /** + * @param aLangGroup language group + * @param aGeneric CSS generic font + * @return suggested default font for this language group and generic family + */ + wstring getDefaultFont(in string aLangGroup, in string aGeneric); + + /** + * get the standard family name on the system from given family + * @param aName family name which may be alias + * @return the standard family name on the system, if given name does not + * exist, returns empty string + */ + wstring getStandardFamilyName(in wstring aName); +}; diff --git a/gfx/src/nsITheme.h b/gfx/src/nsITheme.h new file mode 100644 index 0000000000..48f563751f --- /dev/null +++ b/gfx/src/nsITheme.h @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* service providing platform-specific native rendering for widgets */ + +#ifndef nsITheme_h_ +#define nsITheme_h_ + +#include "mozilla/AlreadyAddRefed.h" +#include "nsISupports.h" +#include "nsID.h" +#include "nscore.h" +#include "Units.h" + +struct nsRect; +class gfxContext; +class nsAttrValue; +class nsPresContext; +class nsDeviceContext; +class nsIFrame; +class nsAtom; +class nsIWidget; + +namespace mozilla { +class ComputedStyle; +enum class StyleAppearance : uint8_t; +enum class StyleScrollbarWidth : uint8_t; +namespace layers { +class StackingContextHelper; +class RenderRootStateManager; +} // namespace layers +namespace widget { +class Theme; +} // namespace widget +namespace wr { +class DisplayListBuilder; +class IpcResourceUpdateQueue; +} // namespace wr +} // namespace mozilla + +// IID for the nsITheme interface +// {7329f760-08cb-450f-8225-dae729096dec} +#define NS_ITHEME_IID \ + { \ + 0x7329f760, 0x08cb, 0x450f, { \ + 0x82, 0x25, 0xda, 0xe7, 0x29, 0x09, 0x6d, 0xec \ + } \ + } + +/** + * nsITheme is a service that provides platform-specific native + * rendering for widgets. In other words, it provides the necessary + * operations to draw a rendering object (an nsIFrame) as a native + * widget. + * + * All the methods on nsITheme take a rendering context or device + * context, a frame (the rendering object), and a widget type (one of + * the constants in nsThemeConstants.h). + */ +class nsITheme : public nsISupports { + protected: + using LayoutDeviceIntMargin = mozilla::LayoutDeviceIntMargin; + using LayoutDeviceIntSize = mozilla::LayoutDeviceIntSize; + using LayoutDeviceIntCoord = mozilla::LayoutDeviceIntCoord; + using StyleAppearance = mozilla::StyleAppearance; + using StyleScrollbarWidth = mozilla::StyleScrollbarWidth; + using ComputedStyle = mozilla::ComputedStyle; + + 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 + * @param DrawOverflow whether outlines, shadows and other such overflowing + * things should be drawn. Honoring this creates better results for + * box-shadow, though it's not a hard requirement. + */ + enum class DrawOverflow { No, Yes }; + NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, + StyleAppearance aWidgetType, + const nsRect& aRect, const nsRect& aDirtyRect, + DrawOverflow = DrawOverflow::Yes) = 0; + + /** + * Create WebRender commands for the theme background. + * @return true if the theme knows how to create WebRender commands for the + * given widget type, false if DrawWidgetBackground need sto be called + * instead. + */ + virtual bool CreateWebRenderCommandsForWidget( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame, + StyleAppearance aWidgetType, const nsRect& aRect) { + return false; + } + + /** + * Returns the minimum widths of a scrollbar for a given style, that is, the + * minimum width for a vertical scrollbar, and the minimum height of a + * horizontal scrollbar. + */ + enum class Overlay { No, Yes }; + virtual LayoutDeviceIntCoord GetScrollbarSize(const nsPresContext*, + StyleScrollbarWidth, + Overlay) = 0; + + /** + * Return the border for the widget, in device pixels. + */ + [[nodiscard]] virtual LayoutDeviceIntMargin GetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aWidgetType) = 0; + + /** + * This method can return false to indicate that the CSS padding + * value should be used. Otherwise, it will fill in aResult with the + * computed padding, in pixels, and return true. + * + * XXXldb This ought to be required to return true for non-containers + * so that we don't let specified padding that has no effect change + * the computed padding and potentially the size. + */ + virtual bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aWidgetType, + LayoutDeviceIntMargin* aResult) = 0; + + /** + * On entry, *aResult is positioned at 0,0 and sized to the new size + * of aFrame (aFrame->GetSize() may be stale and should not be used). + * This method can return false to indicate that no special + * overflow area is required by the native widget. Otherwise it will + * fill in aResult with the desired overflow area, in appunits, relative + * to the frame origin, and return true. + * + * This overflow area is used to determine what area needs to be + * repainted when the widget changes. However, it does not affect the + * widget's size or what area is reachable by scrollbars. (In other + * words, in layout terms, it affects ink overflow but not + * scrollable overflow.) + */ + virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aWidgetType, + /*INOUT*/ nsRect* aOverflowRect) { + return false; + } + + /** + * Get the preferred content-box size of a checkbox / radio button, in app + * units. Historically 9px. + */ + virtual nscoord GetCheckboxRadioPrefSize() { + return mozilla::CSSPixel::ToAppUnits(9); + } + + /** + * Get the minimum border-box size of a widget, in device pixels. + */ + virtual mozilla::LayoutDeviceIntSize GetMinimumWidgetSize( + nsPresContext* aPresContext, nsIFrame* aFrame, + StyleAppearance aWidgetType) = 0; + + enum Transparency { eOpaque = 0, eTransparent, eUnknownTransparency }; + + /** + * Returns what we know about the transparency of the widget. + */ + virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, + StyleAppearance aWidgetType) { + return eUnknownTransparency; + } + + /** + * Sets |*aShouldRepaint| to indicate whether an attribute or content state + * change should trigger a repaint. Call with null |aAttribute| (and + * null |aOldValue|) for content state changes. + */ + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aWidgetType, + nsAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) = 0; + + NS_IMETHOD ThemeChanged() = 0; + + virtual bool WidgetAppearanceDependsOnWindowFocus( + StyleAppearance aWidgetType) { + return false; + } + + /** + * ThemeGeometryType values are used for describing themed nsIFrames in + * calls to nsIWidget::UpdateThemeGeometries. We don't simply pass the + * -moz-appearance value ("widget type") of the frame because the widget may + * want to treat different frames with the same -moz-appearance differently + * based on other properties of the frame. So we give the theme a first look + * at the frame in nsITheme::ThemeGeometryTypeForWidget and pass the + * returned ThemeGeometryType along to the widget. + * Each theme backend defines the ThemeGeometryType values it needs in its + * own nsITheme subclass. eThemeGeometryTypeUnknown is the only value that's + * shared between backends. + */ + typedef uint8_t ThemeGeometryType; + enum { eThemeGeometryTypeUnknown = 0 }; + + /** + * Returns the theme geometry type that should be used in the ThemeGeometry + * array that's passed to the widget using nsIWidget::UpdateThemeGeometries. + * A return value of eThemeGeometryTypeUnknown means that this frame will + * not be included in the ThemeGeometry array. + */ + virtual ThemeGeometryType ThemeGeometryTypeForWidget( + nsIFrame* aFrame, StyleAppearance aWidgetType) { + return eThemeGeometryTypeUnknown; + } + + /** + * Can the nsITheme implementation handle this widget? + */ + virtual bool ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + StyleAppearance aWidgetType) = 0; + + virtual bool WidgetIsContainer(StyleAppearance aWidgetType) = 0; + + /** + * Does the nsITheme implementation draw its own focus ring for this widget? + */ + virtual bool ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) = 0; + + // Whether we want an inner focus ring for buttons and menulists. + virtual bool ThemeWantsButtonInnerFocusRing() { return false; } + + /** + * Should we insert a dropmarker inside of combobox button? + */ + virtual bool ThemeNeedsComboboxDropmarker() = 0; + + virtual bool ThemeSupportsScrollbarButtons() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITheme, NS_ITHEME_IID) + +// Singleton accessor functions, these should never return null. +// +// Do not use directly, use nsPresContext::Theme instead. +extern already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly(); +extern already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly(); +extern already_AddRefed<nsITheme> do_GetRDMThemeDoNotUseDirectly(); + +// Native theme creation function, these should never return null. +extern already_AddRefed<mozilla::widget::Theme> +do_CreateNativeThemeDoNotUseDirectly(); + +#endif diff --git a/gfx/src/nsMargin.h b/gfx/src/nsMargin.h new file mode 100644 index 0000000000..cf05894ff3 --- /dev/null +++ b/gfx/src/nsMargin.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSMARGIN_H +#define NSMARGIN_H + +#include "nsCoord.h" +#include "mozilla/gfx/BaseMargin.h" +#include "mozilla/gfx/Rect.h" + +struct nsMargin : public mozilla::gfx::BaseMargin<nscoord, nsMargin> { + typedef mozilla::gfx::BaseMargin<nscoord, nsMargin> Super; + + // Constructors + nsMargin() = default; + nsMargin(const nsMargin& aMargin) = default; + nsMargin(nscoord aTop, nscoord aRight, nscoord aBottom, nscoord aLeft) + : Super(aTop, aRight, aBottom, aLeft) {} +}; + +typedef mozilla::gfx::IntMargin nsIntMargin; + +#endif /* NSMARGIN_H */ diff --git a/gfx/src/nsPoint.h b/gfx/src/nsPoint.h new file mode 100644 index 0000000000..86688d2142 --- /dev/null +++ b/gfx/src/nsPoint.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSPOINT_H +#define NSPOINT_H + +#include <cstdint> +#include "nsCoord.h" +#include "mozilla/gfx/BasePoint.h" +#include "mozilla/gfx/Point.h" + +// nsIntPoint represents a point in one of the types of pixels. +// Uses of nsIntPoint should eventually be converted to CSSIntPoint, +// LayoutDeviceIntPoint, etc. (see layout/base/Units.h). +typedef mozilla::gfx::IntPoint nsIntPoint; + +// nsPoint represents a point in app units. + +struct nsPoint : public mozilla::gfx::BasePoint<nscoord, nsPoint> { + typedef mozilla::gfx::BasePoint<nscoord, nsPoint> Super; + + nsPoint() = default; + nsPoint(const nsPoint& aPoint) = default; + nsPoint(nscoord aX, nscoord aY) : Super(aX, aY) {} + + inline nsIntPoint ScaleToNearestPixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + inline nsIntPoint ToNearestPixels(nscoord aAppUnitsPerPixel) const; + + /** + * Return this point scaled to a different appunits per pixel (APP) ratio. + * @param aFromAPP the APP to scale from + * @param aToAPP the APP to scale to + */ + [[nodiscard]] inline nsPoint ScaleToOtherAppUnits(int32_t aFromAPP, + int32_t aToAPP) const; + + [[nodiscard]] inline nsPoint RemoveResolution(const float resolution) const; + [[nodiscard]] inline nsPoint ApplyResolution(const float resolution) const; +}; + +inline nsPoint ToAppUnits(const nsIntPoint& aPoint, nscoord aAppUnitsPerPixel); + +inline nsIntPoint nsPoint::ScaleToNearestPixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const { + return nsIntPoint( + NSToIntRoundUp(NSAppUnitsToDoublePixels(x, aAppUnitsPerPixel) * aXScale), + NSToIntRoundUp(NSAppUnitsToDoublePixels(y, aAppUnitsPerPixel) * aYScale)); +} + +inline nsIntPoint nsPoint::ToNearestPixels(nscoord aAppUnitsPerPixel) const { + return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel); +} + +inline nsPoint nsPoint::ScaleToOtherAppUnits(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP != aToAPP) { + nsPoint point; + point.x = NSToCoordRound(NSCoordScale(x, aFromAPP, aToAPP)); + point.y = NSToCoordRound(NSCoordScale(y, aFromAPP, aToAPP)); + return point; + } + return *this; +} + +inline nsPoint nsPoint::RemoveResolution(const float resolution) const { + if (resolution != 1.0f) { + nsPoint point; + point.x = NSToCoordRound(NSCoordToFloat(x) / resolution); + point.y = NSToCoordRound(NSCoordToFloat(y) / resolution); + return point; + } + return *this; +} + +inline nsPoint nsPoint::ApplyResolution(const float resolution) const { + if (resolution != 1.0f) { + nsPoint point; + point.x = NSToCoordRound(NSCoordToFloat(x) * resolution); + point.y = NSToCoordRound(NSCoordToFloat(y) * resolution); + return point; + } + return *this; +} + +// app units are integer multiples of pixels, so no rounding needed +inline nsPoint ToAppUnits(const nsIntPoint& aPoint, nscoord aAppUnitsPerPixel) { + return nsPoint(NSIntPixelsToAppUnits(aPoint.x, aAppUnitsPerPixel), + NSIntPixelsToAppUnits(aPoint.y, aAppUnitsPerPixel)); +} + +#endif /* NSPOINT_H */ diff --git a/gfx/src/nsRect.cpp b/gfx/src/nsRect.cpp new file mode 100644 index 0000000000..8f88c9190e --- /dev/null +++ b/gfx/src/nsRect.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRect.h" +#include "mozilla/gfx/Types.h" // for eSideBottom, etc +#include "mozilla/CheckedInt.h" // for CheckedInt +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsString.h" // for nsAutoString, etc +#include "nsMargin.h" // for nsMargin + +static_assert( + (int(mozilla::eSideTop) == 0) && (int(mozilla::eSideRight) == 1) && + (int(mozilla::eSideBottom) == 2) && (int(mozilla::eSideLeft) == 3), + "The mozilla::Side sequence must match the nsMargin nscoord sequence"); + +const mozilla::gfx::IntRect& GetMaxSizedIntRect() { + static const mozilla::gfx::IntRect r(0, 0, INT32_MAX, INT32_MAX); + return r; +} + +bool nsRect::Overflows() const { + 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(); +} diff --git a/gfx/src/nsRect.h b/gfx/src/nsRect.h new file mode 100644 index 0000000000..ccf48f0d9e --- /dev/null +++ b/gfx/src/nsRect.h @@ -0,0 +1,490 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSRECT_H +#define NSRECT_H + +#include <stdint.h> // for int32_t, int64_t +#include <algorithm> // for min/max +#include "mozilla/Likely.h" // for MOZ_UNLIKELY +#include "mozilla/gfx/BaseRect.h" +#include "mozilla/gfx/Rect.h" +#include "nsCoord.h" // for nscoord, etc +#include "nsISupports.h" // for MOZ_COUNT_CTOR, etc +#include "nsPoint.h" // for nsIntPoint, nsPoint +#include "nsSize.h" // for IntSize, nsSize +#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) +# if defined(_MSC_VER) && !defined(__clang__) +# include "smmintrin.h" +# else +# include "emmintrin.h" +# endif +#endif + +struct nsMargin; + +typedef mozilla::gfx::IntRect nsIntRect; + +struct nsRect : public mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, + nsMargin> { + typedef mozilla::gfx::BaseRect<nscoord, nsRect, nsPoint, nsSize, nsMargin> + Super; + + // Constructors + nsRect() { MOZ_COUNT_CTOR(nsRect); } + nsRect(const nsRect& aRect) : Super(aRect) { MOZ_COUNT_CTOR(nsRect); } + nsRect(const nsPoint& aOrigin, const nsSize& aSize) : Super(aOrigin, aSize) { + MOZ_COUNT_CTOR(nsRect); + } + nsRect(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight) + : Super(aX, aY, aWidth, aHeight) { + MOZ_COUNT_CTOR(nsRect); + } + nsRect& operator=(const nsRect&) = default; + + MOZ_COUNTED_DTOR(nsRect) + + // We have saturating versions of all the Union methods. These avoid + // overflowing nscoord values in the 'width' and 'height' fields by + // clamping the width and height values to nscoord_MAX if necessary. + + // Returns the smallest rectangle that contains both the area of both + // this and aRect. Thus, empty input rectangles are ignored. + // Note: if both rectangles are empty, returns aRect. + [[nodiscard]] nsRect SaturatingUnion(const nsRect& aRect) const { + if (IsEmpty()) { + return aRect; + } else if (aRect.IsEmpty()) { + return *static_cast<const nsRect*>(this); + } else { + return SaturatingUnionEdges(aRect); + } + } + + [[nodiscard]] nsRect SaturatingUnionEdges(const nsRect& aRect) const { + nscoord resultX = std::min(aRect.X(), x); + int64_t w = + std::max(int64_t(aRect.X()) + aRect.Width(), int64_t(x) + width) - + resultX; + if (MOZ_UNLIKELY(w > nscoord_MAX)) { + // Clamp huge negative x to nscoord_MIN / 2 and try again. + resultX = std::max(resultX, nscoord_MIN / 2); + w = std::max(int64_t(aRect.X()) + aRect.Width(), int64_t(x) + width) - + resultX; + if (MOZ_UNLIKELY(w > nscoord_MAX)) { + w = nscoord_MAX; + } + } + + nscoord resultY = std::min(aRect.y, y); + int64_t h = + std::max(int64_t(aRect.Y()) + aRect.Height(), int64_t(y) + height) - + resultY; + if (MOZ_UNLIKELY(h > nscoord_MAX)) { + // Clamp huge negative y to nscoord_MIN / 2 and try again. + resultY = std::max(resultY, nscoord_MIN / 2); + h = std::max(int64_t(aRect.Y()) + aRect.Height(), int64_t(y) + height) - + resultY; + if (MOZ_UNLIKELY(h > nscoord_MAX)) { + h = nscoord_MAX; + } + } + return nsRect(resultX, resultY, nscoord(w), nscoord(h)); + } + + // Make all nsRect Union methods be saturating. + [[nodiscard]] nsRect UnionEdges(const nsRect& aRect) const { + return SaturatingUnionEdges(aRect); + } + [[nodiscard]] nsRect Union(const nsRect& aRect) const { + return SaturatingUnion(aRect); + } + [[nodiscard]] nsRect UnsafeUnion(const nsRect& aRect) const { + return Super::Union(aRect); + } + void UnionRect(const nsRect& aRect1, const nsRect& aRect2) { + *this = aRect1.Union(aRect2); + } + +#if defined(_MSC_VER) && !defined(__clang__) && \ + (defined(_M_X64) || defined(_M_IX86)) + // Only MSVC supports inlining intrinsics for archs you're not compiling for. + [[nodiscard]] nsRect Intersect(const nsRect& aRect) const { + nsRect result; + if (mozilla::gfx::Factory::HasSSE4()) { + __m128i rect1 = _mm_loadu_si128((__m128i*)&aRect); // x1, y1, w1, h1 + __m128i rect2 = _mm_loadu_si128((__m128i*)this); // x2, y2, w2, h2 + + __m128i resultRect = _mm_max_epi32(rect1, rect2); // xr, yr, zz, zz + + // result.width = std::min<int32_t>(x - result.x + width, + // aRect.x - result.x + aRect.width); + // result.height = std::min<int32_t>(y - result.y + height, + // aRect.y - result.y + aRect.height); + __m128i widthheight = _mm_min_epi32( + _mm_add_epi32(_mm_sub_epi32(rect1, resultRect), + _mm_srli_si128(rect1, 8)), + _mm_add_epi32(_mm_sub_epi32(rect2, resultRect), + _mm_srli_si128(rect2, 8))); // w, h, zz, zz + widthheight = _mm_slli_si128(widthheight, 8); // 00, 00, wr, hr + + resultRect = + _mm_blend_epi16(resultRect, widthheight, 0xF0); // xr, yr, wr, hr + + if ((_mm_movemask_ps(_mm_castsi128_ps( + _mm_cmplt_epi32(resultRect, _mm_setzero_si128()))) & + 0xC) != 0) { + // It's potentially more efficient to store all 0s. But the non SSE4 + // code leaves x/y intact so let's do the same here. + resultRect = _mm_and_si128(resultRect, + _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF)); + } + + _mm_storeu_si128((__m128i*)&result, resultRect); + + return result; + } + + result.x = std::max<int32_t>(x, aRect.x); + result.y = std::max<int32_t>(y, aRect.y); + result.width = std::min<int32_t>(x - result.x + width, + aRect.x - result.x + aRect.width); + result.height = std::min<int32_t>(y - result.y + height, + aRect.y - result.y + aRect.height); + if (result.width < 0 || result.height < 0) { + result.SizeTo(0, 0); + } + return result; + } + + bool IntersectRect(const nsRect& aRect1, const nsRect& aRect2) { + if (mozilla::gfx::Factory::HasSSE4()) { + __m128i rect1 = _mm_loadu_si128((__m128i*)&aRect1); // x1, y1, w1, h1 + __m128i rect2 = _mm_loadu_si128((__m128i*)&aRect2); // x2, y2, w2, h2 + + __m128i resultRect = _mm_max_epi32(rect1, rect2); // xr, yr, zz, zz + // result.width = std::min<int32_t>(x - result.x + width, + // aRect.x - result.x + aRect.width); + // result.height = std::min<int32_t>(y - result.y + height, + // aRect.y - result.y + aRect.height); + __m128i widthheight = _mm_min_epi32( + _mm_add_epi32(_mm_sub_epi32(rect1, resultRect), + _mm_srli_si128(rect1, 8)), + _mm_add_epi32(_mm_sub_epi32(rect2, resultRect), + _mm_srli_si128(rect2, 8))); // w, h, zz, zz + widthheight = _mm_slli_si128(widthheight, 8); // 00, 00, wr, hr + + resultRect = + _mm_blend_epi16(resultRect, widthheight, 0xF0); // xr, yr, wr, hr + + if ((_mm_movemask_ps(_mm_castsi128_ps( + _mm_cmpgt_epi32(resultRect, _mm_setzero_si128()))) & + 0xC) != 0xC) { + // It's potentially more efficient to store all 0s. But the non SSE4 + // code leaves x/y intact so let's do the same here. + resultRect = _mm_and_si128(resultRect, + _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF)); + _mm_storeu_si128((__m128i*)this, resultRect); + return false; + } + + _mm_storeu_si128((__m128i*)this, resultRect); + + return true; + } + + int32_t newX = std::max<int32_t>(aRect1.x, aRect2.x); + int32_t newY = std::max<int32_t>(aRect1.y, aRect2.y); + width = std::min<int32_t>(aRect1.x - newX + aRect1.width, + aRect2.x - newX + aRect2.width); + height = std::min<int32_t>(aRect1.y - newY + aRect1.height, + aRect2.y - newY + aRect2.height); + x = newX; + y = newY; + if (width <= 0 || height <= 0) { + SizeTo(0, 0); + return false; + } + return true; + } +#endif + + // Return whether this rect's right or bottom edge overflow int32. + bool Overflows() const; + + /** + * Return this rect scaled to a different appunits per pixel (APP) ratio. + * In the RoundOut version we make the rect the smallest rect containing the + * unrounded result. In the RoundIn version we make the rect the largest rect + * contained in the unrounded result. + * @param aFromAPP the APP to scale from + * @param aToAPP the APP to scale to + * @note this can turn an empty rectangle into a non-empty rectangle + */ + [[nodiscard]] inline nsRect ScaleToOtherAppUnitsRoundOut( + int32_t aFromAPP, int32_t aToAPP) const; + [[nodiscard]] inline nsRect ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, + int32_t aToAPP) const; + + [[nodiscard]] inline mozilla::gfx::IntRect ScaleToNearestPixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const; + + [[nodiscard]] inline mozilla::gfx::IntRect ToNearestPixels( + nscoord aAppUnitsPerPixel) const; + + // Note: this can turn an empty rectangle into a non-empty rectangle + [[nodiscard]] inline mozilla::gfx::IntRect ScaleToOutsidePixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const; + + // Note: this can turn an empty rectangle into a non-empty rectangle + [[nodiscard]] inline mozilla::gfx::IntRect ToOutsidePixels( + nscoord aAppUnitsPerPixel) const; + + [[nodiscard]] inline mozilla::gfx::IntRect ScaleToInsidePixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const; + + [[nodiscard]] inline mozilla::gfx::IntRect ToInsidePixels( + nscoord aAppUnitsPerPixel) const; + + // This is here only to keep IPDL-generated code happy. DO NOT USE. + bool operator==(const nsRect& aRect) const { return IsEqualEdges(aRect); } + + [[nodiscard]] inline nsRect RemoveResolution(const float aResolution) const; + + [[nodiscard]] mozilla::Maybe<nsRect> EdgeInclusiveIntersection( + const nsRect& aOther) const { + nscoord left = std::max(x, aOther.x); + nscoord top = std::max(y, aOther.y); + nscoord right = std::min(XMost(), aOther.XMost()); + nscoord bottom = std::min(YMost(), aOther.YMost()); + if (left > right || top > bottom) { + return mozilla::Nothing(); + } + return mozilla::Some(nsRect(left, top, right - left, bottom - top)); + } +}; + +/* + * App Unit/Pixel conversions + */ + +inline nsRect nsRect::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP == aToAPP) { + return *this; + } + + nsRect rect; + rect.SetBox(NSToCoordFloor(NSCoordScale(x, aFromAPP, aToAPP)), + NSToCoordFloor(NSCoordScale(y, aFromAPP, aToAPP)), + NSToCoordCeil(NSCoordScale(XMost(), aFromAPP, aToAPP)), + NSToCoordCeil(NSCoordScale(YMost(), aFromAPP, aToAPP))); + return rect; +} + +inline nsRect nsRect::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP == aToAPP) { + return *this; + } + + nsRect rect; + rect.SetBox(NSToCoordCeil(NSCoordScale(x, aFromAPP, aToAPP)), + NSToCoordCeil(NSCoordScale(y, aFromAPP, aToAPP)), + NSToCoordFloor(NSCoordScale(XMost(), aFromAPP, aToAPP)), + NSToCoordFloor(NSCoordScale(YMost(), aFromAPP, aToAPP))); + return rect; +} + +#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) +// Life would be so much better if we had SSE4 here. +static MOZ_ALWAYS_INLINE __m128i floor_ps2epi32(__m128 x) { + __m128 one = _mm_set_ps(1.0f, 1.0f, 1.0f, 1.0f); + + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(x)); + __m128 r = _mm_sub_ps(t, _mm_and_ps(_mm_cmplt_ps(x, t), one)); + + return _mm_cvttps_epi32(r); +} + +static MOZ_ALWAYS_INLINE __m128i ceil_ps2epi32(__m128 x) { + __m128 t = _mm_sub_ps(_mm_setzero_ps(), x); + __m128i r = _mm_sub_epi32(_mm_setzero_si128(), floor_ps2epi32(t)); + + return r; +} +#endif + +// scale the rect but round to preserve centers +inline mozilla::gfx::IntRect nsRect::ScaleToNearestPixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const { + mozilla::gfx::IntRect rect; + // Android x86 builds have bindgen issues. +#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) + __m128 appUnitsPacked = _mm_set_ps(aAppUnitsPerPixel, aAppUnitsPerPixel, + aAppUnitsPerPixel, aAppUnitsPerPixel); + __m128 scalesPacked = _mm_set_ps(aYScale, aXScale, aYScale, aXScale); + __m128 biasesPacked = _mm_set_ps(0.5f, 0.5f, 0.5f, 0.5f); + + __m128i rectPacked = _mm_loadu_si128((__m128i*)this); + __m128i topLeft = _mm_slli_si128(rectPacked, 8); + + rectPacked = _mm_add_epi32(rectPacked, topLeft); // X, Y, XMost(), YMost() + + __m128 rectFloat = _mm_cvtepi32_ps(rectPacked); + + // Scale, i.e. ([ x y xmost ymost ] / aAppUnitsPerPixel) * [ aXScale aYScale + // aXScale aYScale ] + rectFloat = _mm_mul_ps(_mm_div_ps(rectFloat, appUnitsPacked), scalesPacked); + + // Floor + // Executed with bias and roundmode down, since round-nearest rounds 0.5 + // downward half the time. + rectFloat = _mm_add_ps(rectFloat, biasesPacked); + rectPacked = floor_ps2epi32(rectFloat); + + topLeft = _mm_slli_si128(rectPacked, 8); + rectPacked = _mm_sub_epi32(rectPacked, topLeft); // X, Y, Width, Height + + // Avoid negative width/height due to overflow. + __m128i mask = _mm_or_si128(_mm_cmpgt_epi32(rectPacked, _mm_setzero_si128()), + _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF)); + // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF) + // (height <= 0 ? 0 : 0xFFFFFFFF) ] + rectPacked = _mm_and_si128(rectPacked, mask); + + _mm_storeu_si128((__m128i*)&rect, rectPacked); +#else + rect.SetNonEmptyBox( + NSToIntRoundUp(NSAppUnitsToFloatPixels(x, aAppUnitsPerPixel) * aXScale), + NSToIntRoundUp(NSAppUnitsToFloatPixels(y, aAppUnitsPerPixel) * aYScale), + NSToIntRoundUp(NSAppUnitsToFloatPixels(XMost(), aAppUnitsPerPixel) * + aXScale), + NSToIntRoundUp(NSAppUnitsToFloatPixels(YMost(), aAppUnitsPerPixel) * + aYScale)); +#endif + return rect; +} + +// scale the rect but round to smallest containing rect +inline mozilla::gfx::IntRect nsRect::ScaleToOutsidePixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const { + mozilla::gfx::IntRect rect; + // Android x86 builds have bindgen issues. +#if !defined(ANDROID) && (defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) + __m128 appUnitsPacked = _mm_set_ps(aAppUnitsPerPixel, aAppUnitsPerPixel, + aAppUnitsPerPixel, aAppUnitsPerPixel); + __m128 scalesPacked = _mm_set_ps(aYScale, aXScale, aYScale, aXScale); + + __m128i rectPacked = _mm_loadu_si128((__m128i*)this); // x, y, w, h + __m128i topLeft = _mm_slli_si128(rectPacked, 8); // 0, 0, x, y + + rectPacked = _mm_add_epi32(rectPacked, topLeft); // X, Y, XMost(), YMost() + + __m128 rectFloat = _mm_cvtepi32_ps(rectPacked); + + // Scale i.e. ([ x y xmost ymost ] / aAppUnitsPerPixel) * + // [ aXScale aYScale aXScale aYScale ] + rectFloat = _mm_mul_ps(_mm_div_ps(rectFloat, appUnitsPacked), scalesPacked); + rectPacked = ceil_ps2epi32(rectFloat); // xx, xx, XMost(), YMost() + __m128i tmp = floor_ps2epi32(rectFloat); // x, y, xx, xx + + // _mm_move_sd is 1 cycle method of getting the blending we want. + rectPacked = _mm_castpd_si128( + _mm_move_sd(_mm_castsi128_pd(rectPacked), + _mm_castsi128_pd(tmp))); // x, y, XMost(), YMost() + + topLeft = _mm_slli_si128(rectPacked, 8); // 0, 0, r.x, r.y + rectPacked = _mm_sub_epi32(rectPacked, topLeft); // r.x, r.y, r.w, r.h + + // Avoid negative width/height due to overflow. + __m128i mask = _mm_or_si128(_mm_cmpgt_epi32(rectPacked, _mm_setzero_si128()), + _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF)); + // clang-format off + // Mask will now contain [ 0xFFFFFFFF 0xFFFFFFFF (width <= 0 ? 0 : 0xFFFFFFFF) (height <= 0 ? 0 : 0xFFFFFFFF) ] + // clang-format on + rectPacked = _mm_and_si128(rectPacked, mask); + + _mm_storeu_si128((__m128i*)&rect, rectPacked); +#else + rect.SetNonEmptyBox( + NSToIntFloor(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) * + aXScale), + NSToIntFloor(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) * + aYScale), + NSToIntCeil(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel)) * + aXScale), + NSToIntCeil(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel)) * + aYScale)); +#endif + return rect; +} + +// scale the rect but round to largest contained rect +inline mozilla::gfx::IntRect nsRect::ScaleToInsidePixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const { + mozilla::gfx::IntRect rect; + rect.SetNonEmptyBox( + NSToIntCeil(NSAppUnitsToFloatPixels(x, float(aAppUnitsPerPixel)) * + aXScale), + NSToIntCeil(NSAppUnitsToFloatPixels(y, float(aAppUnitsPerPixel)) * + aYScale), + NSToIntFloor(NSAppUnitsToFloatPixels(XMost(), float(aAppUnitsPerPixel)) * + aXScale), + NSToIntFloor(NSAppUnitsToFloatPixels(YMost(), float(aAppUnitsPerPixel)) * + aYScale)); + return rect; +} + +inline mozilla::gfx::IntRect nsRect::ToNearestPixels( + nscoord aAppUnitsPerPixel) const { + return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel); +} + +inline mozilla::gfx::IntRect nsRect::ToOutsidePixels( + nscoord aAppUnitsPerPixel) const { + return ScaleToOutsidePixels(1.0f, 1.0f, aAppUnitsPerPixel); +} + +inline mozilla::gfx::IntRect nsRect::ToInsidePixels( + nscoord aAppUnitsPerPixel) const { + return ScaleToInsidePixels(1.0f, 1.0f, aAppUnitsPerPixel); +} + +inline nsRect nsRect::RemoveResolution(const float aResolution) const { + MOZ_ASSERT(aResolution > 0.0f); + nsRect rect; + rect.MoveTo(NSToCoordRound(NSCoordToFloat(x) / aResolution), + NSToCoordRound(NSCoordToFloat(y) / aResolution)); + // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1 + // rect as well instead of possibly rounding the width or height to zero. + if (width == 1 && height == 1) { + rect.SizeTo(1, 1); + } else { + rect.SizeTo(NSToCoordCeil(NSCoordToFloat(width) / aResolution), + NSToCoordCeil(NSCoordToFloat(height) / aResolution)); + } + + return rect; +} + +const mozilla::gfx::IntRect& GetMaxSizedIntRect(); + +// app units are integer multiples of pixels, so no rounding needed +template <class units> +nsRect ToAppUnits(const mozilla::gfx::IntRectTyped<units>& aRect, + nscoord aAppUnitsPerPixel) { + return nsRect(NSIntPixelsToAppUnits(aRect.X(), aAppUnitsPerPixel), + NSIntPixelsToAppUnits(aRect.Y(), aAppUnitsPerPixel), + NSIntPixelsToAppUnits(aRect.Width(), aAppUnitsPerPixel), + NSIntPixelsToAppUnits(aRect.Height(), aAppUnitsPerPixel)); +} + +#endif /* NSRECT_H */ diff --git a/gfx/src/nsRectAbsolute.h b/gfx/src/nsRectAbsolute.h new file mode 100644 index 0000000000..6d9f718893 --- /dev/null +++ b/gfx/src/nsRectAbsolute.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSRECTABSOLUTE_H +#define NSRECTABSOLUTE_H + +#include "mozilla/gfx/RectAbsolute.h" +#include "nsCoord.h" +#include "nsMargin.h" +#include "nsRect.h" + +struct nsRectAbsolute + : public mozilla::gfx::BaseRectAbsolute<nscoord, nsRectAbsolute, nsPoint, + nsRect> { + typedef mozilla::gfx::BaseRectAbsolute<nscoord, nsRectAbsolute, nsPoint, + nsRect> + Super; + + nsRectAbsolute() {} + nsRectAbsolute(nscoord aX1, nscoord aY1, nscoord aX2, nscoord aY2) + : Super(aX1, aY1, aX2, aY2) {} + + MOZ_ALWAYS_INLINE nscoord SafeWidth() const { + int64_t width = right; + width -= left; + return nscoord( + std::min<int64_t>(std::numeric_limits<nscoord>::max(), width)); + } + MOZ_ALWAYS_INLINE nscoord SafeHeight() const { + int64_t height = bottom; + height -= top; + return nscoord( + std::min<int64_t>(std::numeric_limits<nscoord>::max(), height)); + } + + nsRect ToNSRect() const { + return nsRect(left, top, nscoord(SafeWidth()), nscoord(SafeHeight())); + } + + [[nodiscard]] nsRectAbsolute UnsafeUnion(const nsRectAbsolute& aRect) const { + return Super::Union(aRect); + } + + void Inflate(const nsMargin& aMargin) { + left -= aMargin.left; + top -= aMargin.top; + right += aMargin.right; + bottom += aMargin.bottom; + } +}; + +#endif /* NSRECTABSOLUTE_H */ diff --git a/gfx/src/nsRegion.cpp b/gfx/src/nsRegion.cpp new file mode 100644 index 0000000000..aa50d44fc4 --- /dev/null +++ b/gfx/src/nsRegion.cpp @@ -0,0 +1,1024 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRegion.h" +#include "nsTArray.h" +#include "gfxUtils.h" +#include "gfx2DGlue.h" +#include "mozilla/ToString.h" + +void nsRegion::AssertStateInternal() const { + bool failed = false; + // Verify consistent state inside the region. + int32_t lastY = INT32_MIN; + int32_t lowestX = INT32_MAX; + int32_t highestX = INT32_MIN; + for (auto iter = mBands.begin(); iter != mBands.end(); iter++) { + const Band& band = *iter; + if (band.bottom <= band.top) { + failed = true; + break; + } + if (band.top < lastY) { + failed = true; + break; + } + lastY = band.bottom; + + lowestX = std::min(lowestX, band.mStrips.begin()->left); + highestX = std::max(highestX, band.mStrips.LastElement().right); + + int32_t lastX = INT32_MIN; + if (iter != mBands.begin()) { + auto prev = iter; + prev--; + + if (prev->bottom == iter->top) { + if (band.EqualStrips(*prev)) { + failed = true; + break; + } + } + } + for (const Strip& strip : band.mStrips) { + if (strip.right <= strip.left) { + failed = true; + break; + } + if (strip.left <= lastX) { + failed = true; + break; + } + lastX = strip.right; + } + if (failed) { + break; + } + } + + if (!(mBounds.IsEqualEdges(CalculateBounds()))) { + failed = true; + } + + if (failed) { +#ifdef DEBUG_REGIONS + if (mCurrentOpGenerator) { + mCurrentOpGenerator->OutputOp(); + } +#endif + MOZ_ASSERT(false); + } +} + +bool nsRegion::Contains(const nsRegion& aRgn) const { + // XXX this could be made faster by iterating over + // both regions at the same time some how + for (auto iter = aRgn.RectIter(); !iter.Done(); iter.Next()) { + if (!Contains(iter.Get())) { + return false; + } + } + return true; +} + +bool nsRegion::Intersects(const nsRectAbsolute& aRect) const { + if (mBands.IsEmpty()) { + return mBounds.Intersects(aRect); + } + + if (!mBounds.Intersects(aRect)) { + return false; + } + + Strip rectStrip(aRect.X(), aRect.XMost()); + + auto iter = mBands.begin(); + while (iter != mBands.end()) { + if (iter->top >= aRect.YMost()) { + return false; + } + + if (iter->bottom <= aRect.Y()) { + // This band is entirely before aRect, move on. + iter++; + continue; + } + + if (!iter->Intersects(rectStrip)) { + // This band does not intersect aRect horizontally. Move on. + iter++; + continue; + } + + // This band intersects with aRect. + return true; + } + + return false; +} + +void nsRegion::Inflate(const nsMargin& aMargin) { + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRectAbsolute rect = iter.GetAbsolute(); + rect.Inflate(aMargin); + newRegion.AddRect(rect); + } + + *this = std::move(newRegion); +} + +void nsRegion::SimplifyOutward(uint32_t aMaxRects) { + MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count"); + + if (GetNumRects() <= aMaxRects) { + return; + } + + // Try combining rects in horizontal bands into a single rect + // The goal here is to try to keep groups of rectangles that are vertically + // discontiguous as separate rectangles in the final region. This is + // simple and fast to implement and page contents tend to vary more + // vertically than horizontally (which is why our rectangles are stored + // sorted by y-coordinate, too). + // + // Note: if boxes share y1 because of the canonical representation they + // will share y2 + + size_t idx = 0; + + while (idx < mBands.Length()) { + size_t oldIdx = idx; + mBands[idx].mStrips.begin()->right = + mBands[idx].mStrips.LastElement().right; + mBands[idx].mStrips.TruncateLength(1); + idx++; + + // Merge any bands with the same bounds. + while (idx < mBands.Length() && + mBands[idx].mStrips.begin()->left == + mBands[oldIdx].mStrips.begin()->left && + mBands[idx].mStrips.LastElement().right == + mBands[oldIdx].mStrips.begin()->right) { + mBands[oldIdx].bottom = mBands[idx].bottom; + mBands.RemoveElementAt(idx); + } + } + + AssertState(); + + // mBands.size() is now equal to our rect count. + if (mBands.Length() > aMaxRects) { + *this = GetBounds(); + } +} + +// compute the covered area difference between two rows. +// by iterating over both rows simultaneously and adding up +// the additional increase in area caused by extending each +// of the rectangles to the combined height of both rows +uint32_t nsRegion::ComputeMergedAreaIncrease(const Band& aTopBand, + const Band& aBottomBand) { + uint32_t totalArea = 0; + + uint32_t topHeight = aBottomBand.top - aTopBand.top; + uint32_t bottomHeight = aBottomBand.bottom - aTopBand.bottom; + uint32_t currentStripBottom = 0; + + // This could be done with slightly better worse case performance by merging + // these two for-loops, but this makes the code a lot easier to understand. + for (auto& strip : aTopBand.mStrips) { + if (currentStripBottom == aBottomBand.mStrips.Length() || + strip.right < aBottomBand.mStrips[currentStripBottom].left) { + totalArea += bottomHeight * strip.Size(); + continue; + } + + int32_t currentX = strip.left; + while (currentStripBottom != aBottomBand.mStrips.Length() && + aBottomBand.mStrips[currentStripBottom].left < strip.right) { + if (currentX >= strip.right) { + break; + } + if (currentX < aBottomBand.mStrips[currentStripBottom].left) { + // Add the part that's not intersecting. + totalArea += (aBottomBand.mStrips[currentStripBottom].left - currentX) * + bottomHeight; + } + + currentX = + std::max(aBottomBand.mStrips[currentStripBottom].right, currentX); + currentStripBottom++; + } + + // Add remainder of this strip. + if (currentX < strip.right) { + totalArea += (strip.right - currentX) * bottomHeight; + } + if (currentStripBottom) { + currentStripBottom--; + } + } + uint32_t currentStripTop = 0; + for (auto& strip : aBottomBand.mStrips) { + if (currentStripTop == aTopBand.mStrips.Length() || + strip.right < aTopBand.mStrips[currentStripTop].left) { + totalArea += topHeight * strip.Size(); + continue; + } + + int32_t currentX = strip.left; + while (currentStripTop != aTopBand.mStrips.Length() && + aTopBand.mStrips[currentStripTop].left < strip.right) { + if (currentX >= strip.right) { + break; + } + if (currentX < aTopBand.mStrips[currentStripTop].left) { + // Add the part that's not intersecting. + totalArea += + (aTopBand.mStrips[currentStripTop].left - currentX) * topHeight; + } + + currentX = std::max(aTopBand.mStrips[currentStripTop].right, currentX); + currentStripTop++; + } + + // Add remainder of this strip. + if (currentX < strip.right) { + totalArea += (strip.right - currentX) * topHeight; + } + if (currentStripTop) { + currentStripTop--; + } + } + return totalArea; +} + +void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) { + if (mBands.Length() < 2) { + // We have only one or no row and we're done. + return; + } + + uint32_t currentBand = 0; + do { + Band& band = mBands[currentBand]; + + uint32_t totalArea = + ComputeMergedAreaIncrease(band, mBands[currentBand + 1]); + + if (totalArea <= aThreshold) { + for (Strip& strip : mBands[currentBand + 1].mStrips) { + // This could use an optimized function to merge two bands. + band.InsertStrip(strip); + } + band.bottom = mBands[currentBand + 1].bottom; + mBands.RemoveElementAt(currentBand + 1); + } else { + currentBand++; + } + } while (currentBand + 1 < mBands.Length()); + + EnsureSimplified(); + AssertState(); +} + +typedef void (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2, + int y2); + +void nsRegion::VisitEdges(visit_fn visit, void* closure) const { + if (mBands.IsEmpty()) { + visit(closure, VisitSide::LEFT, mBounds.X(), mBounds.Y(), mBounds.X(), + mBounds.YMost()); + visit(closure, VisitSide::RIGHT, mBounds.XMost(), mBounds.Y(), + mBounds.XMost(), mBounds.YMost()); + visit(closure, VisitSide::TOP, mBounds.X() - 1, mBounds.Y(), + mBounds.XMost() + 1, mBounds.Y()); + visit(closure, VisitSide::BOTTOM, mBounds.X() - 1, mBounds.YMost(), + mBounds.XMost() + 1, mBounds.YMost()); + return; + } + + auto band = std::begin(mBands); + auto bandFinal = std::end(mBands); + bandFinal--; + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left, + band->bottom); + visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right, + band->bottom); + visit(closure, VisitSide::TOP, strip.left - 1, band->top, strip.right + 1, + band->top); + } + + if (band != bandFinal) { + do { + const Band& topBand = *band; + band++; + + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left, + band->bottom); + visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right, + band->bottom); + } + + if (band->top == topBand.bottom) { + // Two bands touching each other vertically. + const Band& bottomBand = *band; + auto topStrip = std::begin(topBand.mStrips); + auto bottomStrip = std::begin(bottomBand.mStrips); + + int y = topBand.bottom; + + // State from this point on along the vertical edge: + // 0 - Empty + // 1 - Touched by top rect + // 2 - Touched by bottom rect + // 3 - Touched on both sides + int state; + const int TouchedByNothing = 0; + const int TouchedByTop = 1; + const int TouchedByBottom = 2; + // We always start with nothing. + int oldState = TouchedByNothing; + // Last state change, adjusted by -1 if the last state change was + // a change away from 0. + int lastX = std::min(topStrip->left, bottomStrip->left) - 1; + + // Current edge being considered for top and bottom, + // 0 - left, 1 - right. + bool topEdgeIsLeft = true; + bool bottomEdgeIsLeft = true; + while (topStrip != std::end(topBand.mStrips) && + bottomStrip != std::end(bottomBand.mStrips)) { + int topPos; + int bottomPos; + if (topEdgeIsLeft) { + topPos = topStrip->left; + } else { + topPos = topStrip->right; + } + if (bottomEdgeIsLeft) { + bottomPos = bottomStrip->left; + } else { + bottomPos = bottomStrip->right; + } + + int currentX = std::min(topPos, bottomPos); + if (topPos < bottomPos) { + if (topEdgeIsLeft) { + state = oldState | TouchedByTop; + } else { + state = oldState ^ TouchedByTop; + topStrip++; + } + topEdgeIsLeft = !topEdgeIsLeft; + } else if (bottomPos < topPos) { + if (bottomEdgeIsLeft) { + state = oldState | TouchedByBottom; + } else { + state = oldState ^ TouchedByBottom; + bottomStrip++; + } + bottomEdgeIsLeft = !bottomEdgeIsLeft; + } else { + // bottomPos == topPos + state = TouchedByNothing; + if (bottomEdgeIsLeft) { + state = TouchedByBottom; + } else { + bottomStrip++; + } + if (topEdgeIsLeft) { + state |= TouchedByTop; + } else { + topStrip++; + } + topEdgeIsLeft = !topEdgeIsLeft; + bottomEdgeIsLeft = !bottomEdgeIsLeft; + } + + MOZ_ASSERT(state != oldState); + if (oldState == TouchedByNothing) { + // We had nothing before, make sure the left edge will be padded. + lastX = currentX - 1; + } else if (oldState == TouchedByTop) { + if (state == TouchedByNothing) { + visit(closure, VisitSide::BOTTOM, lastX, y, currentX + 1, y); + } else { + visit(closure, VisitSide::BOTTOM, lastX, y, currentX, y); + lastX = currentX; + } + } else if (oldState == TouchedByBottom) { + if (state == TouchedByNothing) { + visit(closure, VisitSide::TOP, lastX, y, currentX + 1, y); + } else { + visit(closure, VisitSide::TOP, lastX, y, currentX, y); + lastX = currentX; + } + } else { + lastX = currentX; + } + oldState = state; + } + + MOZ_ASSERT(!state || (topEdgeIsLeft || bottomEdgeIsLeft)); + if (topStrip != std::end(topBand.mStrips)) { + if (!topEdgeIsLeft) { + visit(closure, VisitSide::BOTTOM, lastX, y, topStrip->right + 1, y); + topStrip++; + } + while (topStrip != std::end(topBand.mStrips)) { + visit(closure, VisitSide::BOTTOM, topStrip->left - 1, y, + topStrip->right + 1, y); + topStrip++; + } + } else if (bottomStrip != std::end(bottomBand.mStrips)) { + if (!bottomEdgeIsLeft) { + visit(closure, VisitSide::TOP, lastX, y, bottomStrip->right + 1, y); + bottomStrip++; + } + while (bottomStrip != std::end(bottomBand.mStrips)) { + visit(closure, VisitSide::TOP, bottomStrip->left - 1, y, + bottomStrip->right + 1, y); + bottomStrip++; + } + } + } else { + for (const Strip& strip : topBand.mStrips) { + visit(closure, VisitSide::BOTTOM, strip.left - 1, topBand.bottom, + strip.right + 1, topBand.bottom); + } + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::TOP, strip.left - 1, band->top, + strip.right + 1, band->top); + } + } + } while (band != bandFinal); + } + + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::BOTTOM, strip.left - 1, band->bottom, + strip.right + 1, band->bottom); + } +} + +void nsRegion::SimplifyInward(uint32_t aMaxRects) { + NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count"); + + if (GetNumRects() <= aMaxRects) return; + + SetEmpty(); +} + +uint64_t nsRegion::Area() const { + if (mBands.IsEmpty()) { + return mBounds.Area(); + } + + uint64_t area = 0; + for (const Band& band : mBands) { + uint32_t height = band.bottom - band.top; + for (const Strip& strip : band.mStrips) { + area += (strip.right - strip.left) * height; + } + } + + return area; +} + +nsRegion& nsRegion::ScaleRoundOut(float aXScale, float aYScale) { + if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) && + mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) { + return *this; + } + + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRectAbsolute rect = iter.GetAbsolute(); + rect.ScaleRoundOut(aXScale, aYScale); + newRegion.AddRect(rect); + } + + *this = std::move(newRegion); + return *this; +} + +nsRegion& nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) { + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRectAbsolute rect = iter.GetAbsolute(); + rect.ScaleInverseRoundOut(aXScale, aYScale); + newRegion.AddRect(rect); + } + + *this = std::move(newRegion); + return *this; +} + +static mozilla::gfx::IntRect TransformRect( + const mozilla::gfx::IntRect& aRect, + const mozilla::gfx::Matrix4x4& aTransform) { + if (aRect.IsEmpty()) { + return mozilla::gfx::IntRect(); + } + + mozilla::gfx::RectDouble rect(aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + rect = aTransform.TransformAndClipBounds( + rect, mozilla::gfx::RectDouble::MaxIntRect()); + rect.RoundOut(); + + mozilla::gfx::IntRect intRect; + if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) { + return mozilla::gfx::IntRect(); + } + + return intRect; +} + +nsRegion& nsRegion::Transform(const mozilla::gfx::Matrix4x4& aTransform) { + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = nsIntRegion::ToRect( + TransformRect(nsIntRegion::FromRect(iter.Get()), aTransform)); + newRegion.AddRect(nsRectAbsolute::FromRect(rect)); + } + + *this = std::move(newRegion); + return *this; +} + +nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP == aToAPP) { + return *this; + } + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP); + newRegion.AddRect(nsRectAbsolute::FromRect(rect)); + } + + return newRegion; +} + +nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP == aToAPP) { + return *this; + } + + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP); + newRegion.AddRect(nsRectAbsolute::FromRect(rect)); + } + + return newRegion; +} + +nsIntRegion nsRegion::ToPixels(nscoord aAppUnitsPerPixel, + bool aOutsidePixels) const { + nsIntRegion intRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + mozilla::gfx::IntRect deviceRect; + nsRect rect = iter.Get(); + if (aOutsidePixels) + deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel); + else + deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel); + intRegion.OrWith(deviceRect); + } + + return intRegion; +} + +nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const { + return ToPixels(aAppUnitsPerPixel, true); +} + +nsIntRegion nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel) const { + return ToPixels(aAppUnitsPerPixel, false); +} + +nsIntRegion nsRegion::ScaleToNearestPixels(float aScaleX, float aScaleY, + nscoord aAppUnitsPerPixel) const { + nsIntRegion result; + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + mozilla::gfx::IntRect deviceRect = + iter.Get().ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel); + result.Or(result, deviceRect); + } + return result; +} + +nsIntRegion nsRegion::ScaleToOutsidePixels(float aScaleX, float aScaleY, + nscoord aAppUnitsPerPixel) const { + // make a copy of the region so that we can mutate it inplace + nsIntRegion intRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + intRegion.OrWith( + rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel)); + } + return intRegion; +} + +nsIntRegion nsRegion::ScaleToInsidePixels(float aScaleX, float aScaleY, + nscoord aAppUnitsPerPixel) const { + /* When scaling a rect, walk forward through the rect list up until the y + * value is greater than the current rect's YMost() value. + * + * For each rect found, check if the rects have a touching edge (in unscaled + * coordinates), and if one edge is entirely contained within the other. + * + * If it is, then the contained edge can be moved (in scaled pixels) to ensure + * that no gap exists. + * + * Since this could be potentially expensive - O(n^2), we only attempt this + * algorithm for the first rect. + */ + + if (mBands.IsEmpty()) { + nsIntRect rect = mBounds.ToNSRect().ScaleToInsidePixels(aScaleX, aScaleY, + aAppUnitsPerPixel); + return nsIntRegion(rect); + } + + nsIntRegion intRegion; + RectIterator iter = RectIterator(*this); + + nsRect first = iter.Get(); + + mozilla::gfx::IntRect firstDeviceRect = + first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); + + for (iter.Next(); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + mozilla::gfx::IntRect deviceRect = + rect.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); + + if (rect.Y() <= first.YMost()) { + if (rect.XMost() == first.X() && rect.YMost() <= first.YMost()) { + // rect is touching on the left edge of the first rect and contained + // within the length of its left edge + deviceRect.SetRightEdge(firstDeviceRect.X()); + } else if (rect.X() == first.XMost() && rect.YMost() <= first.YMost()) { + // rect is touching on the right edge of the first rect and contained + // within the length of its right edge + deviceRect.SetLeftEdge(firstDeviceRect.XMost()); + } else if (rect.Y() == first.YMost()) { + // The bottom of the first rect is on the same line as the top of rect, + // but they aren't necessarily contained. + if (rect.X() <= first.X() && rect.XMost() >= first.XMost()) { + // The top of rect contains the bottom of the first rect + firstDeviceRect.SetBottomEdge(deviceRect.Y()); + } else if (rect.X() >= first.X() && rect.XMost() <= first.XMost()) { + // The bottom of the first contains the top of rect + deviceRect.SetTopEdge(firstDeviceRect.YMost()); + } + } + } + + intRegion.OrWith(deviceRect); + } + + intRegion.OrWith(firstDeviceRect); + return intRegion; +} + +// A cell's "value" is a pair consisting of +// a) the area of the subrectangle it corresponds to, if it's in +// aContainingRect and in the region, 0 otherwise +// b) the area of the subrectangle it corresponds to, if it's in the region, +// 0 otherwise +// Addition, subtraction and identity are defined on these values in the +// obvious way. Partial order is lexicographic. +// A "large negative value" is defined with large negative numbers for both +// fields of the pair. This negative value has the property that adding any +// number of non-negative values to it always results in a negative value. +// +// The GetLargestRectangle algorithm works in three phases: +// 1) Convert the region into a grid by adding vertical/horizontal lines for +// each edge of each rectangle in the region. +// 2) For each rectangle in the region, for each cell it contains, set that +// cells's value as described above. +// 3) Calculate the submatrix with the largest sum such that none of its cells +// contain any 0s (empty regions). The rectangle represented by the +// submatrix is the largest rectangle in the region. +// +// Let k be the number of rectangles in the region. +// Let m be the height of the grid generated in step 1. +// Let n be the width of the grid generated in step 1. +// +// Step 1 is O(k) in time and O(m+n) in space for the sparse grid. +// Step 2 is O(mn) in time and O(mn) in additional space for the full grid. +// Step 3 is O(m^2 n) in time and O(mn) in additional space +// +// The implementation of steps 1 and 2 are rather straightforward. However our +// implementation of step 3 uses dynamic programming to achieve its efficiency. +// +// Psuedo code for step 3 is as follows where G is the grid from step 1 and A +// is the array from step 2: +// Phase3 = function (G, A, m, n) { +// let (t,b,l,r,_) = MaxSum2D(A,m,n) +// return rect(G[t],G[l],G[r],G[b]); +// } +// MaxSum2D = function (A, m, n) { +// S = array(m+1,n+1) +// S[0][i] = 0 for i in [0,n] +// S[j][0] = 0 for j in [0,m] +// S[j][i] = (if A[j-1][i-1] = 0 then some large negative value +// else A[j-1][i-1]) +// + S[j-1][n] + S[j][i-1] - S[j-1][i-1] +// +// // top, bottom, left, right, area +// var maxRect = (-1, -1, -1, -1, 0); +// +// for all (m',m'') in [0, m]^2 { +// let B = { S[m'][i] - S[m''][i] | 0 <= i <= n } +// let ((l,r),area) = MaxSum1D(B,n+1) +// if (area > maxRect.area) { +// maxRect := (m', m'', l, r, area) +// } +// } +// +// return maxRect; +// } +// +// Originally taken from Improved algorithms for the k-maximum subarray problem +// for small k - SE Bae, T Takaoka but modified to show the explicit tracking +// of indices and we already have the prefix sums from our one call site so +// there's no need to construct them. +// MaxSum1D = function (A,n) { +// var minIdx = 0; +// var min = 0; +// var maxIndices = (0,0); +// var max = 0; +// for i in range(n) { +// let cand = A[i] - min; +// if (cand > max) { +// max := cand; +// maxIndices := (minIdx, i) +// } +// if (min > A[i]) { +// min := A[i]; +// minIdx := i; +// } +// } +// return (minIdx, maxIdx, max); +// } + +namespace { +// This class represents a partitioning of an axis delineated by coordinates. +// It internally maintains a sorted array of coordinates. +class AxisPartition { + public: + // Adds a new partition at the given coordinate to this partitioning. If + // the coordinate is already present in the partitioning, this does nothing. + void InsertCoord(nscoord c) { + uint32_t i = mStops.IndexOfFirstElementGt(c); + if (i == 0 || mStops[i - 1] != c) { + mStops.InsertElementAt(i, c); + } + } + + // Returns the array index of the given partition point. The partition + // point must already be present in the partitioning. + int32_t IndexOf(nscoord p) const { return mStops.BinaryIndexOf(p); } + + // Returns the partition at the given index which must be non-zero and + // less than the number of partitions in this partitioning. + nscoord StopAt(int32_t index) const { return mStops[index]; } + + // Returns the size of the gap between the partition at the given index and + // the next partition in this partitioning. If the index is the last index + // in the partitioning, the result is undefined. + nscoord StopSize(int32_t index) const { + return mStops[index + 1] - mStops[index]; + } + + // Returns the number of partitions in this partitioning. + int32_t GetNumStops() const { return mStops.Length(); } + + private: + nsTArray<nscoord> mStops; +}; + +const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll; + +struct SizePair { + int64_t mSizeContainingRect; + int64_t mSize; + + SizePair() : mSizeContainingRect(0), mSize(0) {} + + static SizePair VeryLargeNegative() { + SizePair result; + result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber; + return result; + } + bool operator<(const SizePair& aOther) const { + if (mSizeContainingRect < aOther.mSizeContainingRect) return true; + if (mSizeContainingRect > aOther.mSizeContainingRect) return false; + return mSize < aOther.mSize; + } + bool operator>(const SizePair& aOther) const { + return aOther.operator<(*this); + } + SizePair operator+(const SizePair& aOther) const { + SizePair result = *this; + result.mSizeContainingRect += aOther.mSizeContainingRect; + result.mSize += aOther.mSize; + return result; + } + SizePair operator-(const SizePair& aOther) const { + SizePair result = *this; + result.mSizeContainingRect -= aOther.mSizeContainingRect; + result.mSize -= aOther.mSize; + return result; + } +}; + +// Returns the sum and indices of the subarray with the maximum sum of the +// given array (A,n), assuming the array is already in prefix sum form. +SizePair MaxSum1D(const nsTArray<SizePair>& A, int32_t n, int32_t* minIdx, + int32_t* maxIdx) { + // The min/max indicies of the largest subarray found so far + SizePair min, max; + int32_t currentMinIdx = 0; + + *minIdx = 0; + *maxIdx = 0; + + // Because we're given the array in prefix sum form, we know the first + // element is 0 + for (int32_t i = 1; i < n; i++) { + SizePair cand = A[i] - min; + if (cand > max) { + max = cand; + *minIdx = currentMinIdx; + *maxIdx = i; + } + if (min > A[i]) { + min = A[i]; + currentMinIdx = i; + } + } + + return max; +} +} // namespace + +nsRect nsRegion::GetLargestRectangle(const nsRect& aContainingRect) const { + nsRect bestRect; + + if (GetNumRects() <= 1) { + bestRect = GetBounds(); + return bestRect; + } + + AxisPartition xaxis, yaxis; + + // Step 1: Calculate the grid lines + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + const nsRect& rect = iter.Get(); + xaxis.InsertCoord(rect.X()); + xaxis.InsertCoord(rect.XMost()); + yaxis.InsertCoord(rect.Y()); + yaxis.InsertCoord(rect.YMost()); + } + if (!aContainingRect.IsEmpty()) { + xaxis.InsertCoord(aContainingRect.X()); + xaxis.InsertCoord(aContainingRect.XMost()); + yaxis.InsertCoord(aContainingRect.Y()); + yaxis.InsertCoord(aContainingRect.YMost()); + } + + // Step 2: Fill out the grid with the areas + // Note: due to the ordering of rectangles in the region, it is not always + // possible to combine steps 2 and 3 so we don't try to be clever. + int32_t matrixHeight = yaxis.GetNumStops() - 1; + int32_t matrixWidth = xaxis.GetNumStops() - 1; + int32_t matrixSize = matrixHeight * matrixWidth; + nsTArray<SizePair> areas(matrixSize); + areas.SetLength(matrixSize); + + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + const nsRect& rect = iter.Get(); + int32_t xstart = xaxis.IndexOf(rect.X()); + int32_t xend = xaxis.IndexOf(rect.XMost()); + int32_t y = yaxis.IndexOf(rect.Y()); + int32_t yend = yaxis.IndexOf(rect.YMost()); + + for (; y < yend; y++) { + nscoord height = yaxis.StopSize(y); + for (int32_t x = xstart; x < xend; x++) { + nscoord width = xaxis.StopSize(x); + int64_t size = width * int64_t(height); + if (rect.Intersects(aContainingRect)) { + areas[y * matrixWidth + x].mSizeContainingRect = size; + } + areas[y * matrixWidth + x].mSize = size; + } + } + } + + // Step 3: Find the maximum submatrix sum that does not contain a rectangle + { + // First get the prefix sum array + int32_t m = matrixHeight + 1; + int32_t n = matrixWidth + 1; + nsTArray<SizePair> pareas(m * n); + pareas.SetLength(m * n); + for (int32_t y = 1; y < m; y++) { + for (int32_t x = 1; x < n; x++) { + SizePair area = areas[(y - 1) * matrixWidth + x - 1]; + if (!area.mSize) { + area = SizePair::VeryLargeNegative(); + } + area = area + pareas[y * n + x - 1] + pareas[(y - 1) * n + x] - + pareas[(y - 1) * n + x - 1]; + pareas[y * n + x] = area; + } + } + + // No longer need the grid + areas.SetLength(0); + + SizePair bestArea; + struct { + int32_t left, top, right, bottom; + } bestRectIndices = {0, 0, 0, 0}; + for (int32_t m1 = 0; m1 < m; m1++) { + for (int32_t m2 = m1 + 1; m2 < m; m2++) { + nsTArray<SizePair> B; + B.SetLength(n); + for (int32_t i = 0; i < n; i++) { + B[i] = pareas[m2 * n + i] - pareas[m1 * n + i]; + } + int32_t minIdx, maxIdx; + SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx); + if (area > bestArea) { + bestRectIndices.left = minIdx; + bestRectIndices.top = m1; + bestRectIndices.right = maxIdx; + bestRectIndices.bottom = m2; + bestArea = area; + } + } + } + + bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left), + yaxis.StopAt(bestRectIndices.top)); + bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.X(), + yaxis.StopAt(bestRectIndices.bottom) - bestRect.Y()); + } + + return bestRect; +} + +std::ostream& operator<<(std::ostream& stream, const nsRegion& m) { + stream << "["; + + bool first = true; + for (auto iter = m.RectIter(); !iter.Done(); iter.Next()) { + if (!first) { + stream << "; "; + } else { + first = false; + } + const nsRect& rect = iter.Get(); + stream << rect.X() << "," << rect.Y() << "," << rect.XMost() << "," + << rect.YMost(); + } + + stream << "]"; + return stream; +} + +void nsRegion::OutputToStream(std::string aObjName, + std::ostream& stream) const { + auto iter = RectIter(); + nsRect r = iter.Get(); + stream << "nsRegion " << aObjName << "(nsRect(" << r.X() << ", " << r.Y() + << ", " << r.Width() << ", " << r.Height() << "));\n"; + iter.Next(); + + for (; !iter.Done(); iter.Next()) { + nsRect r = iter.Get(); + stream << aObjName << ".OrWith(nsRect(" << r.X() << ", " << r.Y() << ", " + << r.Width() << ", " << r.Height() << "));\n"; + } +} + +nsCString nsRegion::ToString() const { + return nsCString(mozilla::ToString(*this).c_str()); +} diff --git a/gfx/src/nsRegion.h b/gfx/src/nsRegion.h new file mode 100644 index 0000000000..505e55e2f2 --- /dev/null +++ b/gfx/src/nsRegion.h @@ -0,0 +1,2532 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRegion_h__ +#define nsRegion_h__ + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint32_t, uint64_t + +#include <ostream> // for std::ostream +#include <utility> // for mozilla::Move + +#include "mozilla/ArrayView.h" // for ArrayView +#include "mozilla/gfx/MatrixFwd.h" // for mozilla::gfx::Matrix4x4 +#include "nsCoord.h" // for nscoord +#include "nsMargin.h" // for nsIntMargin +#include "nsPoint.h" // for nsIntPoint, nsPoint +#include "nsRect.h" // for mozilla::gfx::IntRect, nsRect +#include "nsRectAbsolute.h" +#include "nsRegionFwd.h" // for nsIntRegion +#include "nsString.h" // for nsCString +#include "nsTArray.h" +#include "pixman.h" + +// Uncomment this line to get additional integrity checking. +// #define DEBUG_REGIONS +#ifdef DEBUG_REGIONS +# include <sstream> +#endif + +/* For information on the internal representation look at pixman-region.c + * + * This replaces an older homebrew implementation of nsRegion. The + * representation used here may use more rectangles than nsRegion however, the + * representation is canonical. This means that there's no need for an + * Optimize() method because for a paticular region there is only one + * representation. This means that nsIntRegion will have more predictable + * performance characteristics than the old nsRegion and should not become + * degenerate. + * + * The pixman region code originates from X11 which has spread to a variety of + * projects including Qt, Gtk, Wine. It should perform reasonably well. + */ + +enum class VisitSide { TOP, BOTTOM, LEFT, RIGHT }; + +namespace regiondetails { +struct Band; +} + +template <> +struct nsTArray_RelocationStrategy<regiondetails::Band> { + typedef nsTArray_RelocateUsingMoveConstructor<regiondetails::Band> Type; +}; + +namespace regiondetails { + +template <typename T, typename E> +class UncheckedArray : public T { + public: + using T::Elements; + using T::Length; + + UncheckedArray() = default; + MOZ_IMPLICIT UncheckedArray(T&& aSrc) : T(std::move(aSrc)) {} + + E& operator[](size_t aIndex) { return Elements()[aIndex]; } + const E& operator[](size_t aIndex) const { return Elements()[aIndex]; } + E& LastElement() { return Elements()[Length() - 1]; } + const E& LastElement() const { return Elements()[Length() - 1]; } + + using iterator = E*; + using const_iterator = const E*; + + iterator begin() { return iterator(Elements()); } + const_iterator begin() const { return const_iterator(Elements()); } + const_iterator cbegin() const { return begin(); } + iterator end() { return iterator(Elements() + Length()); } + const_iterator end() const { return const_iterator(Elements() + Length()); } + const_iterator cend() const { return end(); } +}; + +struct Strip { + // Default constructor should never be called, but is required for + // vector::resize to compile. + Strip() { MOZ_CRASH(); } + Strip(int32_t aLeft, int32_t aRight) : left(aLeft), right(aRight) {} + + bool operator!=(const Strip& aOther) const { + return left != aOther.left || right != aOther.right; + } + + uint32_t Size() const { return right - left; } + + int32_t left; + int32_t right; +}; + +struct Band { + using Strip = regiondetails::Strip; +#ifndef DEBUG + using StripArray = + regiondetails::UncheckedArray<CopyableAutoTArray<Strip, 2>, Strip>; +#else + using StripArray = CopyableAutoTArray<Strip, 2>; +#endif + + MOZ_IMPLICIT Band(const nsRectAbsolute& aRect) + : top(aRect.Y()), bottom(aRect.YMost()) { + mStrips.AppendElement(Strip{aRect.X(), aRect.XMost()}); + } + + Band(const Band& aOther) = default; + Band(Band&& aOther) = default; + + void InsertStrip(const Strip& aStrip) { + for (size_t i = 0; i < mStrips.Length(); i++) { + Strip& strip = mStrips[i]; + if (strip.left > aStrip.right) { + // Current strip is beyond aStrip, insert aStrip before. + mStrips.InsertElementAt(i, aStrip); + return; + } + + if (strip.right < aStrip.left) { + // Current strip is before aStrip, try the next. + continue; + } + + // Current strip intersects with aStrip, extend to the lext. + strip.left = std::min(strip.left, aStrip.left); + + if (strip.right >= aStrip.right) { + // Current strip extends beyond aStrip, done. + return; + } + + size_t next = i; + next++; + // Consume any subsequent strips intersecting with aStrip. + while (next < mStrips.Length() && mStrips[next].left <= aStrip.right) { + strip.right = mStrips[next].right; + + mStrips.RemoveElementAt(next); + } + + // Extend the strip in case the aStrip goes on beyond it. + strip.right = std::max(strip.right, aStrip.right); + return; + } + mStrips.AppendElement(aStrip); + } + + void SubStrip(const Strip& aStrip) { + for (size_t i = 0; i < mStrips.Length(); i++) { + Strip& strip = mStrips[i]; + if (strip.left > aStrip.right) { + // Strip is entirely to the right of aStrip. Done. + return; + } + + if (strip.right < aStrip.left) { + // Strip is entirely to the left of aStrip. Move on. + continue; + } + + if (strip.left < aStrip.left) { + if (strip.right <= aStrip.right) { + strip.right = aStrip.left; + // This strip lies to the left of the start of aStrip. + continue; + } + + // aStrip is completely contained by this strip. + Strip newStrip(aStrip.right, strip.right); + strip.right = aStrip.left; + if (i < mStrips.Length()) { + i++; + mStrips.InsertElementAt(i, newStrip); + } else { + mStrips.AppendElement(newStrip); + } + return; + } + + // This strip lies to the right of the start of aStrip. + if (strip.right <= aStrip.right) { + // aStrip completely contains this strip. + mStrips.RemoveElementAt(i); + // Make sure we evaluate the strip now at i. This loop will increment. + i--; + continue; + } + strip.left = aStrip.right; + return; + } + } + + bool Intersects(const Strip& aStrip) const { + for (const Strip& strip : mStrips) { + if (strip.left >= aStrip.right) { + return false; + } + + if (strip.right <= aStrip.left) { + continue; + } + + return true; + } + return false; + } + + bool IntersectStripBounds(Strip& aStrip) const { + bool intersected = false; + + int32_t rightMost; + for (const Strip& strip : mStrips) { + if (strip.left > aStrip.right) { + break; + } + + if (strip.right <= aStrip.left) { + continue; + } + + if (!intersected) { + // First intersection, this is where the left side begins. + aStrip.left = std::max(aStrip.left, strip.left); + } + + intersected = true; + // Expand to the right for each intersecting strip found. + rightMost = std::min(strip.right, aStrip.right); + } + + if (intersected) { + aStrip.right = rightMost; + } else { + aStrip.right = aStrip.left = 0; + } + return intersected; + } + + bool ContainsStrip(const Strip& aStrip) const { + for (const Strip& strip : mStrips) { + if (strip.left > aStrip.left) { + return false; + } + + if (strip.right >= aStrip.right) { + return true; + } + } + return false; + } + + bool EqualStrips(const Band& aBand) const { + if (mStrips.Length() != aBand.mStrips.Length()) { + return false; + } + + for (auto iter1 = mStrips.begin(), iter2 = aBand.mStrips.begin(); + iter1 != mStrips.end(); iter1++, iter2++) { + if (*iter1 != *iter2) { + return false; + } + } + + return true; + } + + void IntersectStrip(const Strip& aStrip) { + size_t i = 0; + + while (i < mStrips.Length()) { + Strip& strip = mStrips[i]; + if (strip.right <= aStrip.left) { + mStrips.RemoveElementAt(i); + continue; + } + + if (strip.left >= aStrip.right) { + mStrips.TruncateLength(i); + return; + } + + strip.left = std::max(aStrip.left, strip.left); + strip.right = std::min(aStrip.right, strip.right); + i++; + } + } + + void IntersectStrips(const Band& aOther) { + auto iter = mStrips.begin(); + auto iterOther = aOther.mStrips.begin(); + + StripArray newStrips; + + // This function finds the intersection between two sets of strips. + while (true) { + while (true) { + while (iter != mStrips.end() && iter->right <= iterOther->left) { + // Increment our current strip until it ends beyond aOther's current + // strip. + iter++; + } + + if (iter == mStrips.end()) { + // End of our strips. Done. + break; + } + + while (iterOther != aOther.mStrips.end() && + iterOther->right <= iter->left) { + // Increment aOther's current strip until it lies beyond our current + // strip. + iterOther++; + } + + if (iterOther == aOther.mStrips.end()) { + // End of aOther's strips. Done. + break; + } + + if (iterOther->left < iter->right) { + // Intersection! + break; + } + } + + if (iter == mStrips.end() || iterOther == aOther.mStrips.end()) { + break; + } + + newStrips.AppendElement(Strip(std::max(iter->left, iterOther->left), + std::min(iterOther->right, iter->right))); + + if (iterOther->right < iter->right) { + iterOther++; + if (iterOther == aOther.mStrips.end()) { + break; + } + } else { + iter++; + } + } + + mStrips = std::move(newStrips); + } + + bool Intersects(const Band& aOther) const { + auto iter = mStrips.begin(); + auto iterOther = aOther.mStrips.begin(); + + // This function finds the intersection between two sets of strips. + while (true) { + while (true) { + while (iter != mStrips.end() && iter->right <= iterOther->left) { + // Increment our current strip until it ends beyond aOther's current + // strip. + iter++; + } + + if (iter == mStrips.end()) { + // End of our strips. Done. + break; + } + + while (iterOther != aOther.mStrips.end() && + iterOther->right <= iter->left) { + // Increment aOther's current strip until it lies beyond our current + // strip. + iterOther++; + } + + if (iterOther == aOther.mStrips.end()) { + // End of aOther's strips. Done. + break; + } + + if (iterOther->left < iter->right) { + // Intersection! + break; + } + } + + if (iter == mStrips.end() || iterOther == aOther.mStrips.end()) { + break; + } + + return true; + } + return false; + } + + void SubStrips(const Band& aOther) { + size_t idx = 0; + auto iterOther = aOther.mStrips.begin(); + + // This function finds the intersection between two sets of strips. + while (true) { + while (true) { + while (idx < mStrips.Length() && + mStrips[idx].right <= iterOther->left) { + // Increment our current strip until it ends beyond aOther's current + // strip. + idx++; + } + + if (idx == mStrips.Length()) { + // End of our strips. Done. + break; + } + + while (iterOther != aOther.mStrips.end() && + iterOther->right <= mStrips[idx].left) { + // Increment aOther's current strip until it lies beyond our current + // strip. + iterOther++; + } + + if (iterOther == aOther.mStrips.end()) { + // End of aOther's strips. Done. + break; + } + + if (iterOther->left < mStrips[idx].right) { + // Intersection! + break; + } + } + + if (idx == mStrips.Length() || iterOther == aOther.mStrips.end()) { + break; + } + + if (mStrips[idx].left < iterOther->left) { + size_t oldIdx = idx; + // Our strip starts beyond other's + if (mStrips[idx].right > iterOther->right) { + // Our strip ends beyond other's as well. + Strip newStrip(mStrips[idx]); + newStrip.left = iterOther->right; + mStrips.InsertElementAt(idx + 1, newStrip); + idx++; + } + mStrips[oldIdx].right = iterOther->left; + // Either idx was just incremented, or the current index no longer + // intersects with iterOther. + continue; + } else if (mStrips[idx].right > iterOther->right) { + mStrips[idx].left = iterOther->right; + // Current strip no longer intersects, continue. + iterOther++; + if (iterOther == aOther.mStrips.end()) { + break; + } + continue; + } + + // Our current strip is completely contained by the other strip. + mStrips.RemoveElementAt(idx); + } + } + + int32_t top; + int32_t bottom; + StripArray mStrips; +}; +} // namespace regiondetails + +class nsRegion { + public: + using Band = regiondetails::Band; + using Strip = regiondetails::Strip; +#ifndef DEBUG + using BandArray = regiondetails::UncheckedArray<nsTArray<Band>, Band>; + using StripArray = regiondetails::UncheckedArray<AutoTArray<Strip, 2>, Strip>; +#else + using BandArray = nsTArray<Band>; + using StripArray = AutoTArray<Strip, 2>; +#endif + + typedef nsRect RectType; + typedef nsPoint PointType; + typedef nsMargin MarginType; + + nsRegion() = default; + MOZ_IMPLICIT nsRegion(const nsRect& aRect) { + mBounds = nsRectAbsolute::FromRect(aRect); + } + MOZ_IMPLICIT nsRegion(const nsRectAbsolute& aRect) { mBounds = aRect; } + explicit nsRegion(mozilla::gfx::ArrayView<pixman_box32_t> aRects) { + for (uint32_t i = 0; i < aRects.Length(); i++) { + AddRect(BoxToRect(aRects[i])); + } + } + + nsRegion(const nsRegion& aRegion) { Copy(aRegion); } + nsRegion(nsRegion&& aRegion) + : mBands(std::move(aRegion.mBands)), mBounds(aRegion.mBounds) { + aRegion.SetEmpty(); + } + nsRegion& operator=(nsRegion&& aRegion) { + mBands = std::move(aRegion.mBands); + mBounds = aRegion.mBounds; + aRegion.SetEmpty(); + return *this; + } + nsRegion& operator=(const nsRect& aRect) { + Copy(aRect); + return *this; + } + nsRegion& operator=(const nsRegion& aRegion) { + Copy(aRegion); + return *this; + } + bool operator==(const nsRegion& aRgn) const { return IsEqual(aRgn); } + bool operator!=(const nsRegion& aRgn) const { return !(*this == aRgn); } + + friend std::ostream& operator<<(std::ostream& stream, const nsRegion& m); + void OutputToStream(std::string aObjName, std::ostream& stream) const; + + private: +#ifdef DEBUG_REGIONS + class OperationStringGenerator { + public: + virtual ~OperationStringGenerator() = default; + + virtual void OutputOp() = 0; + }; +#endif + public: + void AssertStateInternal() const; + void AssertState() const { +#ifdef DEBUG_REGIONS + AssertStateInternal(); +#endif + } + + private: + void And(BandArray& aOut, const BandArray& aIn1, const BandArray& aIn2) { + size_t idx = 0; + size_t idxOther = 0; + + // This algorithm essentially forms a new list of bands, by iterating over + // both regions' lists of band simultaneously, and building a new band + // wherever the two regions intersect. + while (true) { + while (true) { + while (idx != aIn1.Length() && aIn1[idx].bottom <= aIn2[idxOther].top) { + // Increment our current band until it ends beyond aOther's current + // band. + idx++; + } + + if (idx == aIn1.Length()) { + // This region is out of bands, the other region's future bands are + // ignored. + break; + } + + while (idxOther != aIn2.Length() && + aIn2[idxOther].bottom <= aIn1[idx].top) { + // Increment aOther's current band until it ends beyond our current + // band. + idxOther++; + } + + if (idxOther == aIn2.Length()) { + // The other region's bands are all processed, all our future bands + // are ignored. + break; + } + + if (aIn2[idxOther].top < aIn1[idx].bottom) { + // We know the other band's bottom lies beyond our band's top because + // otherwise we would've incremented above. Intersecting bands found. + break; + } + } + + if (idx == aIn1.Length() || idxOther == aIn2.Length()) { + // The above loop executed a break because we're done. + break; + } + + Band newBand(aIn1[idx]); + // The new band is the intersection of the two current bands from both + // regions. + newBand.top = std::max(aIn1[idx].top, aIn2[idxOther].top); + newBand.bottom = std::min(aIn1[idx].bottom, aIn2[idxOther].bottom); + newBand.IntersectStrips(aIn2[idxOther]); + + if (newBand.mStrips.Length()) { + // The intersecting area of the bands had overlapping strips, if it is + // identical to the band above it merge, otherwise append. + if (aOut.Length() && aOut.LastElement().bottom == newBand.top && + aOut.LastElement().EqualStrips(newBand)) { + aOut.LastElement().bottom = newBand.bottom; + } else { + aOut.AppendElement(std::move(newBand)); + } + } + + if (aIn2[idxOther].bottom < aIn1[idx].bottom) { + idxOther++; + if (idxOther == aIn2.Length()) { + // Since we will access idxOther the next iteration, check if we're + // not done. + break; + } + } else { + // No need to check here since we do at the beginning of the next + // iteration. + idx++; + } + } + } + + public: + nsRegion& AndWith(const nsRegion& aRegion) { +#ifdef DEBUG_REGIONS + class OperationStringGeneratorAndWith : public OperationStringGenerator { + public: + OperationStringGeneratorAndWith(nsRegion& aRegion, + const nsRegion& aOtherRegion) + : mRegion(&aRegion), + mRegionCopy(aRegion), + mOtherRegion(aOtherRegion) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorAndWith() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionCopy.OutputToStream("r1", stream); + mOtherRegion.OutputToStream("r2", stream); + stream << "r1.AndWith(r2);\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionCopy; + nsRegion mOtherRegion; + }; + + OperationStringGeneratorAndWith opGenerator(*this, aRegion); +#endif + if (mBounds.IsEmpty()) { + // Region is empty, stays empty. + return *this; + } + + if (aRegion.IsEmpty()) { + SetEmpty(); + return *this; + } + + if (aRegion.mBands.IsEmpty()) { + // Other region is a rect. + return AndWith(aRegion.mBounds); + } + + if (mBands.IsEmpty()) { + mBands.AppendElement(mBounds); + } + + BandArray newBands; + + And(newBands, mBands, aRegion.mBands); + + mBands = std::move(newBands); + if (!mBands.Length()) { + mBounds = nsRectAbsolute(); + } else { + mBounds = CalculateBounds(); + } + + EnsureSimplified(); + AssertState(); + return *this; + } + + nsRegion& AndWith(const nsRectAbsolute& aRect) { +#ifdef DEBUG_REGIONS + class OperationStringGeneratorAndWith : public OperationStringGenerator { + public: + OperationStringGeneratorAndWith(nsRegion& aRegion, + const nsRectAbsolute& aRect) + : mRegion(&aRegion), mRegionCopy(aRegion), mRect(aRect) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorAndWith() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionCopy.OutputToStream("r", stream); + stream << "r.AndWith(nsRect(" << mRect.X() << ", " << mRect.Y() << ", " + << mRect.Width() << ", " << mRect.Height() << "));\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionCopy; + nsRectAbsolute mRect; + }; + + OperationStringGeneratorAndWith opGenerator(*this, aRect); +#endif + if (aRect.IsEmpty()) { + SetEmpty(); + return *this; + } + + if (mBands.IsEmpty()) { + mBounds = mBounds.Intersect(aRect); + return *this; + } + + size_t idx = 0; + + size_t removeStart = 0; + + // This removes all bands that do not intersect with aRect, and intersects + // the remaining ones with aRect. + + // Start by figuring out how much to remove from the start. + while (idx != mBands.Length() && mBands[idx].bottom <= aRect.Y()) { + idx++; + } + + // We'll remove these later to avoid needless copying in the array. + removeStart = idx; + + while (idx != mBands.Length()) { + if (mBands[idx].top >= aRect.YMost()) { + mBands.TruncateLength(idx); + break; + } + + mBands[idx].top = std::max(mBands[idx].top, aRect.Y()); + mBands[idx].bottom = std::min(mBands[idx].bottom, aRect.YMost()); + + mBands[idx].IntersectStrip(Strip(aRect.X(), aRect.XMost())); + + if (!mBands[idx].mStrips.Length()) { + mBands.RemoveElementAt(idx); + } else { + if (idx > removeStart) { + CompressBefore(idx); + } + idx++; + } + } + + if (removeStart) { + mBands.RemoveElementsAt(0, removeStart); + } + + if (mBands.Length()) { + mBounds = CalculateBounds(); + } else { + mBounds.SetEmpty(); + } + EnsureSimplified(); + AssertState(); + return *this; + } + nsRegion& AndWith(const nsRect& aRect) { + return AndWith(nsRectAbsolute::FromRect(aRect)); + } + nsRegion& And(const nsRegion& aRgn1, const nsRegion& aRgn2) { + if (&aRgn1 == this) { + return AndWith(aRgn2); + } +#ifdef DEBUG_REGIONS + class OperationStringGeneratorAnd : public OperationStringGenerator { + public: + OperationStringGeneratorAnd(nsRegion& aRegion, const nsRegion& aRegion1, + const nsRegion& aRegion2) + : mRegion(&aRegion), mRegion1(aRegion1), mRegion2(aRegion2) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorAnd() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegion1.OutputToStream("r1", stream); + mRegion2.OutputToStream("r2", stream); + stream << "nsRegion r3;\nr3.And(r1, r2);\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegion1; + nsRegion mRegion2; + }; + + OperationStringGeneratorAnd opGenerator(*this, aRgn1, aRgn2); +#endif + mBands.Clear(); + + if (aRgn1.IsEmpty() || aRgn2.IsEmpty()) { + mBounds.SetEmpty(); + return *this; + } + + if (aRgn1.mBands.IsEmpty() && aRgn2.mBands.IsEmpty()) { + mBounds = aRgn1.mBounds.Intersect(aRgn2.mBounds); + return *this; + } else if (aRgn1.mBands.IsEmpty()) { + return And(aRgn2, aRgn1.mBounds); + } else if (aRgn2.mBands.IsEmpty()) { + return And(aRgn1, aRgn2.mBounds); + } + + And(mBands, aRgn1.mBands, aRgn2.mBands); + + if (!mBands.Length()) { + mBounds = nsRectAbsolute(); + } else { + mBounds = CalculateBounds(); + } + + EnsureSimplified(); + AssertState(); + return *this; + } + nsRegion& And(const nsRect& aRect, const nsRegion& aRegion) { + return And(aRegion, aRect); + } + nsRegion& And(const nsRegion& aRegion, const nsRectAbsolute& aRect) { + if (&aRegion == this) { + return AndWith(aRect); + } +#ifdef DEBUG_REGIONS + class OperationStringGeneratorAnd : public OperationStringGenerator { + public: + OperationStringGeneratorAnd(nsRegion& aThisRegion, + const nsRegion& aRegion, + const nsRectAbsolute& aRect) + : mThisRegion(&aThisRegion), mRegion(aRegion), mRect(aRect) { + aThisRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorAnd() { + mThisRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegion.OutputToStream("r", stream); + stream << "nsRegion r2;\nr.And(r2, nsRect(" << mRect.X() << ", " + << mRect.Y() << ", " << mRect.Width() << ", " << mRect.Height() + << "));\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mThisRegion; + nsRegion mRegion; + nsRectAbsolute mRect; + }; + + OperationStringGeneratorAnd opGenerator(*this, aRegion, aRect); +#endif + mBands.Clear(); + + if (aRect.IsEmpty()) { + mBounds.SetEmpty(); + return *this; + } + + if (aRegion.mBands.IsEmpty()) { + mBounds = aRegion.mBounds.Intersect(aRect); + return *this; + } + + size_t idx = 0; + const BandArray& bands = aRegion.mBands; + + mBands.SetCapacity(bands.Length() + 3); + while (idx != bands.Length()) { + // Ignore anything before. + if (bands[idx].bottom <= aRect.Y()) { + idx++; + continue; + } + // We're done once we've reached the bottom. + if (bands[idx].top >= aRect.YMost()) { + break; + } + + // Now deal with bands actually intersecting the rectangle. + Band newBand(bands[idx]); + newBand.top = std::max(bands[idx].top, aRect.Y()); + newBand.bottom = std::min(bands[idx].bottom, aRect.YMost()); + + newBand.IntersectStrip(Strip(aRect.X(), aRect.XMost())); + + if (newBand.mStrips.Length()) { + if (!mBands.IsEmpty() && newBand.top == mBands.LastElement().bottom && + newBand.EqualStrips(mBands.LastElement())) { + mBands.LastElement().bottom = newBand.bottom; + } else { + mBands.AppendElement(std::move(newBand)); + } + } + idx++; + } + + if (mBands.Length()) { + mBounds = CalculateBounds(); + } else { + mBounds.SetEmpty(); + } + + EnsureSimplified(); + AssertState(); + return *this; + } + nsRegion& And(const nsRegion& aRegion, const nsRect& aRect) { + return And(aRegion, nsRectAbsolute::FromRect(aRect)); + } + nsRegion& And(const nsRect& aRect1, const nsRect& aRect2) { + nsRect tmpRect; + + tmpRect.IntersectRect(aRect1, aRect2); + return Copy(tmpRect); + } + + nsRegion& OrWith(const nsRegion& aOther) { + for (RectIterator idx(aOther); !idx.Done(); idx.Next()) { + AddRect(idx.GetAbsolute()); + } + return *this; + } + nsRegion& OrWith(const nsRect& aOther) { + AddRect(nsRectAbsolute::FromRect(aOther)); + return *this; + } + nsRegion& Or(const nsRegion& aRgn1, const nsRegion& aRgn2) { + if (&aRgn1 != this) { + *this = aRgn1; + } + for (RectIterator idx(aRgn2); !idx.Done(); idx.Next()) { + AddRect(idx.GetAbsolute()); + } + return *this; + } + nsRegion& Or(const nsRegion& aRegion, const nsRect& aRect) { + if (&aRegion != this) { + *this = aRegion; + } + AddRect(nsRectAbsolute::FromRect(aRect)); + return *this; + } + nsRegion& Or(const nsRect& aRect, const nsRegion& aRegion) { + return Or(aRegion, aRect); + } + nsRegion& Or(const nsRect& aRect1, const nsRect& aRect2) { + Copy(aRect1); + return Or(*this, aRect2); + } + + nsRegion& XorWith(const nsRegion& aOther) { return Xor(*this, aOther); } + nsRegion& XorWith(const nsRect& aOther) { return Xor(*this, aOther); } + nsRegion& Xor(const nsRegion& aRgn1, const nsRegion& aRgn2) { + // this could be implemented better if pixman had direct + // support for xoring regions. + nsRegion p; + p.Sub(aRgn1, aRgn2); + nsRegion q; + q.Sub(aRgn2, aRgn1); + return Or(p, q); + } + nsRegion& Xor(const nsRegion& aRegion, const nsRect& aRect) { + return Xor(aRegion, nsRegion(aRect)); + } + nsRegion& Xor(const nsRect& aRect, const nsRegion& aRegion) { + return Xor(nsRegion(aRect), aRegion); + } + nsRegion& Xor(const nsRect& aRect1, const nsRect& aRect2) { + return Xor(nsRegion(aRect1), nsRegion(aRect2)); + } + + nsRegion ToAppUnits(nscoord aAppUnitsPerPixel) const; + + nsRegion& SubWith(const nsRegion& aOther) { +#ifdef DEBUG_REGIONS + class OperationStringGeneratorSubWith : public OperationStringGenerator { + public: + OperationStringGeneratorSubWith(nsRegion& aRegion, + const nsRegion& aOtherRegion) + : mRegion(&aRegion), + mRegionCopy(aRegion), + mOtherRegion(aOtherRegion) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorSubWith() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionCopy.OutputToStream("r1", stream); + mOtherRegion.OutputToStream("r2", stream); + stream << "r1.SubWith(r2);\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionCopy; + nsRegion mOtherRegion; + }; + + OperationStringGeneratorSubWith opGenerator(*this, aOther); +#endif + + if (mBounds.IsEmpty()) { + return *this; + } + + if (aOther.mBands.IsEmpty()) { + return SubWith(aOther.mBounds); + } + + if (mBands.IsEmpty()) { + mBands.AppendElement(Band(mBounds)); + } + + size_t idx = 0; + size_t idxOther = 0; + while (idx < mBands.Length()) { + while (true) { + while (idx != mBands.Length() && + mBands[idx].bottom <= aOther.mBands[idxOther].top) { + // Increment our current band until it ends beyond aOther's current + // band. + idx++; + } + + if (idx == mBands.Length()) { + // This region is out of bands, the other region's future bands are + // ignored. + break; + } + + while (idxOther != aOther.mBands.Length() && + aOther.mBands[idxOther].bottom <= mBands[idx].top) { + // Increment aOther's current band until it ends beyond our current + // band. + idxOther++; + } + + if (idxOther == aOther.mBands.Length()) { + // The other region's bands are all processed, all our future bands + // are ignored. + break; + } + + if (aOther.mBands[idxOther].top < mBands[idx].bottom) { + // We know the other band's bottom lies beyond our band's top because + // otherwise we would've incremented above. Intersecting bands found. + break; + } + } + + if (idx == mBands.Length() || idxOther == aOther.mBands.Length()) { + // The above loop executed a break because we're done. + break; + } + + const Band& bandOther = aOther.mBands[idxOther]; + + if (!mBands[idx].Intersects(bandOther)) { + if (mBands[idx].bottom < bandOther.bottom) { + idx++; + } else { + idxOther++; + if (idxOther == aOther.mBands.Length()) { + break; + } + } + continue; + } + + // These bands actually intersect. + if (mBands[idx].top < bandOther.top) { + mBands.InsertElementAt(idx + 1, Band(mBands[idx])); + mBands[idx].bottom = bandOther.top; + idx++; + mBands[idx].top = bandOther.top; + } + + // mBands[idx].top >= bandOther.top; + if (mBands[idx].bottom <= bandOther.bottom) { + mBands[idx].SubStrips(bandOther); + if (mBands[idx].mStrips.IsEmpty()) { + mBands.RemoveElementAt(idx); + } else { + CompressBefore(idx); + idx++; + // The band before us just changed, it may be identical now. + CompressBefore(idx); + } + continue; + } + + // mBands[idx].bottom > bandOther.bottom + Band newBand = mBands[idx]; + newBand.SubStrips(bandOther); + + if (!newBand.mStrips.IsEmpty()) { + mBands.InsertElementAt(idx, newBand); + mBands[idx].bottom = bandOther.bottom; + CompressBefore(idx); + idx++; + } + + mBands[idx].top = bandOther.bottom; + + idxOther++; + if (idxOther == aOther.mBands.Length()) { + break; + } + } + + if (mBands.IsEmpty()) { + mBounds.SetEmpty(); + } else { + mBounds = CalculateBounds(); + } + + AssertState(); + EnsureSimplified(); + return *this; + } + nsRegion& SubOut(const nsRegion& aOther) { return SubWith(aOther); } + nsRegion& SubOut(const nsRect& aOther) { return SubWith(aOther); } + + private: + void AppendOrExtend(const Band& aNewBand) { + if (aNewBand.mStrips.IsEmpty()) { + return; + } + if (mBands.IsEmpty()) { + mBands.AppendElement(aNewBand); + return; + } + + if (mBands.LastElement().bottom == aNewBand.top && + mBands.LastElement().EqualStrips(aNewBand)) { + mBands.LastElement().bottom = aNewBand.bottom; + } else { + mBands.AppendElement(aNewBand); + } + } + void AppendOrExtend(const Band&& aNewBand) { + if (aNewBand.mStrips.IsEmpty()) { + return; + } + if (mBands.IsEmpty()) { + mBands.AppendElement(std::move(aNewBand)); + return; + } + + if (mBands.LastElement().bottom == aNewBand.top && + mBands.LastElement().EqualStrips(aNewBand)) { + mBands.LastElement().bottom = aNewBand.bottom; + } else { + mBands.AppendElement(std::move(aNewBand)); + } + } + + public: + nsRegion& Sub(const nsRegion& aRgn1, const nsRegion& aRgn2) { + if (&aRgn1 == this) { + return SubWith(aRgn2); + } +#ifdef DEBUG_REGIONS + class OperationStringGeneratorSub : public OperationStringGenerator { + public: + OperationStringGeneratorSub(nsRegion& aRegion, const nsRegion& aRgn1, + const nsRegion& aRgn2) + : mRegion(&aRegion), mRegion1(aRgn1), mRegion2(aRgn2) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorSub() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegion1.OutputToStream("r1", stream); + mRegion2.OutputToStream("r2", stream); + stream << "nsRegion r3;\nr3.Sub(r1, r2);\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegion1; + nsRegion mRegion2; + }; + + OperationStringGeneratorSub opGenerator(*this, aRgn1, aRgn2); +#endif + + mBands.Clear(); + + if (aRgn1.mBounds.IsEmpty()) { + mBounds.SetEmpty(); + return *this; + } + + if (aRgn2.mBounds.IsEmpty()) { + Copy(aRgn1); + return *this; + } + + if (aRgn1.mBands.IsEmpty() && aRgn2.mBands.IsEmpty()) { + return Sub(aRgn1.mBounds, aRgn2.mBounds); + } else if (aRgn1.mBands.IsEmpty()) { + return Sub(aRgn1.mBounds, aRgn2); + } else if (aRgn2.mBands.IsEmpty()) { + return Sub(aRgn1, aRgn2.mBounds); + } + + const BandArray& bands1 = aRgn1.mBands; + const BandArray& bands2 = aRgn2.mBands; + + size_t idx = 0; + size_t idxOther = 0; + + // We iterate the source region's bands, subtracting the other regions bands + // from them as we move them into ours. + while (idx < bands1.Length()) { + while (idxOther < bands2.Length() && + bands2[idxOther].bottom <= bands1[idx].top) { + // These other bands are irrelevant as they don't intersect with the + // band we're currently processing. + idxOther++; + } + if (idxOther == bands2.Length()) { + break; + } + + const Band& other = bands2[idxOther]; + + // bands2[idxOther].bottom >= bands1[idx].top + Band origBand(bands1[idx]); + if (other.top >= origBand.bottom) { + // No intersecting bands, append and continue. + AppendOrExtend(origBand); + idx++; + continue; + } + + // Push a band for an uncovered region above our band. + if (origBand.top < other.top) { + Band newBand(origBand); + newBand.bottom = other.top; + AppendOrExtend(std::move(newBand)); + } + + int32_t lastBottom = std::max(other.top, origBand.top); + while (idxOther < bands2.Length() && + bands2[idxOther].top < origBand.bottom) { + const Band& other = bands2[idxOther]; + Band newBand(origBand); + newBand.top = std::max(origBand.top, other.top); + newBand.bottom = std::min(origBand.bottom, other.bottom); + + // If there was a gap, we need to add the original band there. + if (newBand.top > lastBottom) { + Band betweenBand(newBand); + betweenBand.top = lastBottom; + betweenBand.bottom = newBand.top; + AppendOrExtend(std::move(betweenBand)); + } + + lastBottom = newBand.bottom; + newBand.SubStrips(other); + AppendOrExtend(std::move(newBand)); + idxOther++; + } + // Decrement other here so we are back at the last band in region 2 + // that intersected. + idxOther--; + + if (bands2[idxOther].bottom < origBand.bottom) { + // The last band in region2 that intersected ended before this one, + // we can copy the rest. + Band newBand(origBand); + newBand.top = bands2[idxOther].bottom; + AppendOrExtend(std::move(newBand)); + idxOther++; + } + idx++; + } + + // Copy any remaining bands, the first one may have to be extended to fit + // the last one added before. The rest can be unconditionally appended. + if (idx < bands1.Length()) { + AppendOrExtend(bands1[idx]); + idx++; + } + + while (idx < bands1.Length()) { + mBands.AppendElement(bands1[idx]); + idx++; + } + + if (mBands.IsEmpty()) { + mBounds.SetEmpty(); + } else { + mBounds = CalculateBounds(); + } + + AssertState(); + EnsureSimplified(); + return *this; + } + + private: + // Internal helper for executing subtraction. + void RunSubtraction(const nsRectAbsolute& aRect) { + Strip rectStrip(aRect.X(), aRect.XMost()); + + size_t idx = 0; + + while (idx < mBands.Length()) { + if (mBands[idx].top >= aRect.YMost()) { + return; + } + + if (mBands[idx].bottom <= aRect.Y()) { + // This band is entirely before aRect, move on. + idx++; + continue; + } + + if (!mBands[idx].Intersects(Strip(aRect.X(), aRect.XMost()))) { + // This band does not intersect aRect horizontally. Move on. + idx++; + continue; + } + + // This band intersects with aRect. + + if (mBands[idx].top < aRect.Y()) { + // This band starts above the start of aRect, split the band into two + // along the intersection, and continue to the next iteration to process + // the one that now intersects exactly. + auto above = mBands.InsertElementAt(idx, Band(mBands[idx])); + above->bottom = aRect.Y(); + idx++; + mBands[idx].top = aRect.Y(); + // Continue to run the loop for the next band. + continue; + } + + if (mBands[idx].bottom <= aRect.YMost()) { + // This band ends before the end of aRect. + mBands[idx].SubStrip(rectStrip); + if (mBands[idx].mStrips.Length()) { + CompressAdjacentBands(idx); + } else { + mBands.RemoveElementAt(idx); + } + continue; + } + + // This band extends beyond aRect. + Band newBand = mBands[idx]; + newBand.SubStrip(rectStrip); + newBand.bottom = aRect.YMost(); + mBands[idx].top = aRect.YMost(); + + if (newBand.mStrips.Length()) { + if (idx && mBands[idx - 1].bottom == newBand.top && + newBand.EqualStrips(mBands[idx - 1])) { + mBands[idx - 1].bottom = aRect.YMost(); + } else { + mBands.InsertElementAt(idx, std::move(newBand)); + } + } + + return; + } + } + + public: + nsRegion& SubWith(const nsRectAbsolute& aRect) { + if (!mBounds.Intersects(aRect)) { + return *this; + } + + if (aRect.Contains(mBounds)) { + SetEmpty(); + return *this; + } + +#ifdef DEBUG_REGIONS + class OperationStringGeneratorSubWith : public OperationStringGenerator { + public: + OperationStringGeneratorSubWith(nsRegion& aRegion, + const nsRectAbsolute& aRect) + : mRegion(&aRegion), mRegionCopy(aRegion), mRect(aRect) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorSubWith() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionCopy.OutputToStream("r", stream); + stream << "r.SubWith(nsRect(" << mRect.X() << ", " << mRect.Y() << ", " + << mRect.Width() << ", " << mRect.Height() << "));\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionCopy; + nsRectAbsolute mRect; + }; + + OperationStringGeneratorSubWith opGenerator(*this, aRect); +#endif + + if (mBands.IsEmpty()) { + mBands.AppendElement(Band(mBounds)); + } + + RunSubtraction(aRect); + + if (aRect.X() <= mBounds.X() || aRect.Y() <= mBounds.Y() || + aRect.XMost() >= mBounds.XMost() || aRect.YMost() >= mBounds.YMost()) { + mBounds = CalculateBounds(); + } + EnsureSimplified(); + AssertState(); + return *this; + } + nsRegion& Sub(const nsRegion& aRegion, const nsRectAbsolute& aRect) { + if (aRect.Contains(aRegion.mBounds)) { + SetEmpty(); + return *this; + } + if (&aRegion == this) { + return SubWith(aRect); + } +#ifdef DEBUG_REGIONS + class OperationStringGeneratorSub : public OperationStringGenerator { + public: + OperationStringGeneratorSub(nsRegion& aRegion, + const nsRegion& aRegionOther, + const nsRectAbsolute& aRect) + : mRegion(&aRegion), mRegionOther(aRegionOther), mRect(aRect) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorSub() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionOther.OutputToStream("r1", stream); + stream << "nsRegion r2;\nr2.Sub(r1, nsRect(" << mRect.X() << ", " + << mRect.Y() << ", " << mRect.Width() << ", " << mRect.Height() + << "));\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionOther; + nsRectAbsolute mRect; + }; + + OperationStringGeneratorSub opGenerator(*this, aRegion, aRect); +#endif + + mBands.Clear(); + + if (aRegion.mBounds.IsEmpty()) { + mBounds.SetEmpty(); + return *this; + } + + if (aRect.IsEmpty()) { + Copy(aRegion); + return *this; + } + + if (aRegion.mBands.IsEmpty()) { + Copy(aRegion.mBounds); + return SubWith(aRect); + } + + const BandArray& bands = aRegion.mBands; + + size_t idx = 0; + + Strip strip(aRect.X(), aRect.XMost()); + + mBands.SetCapacity(bands.Length() + 3); + + // Process all bands that lie before aRect. + while (idx < bands.Length() && bands[idx].bottom <= aRect.Y()) { + mBands.AppendElement(bands[idx]); + idx++; + } + + // This band's bottom lies beyond aRect. + if (idx < bands.Length() && bands[idx].top < aRect.Y()) { + Band newBand(bands[idx]); + if (bands[idx].Intersects(strip)) { + newBand.bottom = aRect.Y(); + } else { + idx++; + } + mBands.AppendElement(std::move(newBand)); + } + + // This tracks whether the band when we -exit- the next loop intersected the + // rectangle. + bool didIntersect = false; + + while (idx < bands.Length() && bands[idx].top < aRect.YMost()) { + // Process all bands intersecting with aRect. + if (!bands[idx].Intersects(strip)) { + AppendOrExtend(bands[idx]); + idx++; + didIntersect = false; + continue; + } + + didIntersect = true; + Band newBand(bands[idx]); + newBand.top = std::max(newBand.top, aRect.Y()); + newBand.bottom = std::min(newBand.bottom, aRect.YMost()); + newBand.SubStrip(strip); + AppendOrExtend(std::move(newBand)); + idx++; + } + + if (didIntersect) { + if (aRect.YMost() < bands[idx - 1].bottom) { + // If this band does not intersect the loop above has already added the + // whole unmodified band. + Band newBand(bands[idx - 1]); + newBand.top = aRect.YMost(); + AppendOrExtend(std::move(newBand)); + } + } + + // Now process all bands beyond aRect. + if (idx < bands.Length()) { + AppendOrExtend(bands[idx]); + idx++; + } + + mBands.AppendElements(bands.Elements() + idx, bands.Length() - idx); + + if (mBands.IsEmpty()) { + mBounds.SetEmpty(); + } else { + mBounds = CalculateBounds(); + } + + AssertState(); + EnsureSimplified(); + return *this; + } + nsRegion& SubWith(const nsRect& aRect) { + return SubWith(nsRectAbsolute::FromRect(aRect)); + } + nsRegion& Sub(const nsRect& aRect, const nsRegion& aRegion) { + Copy(aRect); + return SubWith(aRegion); + } + nsRegion& Sub(const nsRectAbsolute& aRect, const nsRegion& aRegion) { + Copy(aRect); + return SubWith(aRegion); + } + nsRegion& Sub(const nsRect& aRect1, const nsRect& aRect2) { + Copy(aRect1); + return SubWith(aRect2); + } + nsRegion& Sub(const nsRegion& aRegion, const nsRect& aRect) { + return Sub(aRegion, nsRectAbsolute::FromRect(aRect)); + } + nsRegion& Sub(const nsRectAbsolute& aRect1, const nsRectAbsolute& aRect2) { + Copy(aRect1); + return SubWith(aRect2); + } + + /** + * Returns true if the given point is inside the region. A region + * created from a rect (x=0, y=0, w=100, h=100) will NOT contain + * the point x=100, y=100. + */ + bool Contains(int aX, int aY) const { + if (mBands.IsEmpty()) { + return mBounds.Contains(aX, aY); + } + + auto iter = mBands.begin(); + + while (iter != mBands.end()) { + if (iter->bottom <= aY) { + iter++; + continue; + } + + if (iter->top > aY) { + return false; + } + + if (iter->ContainsStrip(Strip(aX, aX + 1))) { + return true; + } + return false; + } + return false; + } + + bool Contains(const nsPoint& aPoint) const { + return Contains(aPoint.x, aPoint.y); + } + + bool Contains(const nsRectAbsolute& aRect) const { + if (aRect.IsEmpty()) { + return false; + } + + if (mBands.IsEmpty()) { + return mBounds.Contains(aRect); + } + + auto iter = mBands.begin(); + + while (iter != mBands.end()) { + if (iter->bottom <= aRect.Y()) { + iter++; + continue; + } + + if (iter->top > aRect.Y()) { + return false; + } + + // Now inside the rectangle. + if (!iter->ContainsStrip(Strip(aRect.X(), aRect.XMost()))) { + return false; + } + + if (iter->bottom >= aRect.YMost()) { + return true; + } + + int32_t lastY = iter->bottom; + iter++; + while (iter != mBands.end()) { + // Bands do not connect. + if (iter->top != lastY) { + return false; + } + + if (!iter->ContainsStrip(Strip(aRect.X(), aRect.XMost()))) { + return false; + } + + if (iter->bottom >= aRect.YMost()) { + return true; + } + + lastY = iter->bottom; + iter++; + } + } + return false; + } + bool Contains(const nsRect& aRect) const { + return Contains(nsRectAbsolute::FromRect(aRect)); + } + + bool Contains(const nsRegion& aRgn) const; + bool Intersects(const nsRectAbsolute& aRect) const; + bool Intersects(const nsRect& aRect) const { + return Intersects(nsRectAbsolute::FromRect(aRect)); + } + + void MoveBy(int32_t aXOffset, int32_t aYOffset) { + MoveBy(nsPoint(aXOffset, aYOffset)); + } + void MoveBy(nsPoint aPt) { +#ifdef DEBUG_REGIONS + class OperationStringGeneratorMoveBy : public OperationStringGenerator { + public: + OperationStringGeneratorMoveBy(nsRegion& aRegion, const nsPoint& aPoint) + : mRegion(&aRegion), mRegionCopy(aRegion), mPoint(aPoint) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorMoveBy() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionCopy.OutputToStream("r", stream); + stream << "r.MoveBy(nsPoint(" << mPoint.x << ", " << mPoint.y + << "));\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionCopy; + nsPoint mPoint; + }; + + OperationStringGeneratorMoveBy opGenerator(*this, aPt); +#endif + + mBounds.MoveBy(aPt); + for (Band& band : mBands) { + band.top += aPt.Y(); + band.bottom += aPt.Y(); + for (Strip& strip : band.mStrips) { + strip.left += aPt.X(); + strip.right += aPt.X(); + } + } + AssertState(); + } + void SetEmpty() { + mBands.Clear(); + mBounds.SetEmpty(); + } + + nsRegion MovedBy(int32_t aXOffset, int32_t aYOffset) const { + return MovedBy(nsPoint(aXOffset, aYOffset)); + } + nsRegion MovedBy(const nsPoint& aPt) const { + nsRegion copy(*this); + copy.MoveBy(aPt); + return copy; + } + + nsRegion Intersect(const nsRegion& aOther) const { + nsRegion intersection; + intersection.And(*this, aOther); + return intersection; + } + + void Inflate(const nsMargin& aMargin); + + nsRegion Inflated(const nsMargin& aMargin) const { + nsRegion copy(*this); + copy.Inflate(aMargin); + return copy; + } + + bool IsEmpty() const { return mBounds.IsEmpty(); } + bool IsComplex() const { return GetNumRects() > 1; } + bool IsEqual(const nsRegion& aRegion) const { + if (!mBounds.IsEqualInterior(aRegion.mBounds)) { + return false; + } + + if (mBands.Length() != aRegion.mBands.Length()) { + return false; + } + + for (auto iter1 = mBands.begin(), iter2 = aRegion.mBands.begin(); + iter1 != mBands.end(); iter1++, iter2++) { + if (iter1->top != iter2->top || iter1->bottom != iter2->bottom || + !iter1->EqualStrips(*iter2)) { + return false; + } + } + + return true; + } + + uint32_t GetNumRects() const { + if (mBands.IsEmpty()) { + return mBounds.IsEmpty() ? 0 : 1; + } + + uint32_t rects = 0; + + for (const Band& band : mBands) { + rects += band.mStrips.Length(); + } + + return rects; + } + const nsRect GetBounds() const { return mBounds.ToNSRect(); } + const nsRectAbsolute GetAbsoluteBounds() const { return mBounds; } + uint64_t Area() const; + + /** + * Return this region scaled to a different appunits per pixel (APP) ratio. + * This applies nsRect::ScaleToOtherAppUnitsRoundOut/In to each rect of the + * region. + * @param aFromAPP the APP to scale from + * @param aToAPP the APP to scale to + * @note this can turn an empty region into a non-empty region + */ + [[nodiscard]] nsRegion ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, + int32_t aToAPP) const; + [[nodiscard]] nsRegion ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, + int32_t aToAPP) const; + nsRegion& ScaleRoundOut(float aXScale, float aYScale); + nsRegion& ScaleInverseRoundOut(float aXScale, float aYScale); + nsRegion& Transform(const mozilla::gfx::Matrix4x4& aTransform); + nsIntRegion ScaleToOutsidePixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + nsIntRegion ScaleToInsidePixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + nsIntRegion ScaleToNearestPixels(float aXScale, float aYScale, + nscoord aAppUnitsPerPixel) const; + nsIntRegion ToOutsidePixels(nscoord aAppUnitsPerPixel) const; + nsIntRegion ToNearestPixels(nscoord aAppUnitsPerPixel) const; + + /** + * Gets the largest rectangle contained in the region. + * @param aContainingRect if non-empty, we choose a rectangle that + * maximizes the area intersecting with aContainingRect (and break ties by + * then choosing the largest rectangle overall) + */ + nsRect GetLargestRectangle(const nsRect& aContainingRect = nsRect()) const; + + /** + * Make sure the region has at most aMaxRects by adding area to it + * if necessary. The simplified region will be a superset of the + * original region. The simplified region's bounding box will be + * the same as for the current region. + */ + void SimplifyOutward(uint32_t aMaxRects); + /** + * Simplify the region by adding at most aThreshold area between spans of + * rects. The simplified region will be a superset of the original region. + * The simplified region's bounding box will be the same as for the current + * region. + */ + void SimplifyOutwardByArea(uint32_t aThreshold); + /** + * Make sure the region has at most aMaxRects by removing area from + * it if necessary. The simplified region will be a subset of the + * original region. + */ + void SimplifyInward(uint32_t aMaxRects); + + /** + * VisitEdges is a weird kind of function that we use for padding + * out surfaces to prevent texture filtering artifacts. + * It calls the visitFn callback for each of the exterior edges of + * the regions. The top and bottom edges will be expanded 1 pixel + * to the left and right if there's an outside corner. The order + * the edges are visited is not guaranteed. + * + * visitFn has a side parameter that can be TOP,BOTTOM,LEFT,RIGHT + * and specifies which kind of edge is being visited. x1, y1, x2, y2 + * are the coordinates of the line. (x1 == x2) || (y1 == y2) + */ + typedef void (*visitFn)(void* closure, VisitSide side, int x1, int y1, int x2, + int y2); + void VisitEdges(visitFn, void* closure) const; + + nsCString ToString() const; + + static inline pixman_box32_t RectToBox(const nsRect& aRect) { + pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()}; + return box; + } + + static inline pixman_box32_t RectToBox(const mozilla::gfx::IntRect& aRect) { + pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()}; + return box; + } + + private: + nsIntRegion ToPixels(nscoord aAppUnitsPerPixel, bool aOutsidePixels) const; + + nsRegion& Copy(const nsRegion& aRegion) { + mBounds = aRegion.mBounds; + mBands = aRegion.mBands.Clone(); + return *this; + } + + nsRegion& Copy(const nsRect& aRect) { + mBands.Clear(); + mBounds = nsRectAbsolute::FromRect(aRect); + return *this; + } + + nsRegion& Copy(const nsRectAbsolute& aRect) { + mBands.Clear(); + mBounds = aRect; + return *this; + } + + void EnsureSimplified() { + if (mBands.Length() == 1 && mBands.begin()->mStrips.Length() == 1) { + mBands.Clear(); + } + } + + static inline nsRectAbsolute BoxToRect(const pixman_box32_t& aBox) { + return nsRectAbsolute(aBox.x1, aBox.y1, aBox.x2, aBox.y2); + } + + void AddRect(const nsRectAbsolute& aRect) { +#ifdef DEBUG_REGIONS + class OperationStringGeneratorAddRect : public OperationStringGenerator { + public: + OperationStringGeneratorAddRect(nsRegion& aRegion, + const nsRectAbsolute& aRect) + : mRegion(&aRegion), mRegionCopy(aRegion), mRect(aRect) { + aRegion.mCurrentOpGenerator = this; + } + virtual ~OperationStringGeneratorAddRect() { + mRegion->mCurrentOpGenerator = nullptr; + } + + virtual void OutputOp() override { + std::stringstream stream; + mRegionCopy.OutputToStream("r", stream); + stream << "r.OrWith(nsRect(" << mRect.X() << ", " << mRect.Y() << ", " + << mRect.Width() << ", " << mRect.Height() << "));\n"; + gfxCriticalError() << stream.str(); + } + + private: + nsRegion* mRegion; + nsRegion mRegionCopy; + nsRectAbsolute mRect; + }; + + OperationStringGeneratorAddRect opGenerator(*this, aRect); +#endif + if (aRect.IsEmpty()) { + return; + } + + if (mBands.IsEmpty()) { + if (mBounds.IsEmpty()) { + mBounds = aRect; + return; + } else if (mBounds.Contains(aRect)) { + return; + } + + mBands.AppendElement(Band(mBounds)); + } + + mBounds = aRect.UnsafeUnion(mBounds); + + size_t idx = 0; + + Strip strip(aRect.X(), aRect.XMost()); + Band remaining(aRect); + + while (idx != mBands.Length()) { + if (mBands[idx].bottom <= remaining.top) { + // This band lies wholly above aRect. + idx++; + continue; + } + + if (remaining.top >= remaining.bottom) { + AssertState(); + EnsureSimplified(); + return; + } + + if (mBands[idx].top >= remaining.bottom) { + // This band lies wholly below aRect. + break; + } + + if (mBands[idx].EqualStrips(remaining)) { + mBands[idx].top = std::min(mBands[idx].top, remaining.top); + // Nothing to do for this band. Just expand. + remaining.top = mBands[idx].bottom; + CompressBefore(idx); + idx++; + continue; + } + + if (mBands[idx].top > remaining.top) { + auto before = mBands.InsertElementAt(idx, remaining); + before->bottom = mBands[idx + 1].top; + remaining.top = before->bottom; + CompressBefore(idx); + idx++; + CompressBefore(idx); + continue; + } + + if (mBands[idx].ContainsStrip(strip)) { + remaining.top = mBands[idx].bottom; + idx++; + continue; + } + + // mBands[idx].top <= remaining.top. + + if (mBands[idx].top < remaining.top) { + auto before = mBands.InsertElementAt(idx, Band(mBands[idx])); + before->bottom = remaining.top; + idx++; + mBands[idx].top = remaining.top; + continue; + } + + // mBands[idx].top == remaining.top + if (mBands[idx].bottom > remaining.bottom) { + auto below = mBands.InsertElementAt(idx + 1, Band(mBands[idx])); + below->top = remaining.bottom; + mBands[idx].bottom = remaining.bottom; + } + + mBands[idx].InsertStrip(strip); + CompressBefore(idx); + remaining.top = mBands[idx].bottom; + idx++; + CompressBefore(idx); + } + + if (remaining.top < remaining.bottom) { + // We didn't find any bands that overlapped aRect. + if (idx) { + if (mBands[idx - 1].bottom == remaining.top && + mBands[idx - 1].EqualStrips(remaining)) { + mBands[idx - 1].bottom = remaining.bottom; + CompressBefore(idx); + AssertState(); + EnsureSimplified(); + return; + } + } + mBands.InsertElementAt(idx, remaining); + idx++; + CompressBefore(idx); + } else { + CompressBefore(idx); + } + + AssertState(); + EnsureSimplified(); + } + + // Most callers could probably do this on the fly, if this ever shows up + // in profiles we could optimize this. + nsRectAbsolute CalculateBounds() const { + if (mBands.IsEmpty()) { + return mBounds; + } + + int32_t top = mBands.begin()->top; + int32_t bottom = mBands.LastElement().bottom; + + int32_t leftMost = mBands.begin()->mStrips.begin()->left; + int32_t rightMost = mBands.begin()->mStrips.LastElement().right; + for (const Band& band : mBands) { + leftMost = std::min(leftMost, band.mStrips.begin()->left); + rightMost = std::max(rightMost, band.mStrips.LastElement().right); + } + + return nsRectAbsolute(leftMost, top, rightMost, bottom); + } + + static uint32_t ComputeMergedAreaIncrease(const Band& aTopBand, + const Band& aBottomBand); + + // Returns true if idx is now referring to the 'next' band + bool CompressAdjacentBands(size_t& aIdx) { + if ((aIdx + 1) < mBands.Length()) { + if (mBands[aIdx + 1].top == mBands[aIdx].bottom && + mBands[aIdx + 1].EqualStrips(mBands[aIdx])) { + mBands[aIdx].bottom = mBands[aIdx + 1].bottom; + mBands.RemoveElementAt(aIdx + 1); + } + } + if (aIdx) { + if (mBands[aIdx - 1].bottom == mBands[aIdx].top && + mBands[aIdx].EqualStrips(mBands[aIdx - 1])) { + mBands[aIdx - 1].bottom = mBands[aIdx].bottom; + mBands.RemoveElementAt(aIdx); + return true; + } + } + return false; + } + + void CompressBefore(size_t& aIdx) { + if (aIdx && aIdx < mBands.Length()) { + if (mBands[aIdx - 1].bottom == mBands[aIdx].top && + mBands[aIdx - 1].EqualStrips(mBands[aIdx])) { + mBands[aIdx].top = mBands[aIdx - 1].top; + mBands.RemoveElementAt(aIdx - 1); + aIdx--; + } + } + } + + BandArray mBands; + // Considering we only ever OR with nsRects, the bounds should fit in an + // nsRect as well. + nsRectAbsolute mBounds; +#ifdef DEBUG_REGIONS + friend class OperationStringGenerator; + OperationStringGenerator* mCurrentOpGenerator; +#endif + + public: + class RectIterator { + const nsRegion& mRegion; + typename BandArray::const_iterator mCurrentBand; + typename StripArray::const_iterator mCurrentStrip; + + public: + explicit RectIterator(const nsRegion& aRegion) + : mRegion(aRegion), + mCurrentBand(aRegion.mBands.begin()) +#ifndef DEBUG + , + mCurrentStrip(nullptr) +#endif + { + mIsDone = mRegion.mBounds.IsEmpty(); + if (mCurrentBand != aRegion.mBands.end()) { + mCurrentStrip = mCurrentBand->mStrips.begin(); + } + } + + bool Done() const { return mIsDone; } + + const nsRect Get() const { + if (mRegion.mBands.IsEmpty()) { + return mRegion.GetBounds(); + } + return nsRect(mCurrentStrip->left, mCurrentBand->top, + mCurrentStrip->right - mCurrentStrip->left, + mCurrentBand->bottom - mCurrentBand->top); + } + + const nsRectAbsolute GetAbsolute() const { + if (mRegion.mBands.IsEmpty()) { + return mRegion.mBounds; + } + return nsRectAbsolute(mCurrentStrip->left, mCurrentBand->top, + mCurrentStrip->right, mCurrentBand->bottom); + } + + void Next() { + if (mRegion.mBands.IsEmpty()) { + mIsDone = true; + return; + } + + mCurrentStrip++; + if (mCurrentStrip == mCurrentBand->mStrips.end()) { + mCurrentBand++; + if (mCurrentBand != mRegion.mBands.end()) { + mCurrentStrip = mCurrentBand->mStrips.begin(); + } else { + mIsDone = true; + } + } + } + + bool mIsDone; + }; + + RectIterator RectIter() const { return RectIterator(*this); } +}; + +namespace mozilla { +namespace gfx { + +/** + * BaseIntRegions use int32_t coordinates. + */ +template <typename Derived, typename Rect, typename Point, typename Margin> +class BaseIntRegion { + friend class ::nsRegion; + + // Give access to all specializations of IntRegionTyped, not just ones that + // derive from this specialization of BaseIntRegion. + template <typename units> + friend class IntRegionTyped; + + public: + typedef Rect RectType; + typedef Point PointType; + typedef Margin MarginType; + + BaseIntRegion() = default; + MOZ_IMPLICIT BaseIntRegion(const Rect& aRect) : mImpl(ToRect(aRect)) {} + explicit BaseIntRegion(mozilla::gfx::ArrayView<pixman_box32_t> aRects) + : mImpl(aRects) {} + BaseIntRegion(const BaseIntRegion& aRegion) : mImpl(aRegion.mImpl) {} + BaseIntRegion(BaseIntRegion&& aRegion) : mImpl(std::move(aRegion.mImpl)) {} + Derived& operator=(const Rect& aRect) { + mImpl = ToRect(aRect); + return This(); + } + Derived& operator=(const Derived& aRegion) { + mImpl = aRegion.mImpl; + return This(); + } + Derived& operator=(Derived&& aRegion) { + mImpl = std::move(aRegion.mImpl); + return This(); + } + + bool operator==(const Derived& aRgn) const { return IsEqual(aRgn); } + bool operator!=(const Derived& aRgn) const { return !(*this == aRgn); } + + friend std::ostream& operator<<(std::ostream& stream, const Derived& m) { + return stream << m.mImpl; + } + + void AndWith(const Derived& aOther) { And(This(), aOther); } + void AndWith(const Rect& aOther) { And(This(), aOther); } + Derived& And(const Derived& aRgn1, const Derived& aRgn2) { + mImpl.And(aRgn1.mImpl, aRgn2.mImpl); + return This(); + } + Derived& And(const Derived& aRegion, const Rect& aRect) { + mImpl.And(aRegion.mImpl, ToRect(aRect)); + return This(); + } + Derived& And(const Rect& aRect, const Derived& aRegion) { + return And(aRegion, aRect); + } + Derived& And(const Rect& aRect1, const Rect& aRect2) { + Rect TmpRect; + + TmpRect.IntersectRect(aRect1, aRect2); + mImpl = ToRect(TmpRect); + return This(); + } + + Derived& OrWith(const Derived& aOther) { return Or(This(), aOther); } + Derived& OrWith(const Rect& aOther) { return Or(This(), aOther); } + Derived& Or(const Derived& aRgn1, const Derived& aRgn2) { + mImpl.Or(aRgn1.mImpl, aRgn2.mImpl); + return This(); + } + Derived& Or(const Derived& aRegion, const Rect& aRect) { + mImpl.Or(aRegion.mImpl, ToRect(aRect)); + return This(); + } + Derived& Or(const Rect& aRect, const Derived& aRegion) { + return Or(aRegion, aRect); + } + Derived& Or(const Rect& aRect1, const Rect& aRect2) { + mImpl = ToRect(aRect1); + return Or(This(), aRect2); + } + + Derived& XorWith(const Derived& aOther) { return Xor(This(), aOther); } + Derived& XorWith(const Rect& aOther) { return Xor(This(), aOther); } + Derived& Xor(const Derived& aRgn1, const Derived& aRgn2) { + mImpl.Xor(aRgn1.mImpl, aRgn2.mImpl); + return This(); + } + Derived& Xor(const Derived& aRegion, const Rect& aRect) { + mImpl.Xor(aRegion.mImpl, ToRect(aRect)); + return This(); + } + Derived& Xor(const Rect& aRect, const Derived& aRegion) { + return Xor(aRegion, aRect); + } + Derived& Xor(const Rect& aRect1, const Rect& aRect2) { + mImpl = ToRect(aRect1); + return Xor(This(), aRect2); + } + + Derived& SubOut(const Derived& aOther) { return Sub(This(), aOther); } + Derived& SubOut(const Rect& aOther) { return Sub(This(), aOther); } + Derived& Sub(const Derived& aRgn1, const Derived& aRgn2) { + mImpl.Sub(aRgn1.mImpl, aRgn2.mImpl); + return This(); + } + Derived& Sub(const Derived& aRegion, const Rect& aRect) { + mImpl.Sub(aRegion.mImpl, ToRect(aRect)); + return This(); + } + Derived& Sub(const Rect& aRect, const Derived& aRegion) { + return Sub(Derived(aRect), aRegion); + } + Derived& Sub(const Rect& aRect1, const Rect& aRect2) { + mImpl = ToRect(aRect1); + return Sub(This(), aRect2); + } + + /** + * Returns true iff the given point is inside the region. A region + * created from a rect (x=0, y=0, w=100, h=100) will NOT contain + * the point x=100, y=100. + */ + bool Contains(int aX, int aY) const { return mImpl.Contains(aX, aY); } + bool Contains(const Point& aPoint) const { + return mImpl.Contains(aPoint.x, aPoint.y); + } + bool Contains(const Rect& aRect) const { + return mImpl.Contains(ToRect(aRect)); + } + bool Contains(const Derived& aRgn) const { + return mImpl.Contains(aRgn.mImpl); + } + bool Intersects(const Rect& aRect) const { + return mImpl.Intersects(ToRect(aRect)); + } + + void MoveBy(int32_t aXOffset, int32_t aYOffset) { + MoveBy(Point(aXOffset, aYOffset)); + } + void MoveBy(Point aPt) { mImpl.MoveBy(aPt.X(), aPt.Y()); } + Derived MovedBy(int32_t aXOffset, int32_t aYOffset) const { + return MovedBy(Point(aXOffset, aYOffset)); + } + Derived MovedBy(const Point& aPt) const { + Derived copy(This()); + copy.MoveBy(aPt); + return copy; + } + + Derived Intersect(const Derived& aOther) const { + Derived intersection; + intersection.And(This(), aOther); + return intersection; + } + + void Inflate(const Margin& aMargin) { + mImpl.Inflate( + nsMargin(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left)); + } + Derived Inflated(const Margin& aMargin) const { + Derived copy(This()); + copy.Inflate(aMargin); + return copy; + } + + void SetEmpty() { mImpl.SetEmpty(); } + + bool IsEmpty() const { return mImpl.IsEmpty(); } + bool IsComplex() const { return mImpl.IsComplex(); } + bool IsEqual(const Derived& aRegion) const { + return mImpl.IsEqual(aRegion.mImpl); + } + uint32_t GetNumRects() const { return mImpl.GetNumRects(); } + Rect GetBounds() const { return FromRect(mImpl.GetBounds()); } + uint64_t Area() const { return mImpl.Area(); } + nsRegion ToAppUnits(nscoord aAppUnitsPerPixel) const { + nsRegion result; + for (auto iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect appRect = ::ToAppUnits(iter.Get(), aAppUnitsPerPixel); + result.Or(result, appRect); + } + return result; + } + Rect GetLargestRectangle(const Rect& aContainingRect = Rect()) const { + return FromRect(mImpl.GetLargestRectangle(ToRect(aContainingRect))); + } + + Derived& ScaleRoundOut(float aXScale, float aYScale) { + mImpl.ScaleRoundOut(aXScale, aYScale); + return This(); + } + + Derived& ScaleInverseRoundOut(float aXScale, float aYScale) { + mImpl.ScaleInverseRoundOut(aXScale, aYScale); + return This(); + } + + // Prefer using TransformBy(matrix, region) from UnitTransforms.h, + // as applying the transform should typically change the unit system. + // TODO(botond): Move this to IntRegionTyped and disable it for + // unit != UnknownUnits. + Derived& Transform(const mozilla::gfx::Matrix4x4& aTransform) { + mImpl.Transform(aTransform); + return This(); + } + + /** + * Make sure the region has at most aMaxRects by adding area to it + * if necessary. The simplified region will be a superset of the + * original region. The simplified region's bounding box will be + * the same as for the current region. + */ + void SimplifyOutward(uint32_t aMaxRects) { mImpl.SimplifyOutward(aMaxRects); } + void SimplifyOutwardByArea(uint32_t aThreshold) { + mImpl.SimplifyOutwardByArea(aThreshold); + } + /** + * Make sure the region has at most aMaxRects by removing area from + * it if necessary. The simplified region will be a subset of the + * original region. + */ + void SimplifyInward(uint32_t aMaxRects) { mImpl.SimplifyInward(aMaxRects); } + + typedef void (*visitFn)(void* closure, VisitSide side, int x1, int y1, int x2, + int y2); + void VisitEdges(visitFn visit, void* closure) const { + mImpl.VisitEdges(visit, closure); + } + + nsCString ToString() const { return mImpl.ToString(); } + + class RectIterator { + nsRegion::RectIterator mImpl; // The underlying iterator. + mutable Rect mTmp; // The most recently gotten rectangle. + + public: + explicit RectIterator(const BaseIntRegion& aRegion) + : mImpl(aRegion.mImpl) {} + + bool Done() const { return mImpl.Done(); } + + const Rect& Get() const { + mTmp = FromRect(mImpl.Get()); + return mTmp; + } + + void Next() { mImpl.Next(); } + }; + + RectIterator RectIter() const { return RectIterator(*this); } + + protected: + // Expose enough to derived classes from them to define conversions + // between different types of BaseIntRegions. + explicit BaseIntRegion(const nsRegion& aImpl) : mImpl(aImpl) {} + const nsRegion& Impl() const { return mImpl; } + + private: + nsRegion mImpl; + + static nsRect ToRect(const Rect& aRect) { + return nsRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + } + static Rect FromRect(const nsRect& aRect) { + return Rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + } + + Derived& This() { return *static_cast<Derived*>(this); } + const Derived& This() const { return *static_cast<const Derived*>(this); } +}; + +template <class units> +class IntRegionTyped + : public BaseIntRegion<IntRegionTyped<units>, IntRectTyped<units>, + IntPointTyped<units>, IntMarginTyped<units>> { + typedef BaseIntRegion<IntRegionTyped<units>, IntRectTyped<units>, + IntPointTyped<units>, IntMarginTyped<units>> + Super; + + // Make other specializations of IntRegionTyped friends. + template <typename OtherUnits> + friend class IntRegionTyped; + + static_assert(IsPixel<units>::value, + "'units' must be a coordinate system tag"); + + public: + typedef IntRectTyped<units> RectType; + typedef IntPointTyped<units> PointType; + typedef IntMarginTyped<units> MarginType; + + // Forward constructors. + IntRegionTyped() = default; + MOZ_IMPLICIT IntRegionTyped(const IntRectTyped<units>& aRect) + : Super(aRect) {} + IntRegionTyped(const IntRegionTyped& aRegion) : Super(aRegion) {} + explicit IntRegionTyped(mozilla::gfx::ArrayView<pixman_box32_t> aRects) + : Super(aRects) {} + IntRegionTyped(IntRegionTyped&& aRegion) : Super(std::move(aRegion)) {} + + // Assignment operators need to be forwarded as well, otherwise the compiler + // will declare deleted ones. + IntRegionTyped& operator=(const IntRegionTyped& aRegion) { + return Super::operator=(aRegion); + } + IntRegionTyped& operator=(IntRegionTyped&& aRegion) { + return Super::operator=(std::move(aRegion)); + } + + static IntRegionTyped FromUnknownRegion(const IntRegion& aRegion) { + return IntRegionTyped(aRegion.Impl()); + } + IntRegion ToUnknownRegion() const { + // Need |this->| because Impl() is defined in a dependent base class. + return IntRegion(this->Impl()); + } + + private: + // This is deliberately private, so calling code uses FromUnknownRegion(). + explicit IntRegionTyped(const nsRegion& aRegion) : Super(aRegion) {} +}; + +} // namespace gfx +} // namespace mozilla + +typedef mozilla::gfx::IntRegion nsIntRegion; + +#endif diff --git a/gfx/src/nsRegionFwd.h b/gfx/src/nsRegionFwd.h new file mode 100644 index 0000000000..b029b35c25 --- /dev/null +++ b/gfx/src/nsRegionFwd.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRegionFwd_h__ +#define nsRegionFwd_h__ + +// Forward declare enough things to define the typedef |nsIntRegion|. + +namespace mozilla { +namespace gfx { + +struct UnknownUnits; + +template <class units> +class IntRegionTyped; + +typedef IntRegionTyped<UnknownUnits> IntRegion; + +} // namespace gfx +} // namespace mozilla + +typedef mozilla::gfx::IntRegion nsIntRegion; + +#endif diff --git a/gfx/src/nsSize.h b/gfx/src/nsSize.h new file mode 100644 index 0000000000..df1fa5570e --- /dev/null +++ b/gfx/src/nsSize.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSSIZE_H +#define NSSIZE_H + +#include "nsCoord.h" +#include "mozilla/gfx/BaseSize.h" +#include "mozilla/gfx/Point.h" + +// Maximum allowable size +inline constexpr nscoord NS_MAXSIZE = nscoord_MAX; + +typedef mozilla::gfx::IntSize nsIntSize; + +struct nsSize : public mozilla::gfx::BaseSize<nscoord, nsSize> { + typedef mozilla::gfx::BaseSize<nscoord, nsSize> Super; + + constexpr nsSize() {} + constexpr nsSize(nscoord aWidth, nscoord aHeight) : Super(aWidth, aHeight) {} + + inline mozilla::gfx::IntSize ScaleToNearestPixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const; + inline mozilla::gfx::IntSize ToNearestPixels(nscoord aAppUnitsPerPixel) const; + + /** + * Return this size scaled to a different appunits per pixel (APP) ratio. + * @param aFromAPP the APP to scale from + * @param aToAPP the APP to scale to + */ + [[nodiscard]] inline nsSize ScaleToOtherAppUnits(int32_t aFromAPP, + int32_t aToAPP) const; +}; + +inline mozilla::gfx::IntSize nsSize::ScaleToNearestPixels( + float aXScale, float aYScale, nscoord aAppUnitsPerPixel) const { + return mozilla::gfx::IntSize( + NSToIntRoundUp(NSAppUnitsToDoublePixels(width, aAppUnitsPerPixel) * + aXScale), + NSToIntRoundUp(NSAppUnitsToDoublePixels(height, aAppUnitsPerPixel) * + aYScale)); +} + +inline mozilla::gfx::IntSize nsSize::ToNearestPixels( + nscoord aAppUnitsPerPixel) const { + return ScaleToNearestPixels(1.0f, 1.0f, aAppUnitsPerPixel); +} + +inline nsSize nsSize::ScaleToOtherAppUnits(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP != aToAPP) { + nsSize size; + size.width = NSToCoordRound(NSCoordScale(width, aFromAPP, aToAPP)); + size.height = NSToCoordRound(NSCoordScale(height, aFromAPP, aToAPP)); + return size; + } + return *this; +} + +inline nsSize IntSizeToAppUnits(mozilla::gfx::IntSize aSize, + nscoord aAppUnitsPerPixel) { + return nsSize(NSIntPixelsToAppUnits(aSize.width, aAppUnitsPerPixel), + NSIntPixelsToAppUnits(aSize.height, aAppUnitsPerPixel)); +} + +#endif /* NSSIZE_H */ diff --git a/gfx/src/nsThebesFontEnumerator.cpp b/gfx/src/nsThebesFontEnumerator.cpp new file mode 100644 index 0000000000..baf8c50142 --- /dev/null +++ b/gfx/src/nsThebesFontEnumerator.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsThebesFontEnumerator.h" +#include <stdint.h> // for uint32_t +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/Promise.h" // for mozilla::dom::Promise +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsDebug.h" // for NS_ENSURE_ARG_POINTER +#include "nsError.h" // for NS_OK, NS_FAILED, nsresult +#include "nsAtom.h" // for nsAtom, NS_Atomize +#include "nsID.h" +#include "nsString.h" // for nsAutoCString, nsAutoString, etc +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nscore.h" // for char16_t, NS_IMETHODIMP + +using mozilla::MakeUnique; +using mozilla::Runnable; +using mozilla::UniquePtr; + +NS_IMPL_ISUPPORTS(nsThebesFontEnumerator, nsIFontEnumerator) + +nsThebesFontEnumerator::nsThebesFontEnumerator() = default; + +NS_IMETHODIMP +nsThebesFontEnumerator::EnumerateAllFonts(nsTArray<nsString>& aResult) { + return EnumerateFonts(nullptr, nullptr, aResult); +} + +NS_IMETHODIMP +nsThebesFontEnumerator::EnumerateFonts(const char* aLangGroup, + const char* aGeneric, + nsTArray<nsString>& aResult) { + nsAutoCString generic; + if (aGeneric) + generic.Assign(aGeneric); + else + generic.SetIsVoid(true); + + RefPtr<nsAtom> langGroupAtom; + if (aLangGroup) { + nsAutoCString lowered; + lowered.Assign(aLangGroup); + ToLowerCase(lowered); + langGroupAtom = NS_Atomize(lowered); + } + + return gfxPlatform::GetPlatform()->GetFontList(langGroupAtom, generic, + aResult); +} + +struct EnumerateFontsPromise final { + explicit EnumerateFontsPromise(mozilla::dom::Promise* aPromise) + : mPromise(aPromise) { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(NS_IsMainThread()); + } + + RefPtr<mozilla::dom::Promise> mPromise; +}; + +class EnumerateFontsResult final : public Runnable { + public: + EnumerateFontsResult(nsresult aRv, + UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise, + nsTArray<nsString> aFontList) + : Runnable("EnumerateFontsResult"), + mRv(aRv), + mEnumerateFontsPromise(std::move(aEnumerateFontsPromise)), + mFontList(std::move(aFontList)), + mWorkerThread(do_GetCurrentThread()) { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_FAILED(mRv)) { + mEnumerateFontsPromise->mPromise->MaybeReject(mRv); + } else { + mEnumerateFontsPromise->mPromise->MaybeResolve(mFontList); + } + + mWorkerThread->Shutdown(); + + return NS_OK; + } + + private: + nsresult mRv; + UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise; + nsTArray<nsString> mFontList; + nsCOMPtr<nsIThread> mWorkerThread; +}; + +class EnumerateFontsTask final : public Runnable { + public: + EnumerateFontsTask(nsAtom* aLangGroupAtom, const nsAutoCString& aGeneric, + UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise, + nsIEventTarget* aMainThreadTarget) + : Runnable("EnumerateFontsTask"), + mLangGroupAtom(aLangGroupAtom), + mGeneric(aGeneric), + mEnumerateFontsPromise(std::move(aEnumerateFontsPromise)), + mMainThreadTarget(aMainThreadTarget) { + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(!NS_IsMainThread()); + + nsTArray<nsString> fontList; + + nsresult rv = gfxPlatform::GetPlatform()->GetFontList(mLangGroupAtom, + mGeneric, fontList); + nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsResult( + rv, std::move(mEnumerateFontsPromise), std::move(fontList)); + mMainThreadTarget->Dispatch(runnable.forget()); + + return NS_OK; + } + + private: + RefPtr<nsAtom> mLangGroupAtom; + nsAutoCStringN<16> mGeneric; + UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise; + RefPtr<nsIEventTarget> mMainThreadTarget; +}; + +NS_IMETHODIMP +nsThebesFontEnumerator::EnumerateAllFontsAsync( + JSContext* aCx, JS::MutableHandle<JS::Value> aRval) { + return EnumerateFontsAsync(nullptr, nullptr, aCx, aRval); +} + +NS_IMETHODIMP +nsThebesFontEnumerator::EnumerateFontsAsync( + const char* aLangGroup, const char* aGeneric, JSContext* aCx, + JS::MutableHandle<JS::Value> aRval) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx); + NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED); + + mozilla::ErrorResult errv; + RefPtr<mozilla::dom::Promise> promise = + mozilla::dom::Promise::Create(global, errv); + if (errv.Failed()) { + return errv.StealNSResult(); + } + + auto enumerateFontsPromise = MakeUnique<EnumerateFontsPromise>(promise); + + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread("FontEnumThread", getter_AddRefs(thread)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsAtom> langGroupAtom; + if (aLangGroup) { + nsAutoCStringN<16> lowered; + lowered.Assign(aLangGroup); + ToLowerCase(lowered); + langGroupAtom = NS_Atomize(lowered); + } + + nsAutoCString generic; + if (aGeneric) { + generic.Assign(aGeneric); + } else { + generic.SetIsVoid(true); + } + + nsCOMPtr<nsIEventTarget> target = global->SerialEventTarget(); + nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsTask( + langGroupAtom, generic, std::move(enumerateFontsPromise), target); + thread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + + if (!ToJSValue(aCx, promise, aRval)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThebesFontEnumerator::HaveFontFor(const char* aLangGroup, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsThebesFontEnumerator::GetDefaultFont(const char* aLangGroup, + const char* aGeneric, + char16_t** aResult) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aLangGroup) || + NS_WARN_IF(!aGeneric)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + nsAutoCString defaultFontName(gfxPlatform::GetPlatform()->GetDefaultFontName( + nsDependentCString(aLangGroup), nsDependentCString(aGeneric))); + if (!defaultFontName.IsEmpty()) { + *aResult = UTF8ToNewUnicode(defaultFontName); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThebesFontEnumerator::GetStandardFamilyName(const char16_t* aName, + char16_t** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aName); + + nsAutoString name(aName); + if (name.IsEmpty()) { + *aResult = nullptr; + return NS_OK; + } + + nsAutoCString family; + gfxPlatform::GetPlatform()->GetStandardFamilyName( + NS_ConvertUTF16toUTF8(aName), family); + if (family.IsEmpty()) { + *aResult = nullptr; + return NS_OK; + } + *aResult = UTF8ToNewUnicode(family); + return NS_OK; +} diff --git a/gfx/src/nsThebesFontEnumerator.h b/gfx/src/nsThebesFontEnumerator.h new file mode 100644 index 0000000000..c0f2d79b68 --- /dev/null +++ b/gfx/src/nsThebesFontEnumerator.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSTHEBESFONTENUMERATOR_H_ +#define _NSTHEBESFONTENUMERATOR_H_ + +#include "mozilla/Attributes.h" // for final +#include "nsIFontEnumerator.h" // for NS_DECL_NSIFONTENUMERATOR, etc +#include "nsISupports.h" // for NS_DECL_ISUPPORTS + +class nsThebesFontEnumerator final : public nsIFontEnumerator { + ~nsThebesFontEnumerator() = default; + + public: + nsThebesFontEnumerator(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSIFONTENUMERATOR +}; + +#endif /* _NSTHEBESFONTENUMERATOR_H_ */ diff --git a/gfx/src/nsTransform2D.cpp b/gfx/src/nsTransform2D.cpp new file mode 100644 index 0000000000..655511a626 --- /dev/null +++ b/gfx/src/nsTransform2D.cpp @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTransform2D.h" + +void nsTransform2D ::TransformCoord(nscoord* ptX, nscoord* ptY) const { + *ptX = NSToCoordRound(*ptX * m00 + m20); + *ptY = NSToCoordRound(*ptY * m11 + m21); +} + +void nsTransform2D ::TransformCoord(nscoord* aX, nscoord* aY, nscoord* aWidth, + nscoord* aHeight) const { + nscoord x2 = *aX + *aWidth; + nscoord y2 = *aY + *aHeight; + TransformCoord(aX, aY); + TransformCoord(&x2, &y2); + *aWidth = x2 - *aX; + *aHeight = y2 - *aY; +} diff --git a/gfx/src/nsTransform2D.h b/gfx/src/nsTransform2D.h new file mode 100644 index 0000000000..fb04f16a12 --- /dev/null +++ b/gfx/src/nsTransform2D.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTransform2D_h___ +#define nsTransform2D_h___ + +#include "nsCoord.h" + +class nsTransform2D { + private: + /** + * This represents the following matrix (note that the order of row/column + * indices is opposite to usual notation) + * + * / m00 0 m20 \ + * M = | 0 m11 m21 | + * \ 0 0 1 / + * + * Transformation of a coordinate (x, y) is obtained by setting + * v = (x, y, 1)^T and evaluating M . v + **/ + + float m00, m11, m20, m21; + + public: + nsTransform2D(void) { + m20 = m21 = 0.0f; + m00 = m11 = 1.0f; + } + + ~nsTransform2D(void) = default; + + /** + * set this transform to a translation + * + * @param tx, x translation + * @param ty, y translation + **/ + + void SetToTranslate(float tx, float ty) { + m00 = m11 = 1.0f; + m20 = tx; + m21 = ty; + } + + /** + * get the translation portion of this transform + * + * @param pt, Point to return translation values in + **/ + + void GetTranslationCoord(nscoord* ptX, nscoord* ptY) const { + *ptX = NSToCoordRound(m20); + *ptY = NSToCoordRound(m21); + } + + /** + * apply matrix to vector + * + * @param pt Point to transform + **/ + + void TransformCoord(nscoord* ptX, nscoord* ptY) const; + + /** + * apply matrix to rect + * + * @param rect Rect to transform + **/ + + void TransformCoord(nscoord* aX, nscoord* aY, nscoord* aWidth, + nscoord* aHeight) const; + + /** + * add a scale to a Transform via x, y pair + * + * @param ptX x value to add as x scale + * @param ptY y value to add as y scale + **/ + + void AddScale(float ptX, float ptY) { + m00 *= ptX; + m11 *= ptY; + } + + /** + * Set the scale (overriding any previous calls to AddScale, but leaving + * any existing translation). + * + * @param ptX x value to add as x scale + * @param ptY y value to add as y scale + **/ + + void SetScale(float ptX, float ptY) { + m00 = ptX; + m11 = ptY; + } +}; + +#endif |