diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e9550d05..6595a41e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Setup clang-format uses: aminya/setup-cpp@v1 with: - clangformat: 20 + clangformat: 16 - name: Install dependencies run: npm ci diff --git a/.vscode/settings.json b/.vscode/settings.json index be313d921..89d56cba4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -99,7 +99,8 @@ "span": "cpp", "execution": "cpp", "cassert": "cpp", - "print": "cpp" + "print": "cpp", + "version": "cpp" }, "cmake.buildDirectory": "${workspaceFolder}/build/targets/darwin", "C_Cpp.default.cppStandard": "c++17", diff --git a/src/bindings/dom/browsing_context.cpp b/src/bindings/dom/browsing_context.cpp index b47e93a03..3118dc130 100644 --- a/src/bindings/dom/browsing_context.cpp +++ b/src/bindings/dom/browsing_context.cpp @@ -2,6 +2,7 @@ #include "./browsing_context.hpp" using namespace std; +using namespace endor; namespace dombinding { @@ -23,7 +24,7 @@ namespace dombinding BrowsingContext::BrowsingContext(const Napi::CallbackInfo &info) : RuntimeContextBase(info) - , client_context_(TrClientContextPerProcess::Get()) + , client_context_(endor::TrClientContextPerProcess::Get()) { assert(this->contextImpl != nullptr && "contextImpl should not be null"); client_context_->browsingContext = this->contextImpl; diff --git a/src/bindings/dom/browsing_context.hpp b/src/bindings/dom/browsing_context.hpp index c9b56f742..c3273b6da 100644 --- a/src/bindings/dom/browsing_context.hpp +++ b/src/bindings/dom/browsing_context.hpp @@ -8,7 +8,7 @@ namespace dombinding { - class BrowsingContext : public RuntimeContextBase + class BrowsingContext : public RuntimeContextBase { public: static void Init(Napi::Env env, Napi::Object exports); @@ -20,7 +20,7 @@ namespace dombinding Napi::Value Start(const Napi::CallbackInfo &info); private: - TrClientContextPerProcess *client_context_; + endor::TrClientContextPerProcess *client_context_; static thread_local Napi::FunctionReference *constructor; }; } diff --git a/src/bindings/dom/worker_context.cpp b/src/bindings/dom/worker_context.cpp index 1f4886b6f..7fffc07f6 100644 --- a/src/bindings/dom/worker_context.cpp +++ b/src/bindings/dom/worker_context.cpp @@ -2,6 +2,7 @@ #include "./worker_context.hpp" using namespace std; +using namespace endor; namespace dombinding { diff --git a/src/bindings/dom/worker_context.hpp b/src/bindings/dom/worker_context.hpp index be354f76e..3465444f7 100644 --- a/src/bindings/dom/worker_context.hpp +++ b/src/bindings/dom/worker_context.hpp @@ -7,7 +7,7 @@ namespace dombinding { - class WorkerContext : public RuntimeContextBase + class WorkerContext : public RuntimeContextBase { public: static void Init(Napi::Env env, Napi::Object exports); diff --git a/src/bindings/env/client_context.cpp b/src/bindings/env/client_context.cpp index 90ca3dfbb..b2be08ed1 100644 --- a/src/bindings/env/client_context.cpp +++ b/src/bindings/env/client_context.cpp @@ -1,6 +1,9 @@ #include "client_context.hpp" #include "common/xr/types.hpp" +using namespace std; +using namespace endor; + namespace bindings { thread_local Napi::FunctionReference *ClientContext::constructor; diff --git a/src/bindings/env/client_context.hpp b/src/bindings/env/client_context.hpp index d59233b75..96ed5af08 100644 --- a/src/bindings/env/client_context.hpp +++ b/src/bindings/env/client_context.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include "client/per_process.hpp" +#include using namespace std; @@ -18,7 +18,7 @@ namespace bindings Napi::Value FastPerformanceNow(const Napi::CallbackInfo &info); private: - TrClientContextPerProcess *clientContext = nullptr; + endor::TrClientContextPerProcess *clientContext = nullptr; private: static thread_local Napi::FunctionReference *constructor; diff --git a/src/bindings/messaging/event_target.cpp b/src/bindings/messaging/event_target.cpp index 8443aab01..1ae93c77c 100644 --- a/src/bindings/messaging/event_target.cpp +++ b/src/bindings/messaging/event_target.cpp @@ -1,5 +1,7 @@ #include "./event_target.hpp" +using namespace endor; + namespace bindings { namespace messaging @@ -45,7 +47,7 @@ namespace bindings Napi::Env env = info.Env(); Napi::HandleScope scope(env); - clientContext = TrClientContextPerProcess::Get(); + clientContext = endor::TrClientContextPerProcess::Get(); if (clientContext == nullptr) { Napi::Error::New(env, "The client context is not initialized.").ThrowAsJavaScriptException(); diff --git a/src/bindings/messaging/event_target.hpp b/src/bindings/messaging/event_target.hpp index 169fc154d..420e3a40a 100644 --- a/src/bindings/messaging/event_target.hpp +++ b/src/bindings/messaging/event_target.hpp @@ -21,7 +21,7 @@ namespace bindings Napi::Value DispatchEvent(const Napi::CallbackInfo &info); private: - TrClientContextPerProcess *clientContext; + endor::TrClientContextPerProcess *clientContext; Napi::ThreadSafeFunction eventListener; thread eventRecvThread; bool recvingEvents = false; diff --git a/src/bindings/renderer/animation_frame_listener.cpp b/src/bindings/renderer/animation_frame_listener.cpp index 486b708b2..5d3c0997c 100644 --- a/src/bindings/renderer/animation_frame_listener.cpp +++ b/src/bindings/renderer/animation_frame_listener.cpp @@ -1,5 +1,8 @@ #include "animation_frame_listener.hpp" +using namespace std; +using namespace endor; + namespace bindings { thread_local Napi::FunctionReference *AnimationFrameListener::constructor; diff --git a/src/bindings/renderer/animation_frame_listener.hpp b/src/bindings/renderer/animation_frame_listener.hpp index 678f97b10..3adc8acf6 100644 --- a/src/bindings/renderer/animation_frame_listener.hpp +++ b/src/bindings/renderer/animation_frame_listener.hpp @@ -30,7 +30,7 @@ namespace bindings private: atomic connected = false; - TrClientContextPerProcess *clientContext = nullptr; + endor::TrClientContextPerProcess *clientContext = nullptr; Napi::ThreadSafeFunction onframeTsfn; uv_loop_t *eventloop; uv_timer_t tickHandle; diff --git a/src/client/animation/animatable.cpp b/src/client/animation/animatable.cpp index 14b3bccac..812db92df 100644 --- a/src/client/animation/animatable.cpp +++ b/src/client/animation/animatable.cpp @@ -1,16 +1,19 @@ #include "./animatable.hpp" -namespace dom +namespace endor { - using namespace std; - - Animation &Animatable::animate(Keyframes &keyframes) + namespace dom { - throw runtime_error("Not implemented"); - } + using namespace std; - vector> Animatable::getAnimations(optional options) - { - return {}; + Animation &Animatable::animate(Keyframes &keyframes) + { + throw runtime_error("Not implemented"); + } + + vector> Animatable::getAnimations(optional options) + { + return {}; + } } -} +} // namespace endor diff --git a/src/client/animation/animatable.hpp b/src/client/animation/animatable.hpp index 8175a5cc9..051085f4c 100644 --- a/src/client/animation/animatable.hpp +++ b/src/client/animation/animatable.hpp @@ -7,22 +7,25 @@ #include "./animation.hpp" #include "./keyframes.hpp" -namespace dom +namespace endor { - class Element; - - struct GetAnimationsOptions + namespace dom { - bool subtree; - }; + class Element; - class Animatable - { - public: - virtual std::shared_ptr getAnimationTarget() = 0; + struct GetAnimationsOptions + { + bool subtree; + }; + + class Animatable + { + public: + virtual std::shared_ptr getAnimationTarget() = 0; - Animation &animate(Keyframes &keyframes); - std::vector> getAnimations( - std::optional options = std::nullopt); - }; -} + Animation &animate(Keyframes &keyframes); + std::vector> getAnimations( + std::optional options = std::nullopt); + }; + } +} // namespace endor diff --git a/src/client/animation/animation-inl.hpp b/src/client/animation/animation-inl.hpp index 16b0f1c2c..f61b36c53 100644 --- a/src/client/animation/animation-inl.hpp +++ b/src/client/animation/animation-inl.hpp @@ -5,22 +5,25 @@ #include "./animation.hpp" #include "./animation_timeline.hpp" -namespace dom +namespace endor { - template - std::shared_ptr Animation::MakeAnimation(std::unique_ptr effect, - std::shared_ptr timeline) + namespace dom { - if (TR_UNLIKELY(timeline == nullptr)) - return nullptr; - - auto deleter = [](T *animation) + template + std::shared_ptr Animation::MakeAnimation(std::unique_ptr effect, + std::shared_ptr timeline) { - if (TR_LIKELY(animation != nullptr)) - delete animation; - }; - auto animation = std::shared_ptr(new T(std::move(effect), timeline), deleter); - timeline->animationAttached(animation); - return animation; + if (TR_UNLIKELY(timeline == nullptr)) + return nullptr; + + auto deleter = [](T *animation) + { + if (TR_LIKELY(animation != nullptr)) + delete animation; + }; + auto animation = std::shared_ptr(new T(std::move(effect), timeline), deleter); + timeline->animationAttached(animation); + return animation; + } } -} +} // namespace endor diff --git a/src/client/animation/animation.cpp b/src/client/animation/animation.cpp index f5c4781cf..1841d3231 100644 --- a/src/client/animation/animation.cpp +++ b/src/client/animation/animation.cpp @@ -2,163 +2,166 @@ #include "./animation_effect.hpp" #include "./animation_timeline.hpp" -namespace dom +namespace endor { - using namespace std; - - Animation::Animation(unique_ptr effect, - shared_ptr timeline) - : effect_(move(effect)) - , timeline_(timeline) - , id_("") - , pending_(false) - , ready_(false) - , play_state_(kPlayStateIdle) - , playback_rate_(1.0f) - { - } - - Animation::~Animation() - { - if (!timeline_.expired()) + namespace dom + { + using namespace std; + + Animation::Animation(unique_ptr effect, + shared_ptr timeline) + : effect_(move(effect)) + , timeline_(timeline) + , id_("") + , pending_(false) + , ready_(false) + , play_state_(kPlayStateIdle) + , playback_rate_(1.0f) { - auto timeline = timeline_.lock(); - if (timeline) - timeline->detachInvalidAnimations(); } - } - bool Animation::update(TimingUpdateReason reason) - { - // TODO(yorkie): update the animation timings, and mark the target elements to be needed to recalculate styles. - return true; - } + Animation::~Animation() + { + if (!timeline_.expired()) + { + auto timeline = timeline_.lock(); + if (timeline) + timeline->detachInvalidAnimations(); + } + } - void Animation::cancel() - { - play_state_ = kPlayStateIdle; - pending_ = false; - ready_ = false; + bool Animation::update(TimingUpdateReason reason) + { + // TODO(yorkie): update the animation timings, and mark the target elements to be needed to recalculate styles. + return true; + } - // TODO: clear all effects - // TODO: abort playback - } + void Animation::cancel() + { + play_state_ = kPlayStateIdle; + pending_ = false; + ready_ = false; - void Animation::commitStyles() - { - // TODO: commit animation styles to target element's style - } + // TODO: clear all effects + // TODO: abort playback + } - void Animation::finish() - { - // TODO: finish playback of the animation - } + void Animation::commitStyles() + { + // TODO: commit animation styles to target element's style + } - void Animation::pause() - { - // TODO: suspends playback of the animation - } + void Animation::finish() + { + // TODO: finish playback of the animation + } - void Animation::persist() - { - replace_state_ = ReplaceState::kReplaceStatePersisted; - } + void Animation::pause() + { + // TODO: suspends playback of the animation + } - void Animation::play() - { - play_state_ = kPlayStateRunning; - } + void Animation::persist() + { + replace_state_ = ReplaceState::kReplaceStatePersisted; + } - void Animation::reverse() - { - } + void Animation::play() + { + play_state_ = kPlayStateRunning; + } - void Animation::updatePlaybackRate(float playback_rate) - { - playback_rate_ = playback_rate; - } + void Animation::reverse() + { + } - bool Animation::updateFrameToStyle(client_cssom::ComputedStyle &) - { - // TODO(yorkie): update the animation frame to the style. - return false; - } + void Animation::updatePlaybackRate(float playback_rate) + { + playback_rate_ = playback_rate; + } - bool Animation::updatePropertyToStyle(client_cssom::ComputedStyle &style, const std::string &property) - { - // Skip if the timeline is not active or expired. - if (TR_UNLIKELY(timeline_.expired() || !timeline_.lock()->isActive())) + bool Animation::updateFrameToStyle(client_cssom::ComputedStyle &) + { + // TODO(yorkie): update the animation frame to the style. return false; + } - // Skip if the property is not animatable or not set in the style. - if (!style.hasProperty(property)) + bool Animation::updatePropertyToStyle(client_cssom::ComputedStyle &style, const std::string &property) + { + // Skip if the timeline is not active or expired. + if (TR_UNLIKELY(timeline_.expired() || !timeline_.lock()->isActive())) + return false; + + // Skip if the property is not animatable or not set in the style. + if (!style.hasProperty(property)) + return false; + + // Switches the playing state based on the current. + if (play_state_ == kPlayStateIdle || + play_state_ == kPlayStateFinished) + { + play(); + } + else if (play_state_ == kPlayStatePaused) + { + play_state_ = kPlayStateRunning; + } + + // TODO(yorkie): update the property based on the animation effect. return false; + } - // Switches the playing state based on the current. - if (play_state_ == kPlayStateIdle || - play_state_ == kPlayStateFinished) + std::optional Animation::currentTime() const { - play(); + return hold_time_ ? hold_time_ : calculateCurrentTime(); } - else if (play_state_ == kPlayStatePaused) + + void Animation::setCurrentTime(float time) { - play_state_ = kPlayStateRunning; + if (hold_time_ || !start_time_ || timeline_.expired() || !timeline_.lock()->isActive() || + playback_rate_ == 0.0f) + { + hold_time_ = time; + } + else + { + start_time_ = calculateStartTime(time); + } } - // TODO(yorkie): update the property based on the animation effect. - return false; - } - - std::optional Animation::currentTime() const - { - return hold_time_ ? hold_time_ : calculateCurrentTime(); - } - - void Animation::setCurrentTime(float time) - { - if (hold_time_ || !start_time_ || timeline_.expired() || !timeline_.lock()->isActive() || - playback_rate_ == 0.0f) + const AnimationEffect &Animation::effect() const { - hold_time_ = time; + return *effect_; } - else + + AnimationEffect &Animation::effect() { - start_time_ = calculateStartTime(time); + return *effect_; } - } - - const AnimationEffect &Animation::effect() const - { - return *effect_; - } - - AnimationEffect &Animation::effect() - { - return *effect_; - } - optional Animation::calculateStartTime(float current_time) const - { - optional start_time; - if (timeline_.expired()) + optional Animation::calculateStartTime(float current_time) const { - auto timeline_time = timeline_.lock()->currentTime(); - if (timeline_time.has_value()) - start_time = timeline_time.value() - current_time / playback_rate_; + optional start_time; + if (timeline_.expired()) + { + auto timeline_time = timeline_.lock()->currentTime(); + if (timeline_time.has_value()) + start_time = timeline_time.value() - current_time / playback_rate_; + } + return start_time; } - return start_time; - } - optional Animation::calculateCurrentTime() const - { - if (!start_time_.has_value() || timeline_.expired()) - return std::nullopt; - auto timeline = timeline_.lock(); - if (!timeline->isActive()) - return std::nullopt; - - auto timeline_time = timeline->currentTime(); - assert(timeline_time.has_value() && "The timeline time should be valid."); - return (timeline_time.value() - start_time_.value()) * playback_rate_; + optional Animation::calculateCurrentTime() const + { + if (!start_time_.has_value() || timeline_.expired()) + return std::nullopt; + auto timeline = timeline_.lock(); + if (!timeline->isActive()) + return std::nullopt; + + auto timeline_time = timeline->currentTime(); + assert(timeline_time.has_value() && "The timeline time should be valid."); + return (timeline_time.value() - start_time_.value()) * playback_rate_; + } } -} +} // namespace endor diff --git a/src/client/animation/animation.hpp b/src/client/animation/animation.hpp index 645770893..59731063f 100644 --- a/src/client/animation/animation.hpp +++ b/src/client/animation/animation.hpp @@ -6,131 +6,134 @@ #include #include "./animation_effect.hpp" -namespace dom +namespace endor { - class AnimationTimeline; - class Element; - - class Animation : public std::enable_shared_from_this + namespace dom { - public: - // Construct a new animation with specific type `T`. - template - static std::shared_ptr MakeAnimation(std::unique_ptr, std::shared_ptr); - - public: - Animation(std::unique_ptr, std::shared_ptr); - virtual ~Animation(); - - public: - virtual bool isCSSAnimation() const - { - return false; - } - virtual bool isCSSTransition() const - { - return false; - } - virtual std::shared_ptr owningElement() const - { - return nullptr; - } - virtual void clearOwningElement() - { - } - inline bool isOwned() const - { - return owningElement() != nullptr; - } - - // Returns whether the animation is finished. - bool update(TimingUpdateReason); - - void cancel(); - void commitStyles(); - void finish(); - void pause(); - void persist(); - void play(); - void reverse(); - void updatePlaybackRate(float); - - bool updateFrameToStyle(client_cssom::ComputedStyle &); - bool updatePropertyToStyle(client_cssom::ComputedStyle &, const std::string &property); + class AnimationTimeline; + class Element; - public: - std::optional currentTime() const; - void setCurrentTime(float time); - - std::optional startTime() const + class Animation : public std::enable_shared_from_this { - return start_time_; - } - - const AnimationEffect &effect() const; - AnimationEffect &effect(); - - std::string id() const - { - return id_; - } - bool ready() const - { - return ready_; - } - - enum PlayState - { - kPlayStateIdle, - kPlayStateRunning, - kPlayStatePaused, - kPlayStateFinished, + public: + // Construct a new animation with specific type `T`. + template + static std::shared_ptr MakeAnimation(std::unique_ptr, std::shared_ptr); + + public: + Animation(std::unique_ptr, std::shared_ptr); + virtual ~Animation(); + + public: + virtual bool isCSSAnimation() const + { + return false; + } + virtual bool isCSSTransition() const + { + return false; + } + virtual std::shared_ptr owningElement() const + { + return nullptr; + } + virtual void clearOwningElement() + { + } + inline bool isOwned() const + { + return owningElement() != nullptr; + } + + // Returns whether the animation is finished. + bool update(TimingUpdateReason); + + void cancel(); + void commitStyles(); + void finish(); + void pause(); + void persist(); + void play(); + void reverse(); + void updatePlaybackRate(float); + + bool updateFrameToStyle(client_cssom::ComputedStyle &); + bool updatePropertyToStyle(client_cssom::ComputedStyle &, const std::string &property); + + public: + std::optional currentTime() const; + void setCurrentTime(float time); + + std::optional startTime() const + { + return start_time_; + } + + const AnimationEffect &effect() const; + AnimationEffect &effect(); + + std::string id() const + { + return id_; + } + bool ready() const + { + return ready_; + } + + enum PlayState + { + kPlayStateIdle, + kPlayStateRunning, + kPlayStatePaused, + kPlayStateFinished, + }; + bool pending() const + { + return pending_; + } + bool played() const + { + return play_state_ == kPlayStateRunning || + play_state_ == kPlayStatePaused; + } + + float playbackRate() const + { + return playback_rate_; + } + PlayState playState() const + { + return play_state_; + } + + enum ReplaceState + { + kReplaceStateActive, + kReplaceStatePersisted, + kReplaceStateRemoved, + }; + ReplaceState replaceState() const + { + return replace_state_; + } + + private: + std::optional calculateStartTime(float current_time) const; + std::optional calculateCurrentTime() const; + + private: + std::string id_; + std::optional start_time_; + std::optional hold_time_; + float playback_rate_ = 1.0f; + + std::unique_ptr effect_; + std::weak_ptr timeline_; + bool ready_ = false; + bool pending_ = false; + PlayState play_state_ = kPlayStateIdle; + ReplaceState replace_state_ = kReplaceStateActive; }; - bool pending() const - { - return pending_; - } - bool played() const - { - return play_state_ == kPlayStateRunning || - play_state_ == kPlayStatePaused; - } - - float playbackRate() const - { - return playback_rate_; - } - PlayState playState() const - { - return play_state_; - } - - enum ReplaceState - { - kReplaceStateActive, - kReplaceStatePersisted, - kReplaceStateRemoved, - }; - ReplaceState replaceState() const - { - return replace_state_; - } - - private: - std::optional calculateStartTime(float current_time) const; - std::optional calculateCurrentTime() const; - - private: - std::string id_; - std::optional start_time_; - std::optional hold_time_; - float playback_rate_ = 1.0f; - - std::unique_ptr effect_; - std::weak_ptr timeline_; - bool ready_ = false; - bool pending_ = false; - PlayState play_state_ = kPlayStateIdle; - ReplaceState replace_state_ = kReplaceStateActive; - }; -} + } +} // namespace endor diff --git a/src/client/animation/animation_effect.cpp b/src/client/animation/animation_effect.cpp index ee479fccc..bce406c41 100644 --- a/src/client/animation/animation_effect.cpp +++ b/src/client/animation/animation_effect.cpp @@ -1,68 +1,71 @@ #include "./animation_effect.hpp" -namespace dom +namespace endor { - using namespace client_cssom; - - AnimationEffect::AnimationEffect(const ComputedStyle::TransitionProperty &transition_property) - : timing_function_(nullptr) + namespace dom { - timing_.delay = transition_property.delay.seconds().value; - timing_.duration = transition_property.duration.seconds().value; - timing_function_ = LinearTimingFunction::Create({}); - } + using namespace client_cssom; - AnimationEffect::ComputedTiming AnimationEffect::getComputedTiming() const - { - ComputedTiming computed_timing(timing_); - // Fill in the computed timing values based on the effect's timing - // TODO: Implement the logic to compute endTime, activeDuration, localTime, progress, and currentIteration - return computed_timing; - } + AnimationEffect::AnimationEffect(const ComputedStyle::TransitionProperty &transition_property) + : timing_function_(nullptr) + { + timing_.delay = transition_property.delay.seconds().value; + timing_.duration = transition_property.duration.seconds().value; + timing_function_ = LinearTimingFunction::Create({}); + } - AnimationEffect::Timing AnimationEffect::getTiming() const - { - return timing_; - } + AnimationEffect::ComputedTiming AnimationEffect::getComputedTiming() const + { + ComputedTiming computed_timing(timing_); + // Fill in the computed timing values based on the effect's timing + // TODO: Implement the logic to compute endTime, activeDuration, localTime, progress, and currentIteration + return computed_timing; + } - void AnimationEffect::updateTimingDelay(float delay) - { - timing_.delay = delay; - } + AnimationEffect::Timing AnimationEffect::getTiming() const + { + return timing_; + } - void AnimationEffect::updateTimingDuration(float duration) - { - timing_.duration = duration; - } + void AnimationEffect::updateTimingDelay(float delay) + { + timing_.delay = delay; + } - void AnimationEffect::updateTimingEndDelay(float end_delay) - { - timing_.endDelay = end_delay; - } + void AnimationEffect::updateTimingDuration(float duration) + { + timing_.duration = duration; + } - void AnimationEffect::updateTimingIterations(size_t iterations) - { - timing_.iterations = iterations; - } + void AnimationEffect::updateTimingEndDelay(float end_delay) + { + timing_.endDelay = end_delay; + } - void AnimationEffect::updateTimingIterationStart(float iteration_start) - { - timing_.iterationStart = iteration_start; - } + void AnimationEffect::updateTimingIterations(size_t iterations) + { + timing_.iterations = iterations; + } - void AnimationEffect::updateTimingDirection(Direction direction) - { - timing_.direction = direction; - } + void AnimationEffect::updateTimingIterationStart(float iteration_start) + { + timing_.iterationStart = iteration_start; + } - void AnimationEffect::updateTimingFill(FillMode fill) - { - timing_.fill = fill; - } + void AnimationEffect::updateTimingDirection(Direction direction) + { + timing_.direction = direction; + } - void AnimationEffect::updateTimingEasing(std::unique_ptr easing) - { - timing_function_ = move(easing); - timing_.easing = timing_function_.get(); + void AnimationEffect::updateTimingFill(FillMode fill) + { + timing_.fill = fill; + } + + void AnimationEffect::updateTimingEasing(std::unique_ptr easing) + { + timing_function_ = move(easing); + timing_.easing = timing_function_.get(); + } } -} +} // namespace endor diff --git a/src/client/animation/animation_effect.hpp b/src/client/animation/animation_effect.hpp index 8971da8d0..ff032bcb1 100644 --- a/src/client/animation/animation_effect.hpp +++ b/src/client/animation/animation_effect.hpp @@ -5,140 +5,143 @@ #include #include "./timing_function.hpp" -namespace dom +namespace endor { - enum TimingUpdateReason + namespace dom { - kTimingUpdateOnDemand, - kTimingUpdateForAnimationFrame - }; - - class AnimationEffect - { - public: - enum Direction + enum TimingUpdateReason { - kDirectionNormal, - kDirectionReverse, - kDirectionAlternate, - kDirectionAlternateReverse, - }; - enum FillMode - { - kFillModeNone, - kFillModeForwards, - kFillModeBackwards, - kFillModeBoth, - kFillModeAuto, + kTimingUpdateOnDemand, + kTimingUpdateForAnimationFrame }; - struct Timing + class AnimationEffect { - float delay; - float duration; - float endDelay; - - size_t iterations; - float iterationStart; - - Direction direction; - FillMode fill; - - TimingFunction *easing = nullptr; - - Timing() - : delay(0.0f) - , duration(0.0f) - , endDelay(0.0f) - , iterations(1) - , iterationStart(0.0f) - , direction(kDirectionNormal) - , fill(kFillModeNone) + public: + enum Direction { - } - Timing(const Timing &other) - : delay(other.delay) - , duration(other.duration) - , endDelay(other.endDelay) - , iterations(other.iterations) - , iterationStart(other.iterationStart) - , direction(other.direction) - , fill(other.fill) + kDirectionNormal, + kDirectionReverse, + kDirectionAlternate, + kDirectionAlternateReverse, + }; + enum FillMode { - } - }; - - struct ComputedTiming : public Timing - { - float endTime; - float activeDuration; - float localTime; - - std::optional progress; - std::optional currentIteration; - - ComputedTiming() - : Timing() - , endTime(0.0f) - , activeDuration(0.0f) - , localTime(0.0f) - , progress(std::nullopt) - , currentIteration(std::nullopt) + kFillModeNone, + kFillModeForwards, + kFillModeBackwards, + kFillModeBoth, + kFillModeAuto, + }; + + struct Timing { - } - ComputedTiming(const Timing &timing) - : Timing(timing) - , endTime(0.0f) - , activeDuration(0.0f) - , localTime(0.0f) - , progress(std::nullopt) - , currentIteration(std::nullopt) + float delay; + float duration; + float endDelay; + + size_t iterations; + float iterationStart; + + Direction direction; + FillMode fill; + + TimingFunction *easing = nullptr; + + Timing() + : delay(0.0f) + , duration(0.0f) + , endDelay(0.0f) + , iterations(1) + , iterationStart(0.0f) + , direction(kDirectionNormal) + , fill(kFillModeNone) + { + } + Timing(const Timing &other) + : delay(other.delay) + , duration(other.duration) + , endDelay(other.endDelay) + , iterations(other.iterations) + , iterationStart(other.iterationStart) + , direction(other.direction) + , fill(other.fill) + { + } + }; + + struct ComputedTiming : public Timing { - } - ComputedTiming(const ComputedTiming &other) - : Timing(other) - , endTime(other.endTime) - , activeDuration(other.activeDuration) - , localTime(other.localTime) - , progress(other.progress) - , currentIteration(other.currentIteration) + float endTime; + float activeDuration; + float localTime; + + std::optional progress; + std::optional currentIteration; + + ComputedTiming() + : Timing() + , endTime(0.0f) + , activeDuration(0.0f) + , localTime(0.0f) + , progress(std::nullopt) + , currentIteration(std::nullopt) + { + } + ComputedTiming(const Timing &timing) + : Timing(timing) + , endTime(0.0f) + , activeDuration(0.0f) + , localTime(0.0f) + , progress(std::nullopt) + , currentIteration(std::nullopt) + { + } + ComputedTiming(const ComputedTiming &other) + : Timing(other) + , endTime(other.endTime) + , activeDuration(other.activeDuration) + , localTime(other.localTime) + , progress(other.progress) + , currentIteration(other.currentIteration) + { + } + }; + + public: + AnimationEffect() = default; + // Construct an `AnimationEffect` from a transition property. + AnimationEffect(const client_cssom::ComputedStyle::TransitionProperty &); + + ComputedTiming getComputedTiming() const; + Timing getTiming() const; + + void updateTimingDelay(float delay); + void updateTimingDuration(float duration); + void updateTimingEndDelay(float end_delay); + void updateTimingIterations(size_t iterations); + void updateTimingIterationStart(float iteration_start); + void updateTimingDirection(Direction direction); + void updateTimingFill(FillMode fill); + void updateTimingEasing(std::unique_ptr easing); + + friend std::ostream &operator<<(std::ostream &os, const AnimationEffect &effect) { + os << "AnimationEffect(" << endl + << " timing=" << effect.timing_.delay << endl + << " duration=" << effect.timing_.duration << endl + << " endDelay=" << effect.timing_.endDelay << endl + << " iterations=" << effect.timing_.iterations << endl + << " iterationStart=" << effect.timing_.iterationStart << endl + << " direction=" << effect.timing_.direction << endl + << " fill=" << effect.timing_.fill << endl + << ")"; + return os; } - }; - - public: - AnimationEffect() = default; - // Construct an `AnimationEffect` from a transition property. - AnimationEffect(const client_cssom::ComputedStyle::TransitionProperty &); - ComputedTiming getComputedTiming() const; - Timing getTiming() const; - - void updateTimingDelay(float delay); - void updateTimingDuration(float duration); - void updateTimingEndDelay(float end_delay); - void updateTimingIterations(size_t iterations); - void updateTimingIterationStart(float iteration_start); - void updateTimingDirection(Direction direction); - void updateTimingFill(FillMode fill); - void updateTimingEasing(std::unique_ptr easing); - - friend std::ostream &operator<<(std::ostream &os, const AnimationEffect &effect) - { - os << "AnimationEffect(" << endl - << " timing=" << effect.timing_.delay << endl - << " duration=" << effect.timing_.duration << endl - << " endDelay=" << effect.timing_.endDelay << endl - << " iterations=" << effect.timing_.iterations << endl - << " iterationStart=" << effect.timing_.iterationStart << endl - << " direction=" << effect.timing_.direction << endl - << " fill=" << effect.timing_.fill << endl - << ")"; - return os; - } - - private: - Timing timing_; - std::unique_ptr timing_function_ = nullptr; - }; -} + private: + Timing timing_; + std::unique_ptr timing_function_ = nullptr; + }; + } +} // namespace endor diff --git a/src/client/animation/animation_timeline.cpp b/src/client/animation/animation_timeline.cpp index 0977c52da..15e9b2a3e 100644 --- a/src/client/animation/animation_timeline.cpp +++ b/src/client/animation/animation_timeline.cpp @@ -1,68 +1,71 @@ #include "./animation_timeline.hpp" -namespace dom +namespace endor { - using namespace std; - - void AnimationTimeline::animationAttached(shared_ptr animation) + namespace dom { - if (TR_UNLIKELY(animation == nullptr)) - return; + using namespace std; - // TODO(yorkie): Check if the animation is already attached. - animations_.push_back(animation); - } + void AnimationTimeline::animationAttached(shared_ptr animation) + { + if (TR_UNLIKELY(animation == nullptr)) + return; - void AnimationTimeline::animationDetached(shared_ptr target_animation) - { - if (TR_UNLIKELY(target_animation == nullptr)) - return; + // TODO(yorkie): Check if the animation is already attached. + animations_.push_back(animation); + } - for (auto it = animations_.begin(); it != animations_.end(); ++it) + void AnimationTimeline::animationDetached(shared_ptr target_animation) { - auto animation_ref = *it; - if (TR_UNLIKELY(animation_ref.expired())) - continue; + if (TR_UNLIKELY(target_animation == nullptr)) + return; - auto animation = animation_ref.lock(); - if (animation == target_animation) + for (auto it = animations_.begin(); it != animations_.end(); ++it) { - animations_.erase(it); - return; + auto animation_ref = *it; + if (TR_UNLIKELY(animation_ref.expired())) + continue; + + auto animation = animation_ref.lock(); + if (animation == target_animation) + { + animations_.erase(it); + return; + } } } - } - void AnimationTimeline::detachInvalidAnimations() - { - for (auto it = animations_.begin(); it != animations_.end();) + void AnimationTimeline::detachInvalidAnimations() { - auto animation = *it; - if (animation.expired()) - it = animations_.erase(it); - else - it++; + for (auto it = animations_.begin(); it != animations_.end();) + { + auto animation = *it; + if (animation.expired()) + it = animations_.erase(it); + else + it++; + } } - } - - // This implements https://www.w3.org/TR/web-animations-1/#update-animations-and-send-events - void AnimationTimeline::serviceAnimations(TimingUpdateReason reason) - { - updateCurrentTime(); - vector> animations; - for (auto &animation : animations_) + // This implements https://www.w3.org/TR/web-animations-1/#update-animations-and-send-events + void AnimationTimeline::serviceAnimations(TimingUpdateReason reason) { - if (TR_LIKELY(!animation.expired())) - animations.push_back(animation.lock()); - } + updateCurrentTime(); - for (const auto &animation : animations) - { - if (animation->update(reason)) + vector> animations; + for (auto &animation : animations_) + { + if (TR_LIKELY(!animation.expired())) + animations.push_back(animation.lock()); + } + + for (const auto &animation : animations) { - // TODO(yorkie): Dispatch events for the animation. + if (animation->update(reason)) + { + // TODO(yorkie): Dispatch events for the animation. + } } } } -} +} // namespace endor diff --git a/src/client/animation/animation_timeline.hpp b/src/client/animation/animation_timeline.hpp index d48cb28e2..f267a9737 100644 --- a/src/client/animation/animation_timeline.hpp +++ b/src/client/animation/animation_timeline.hpp @@ -7,64 +7,67 @@ #include "./animation.hpp" -namespace dom +namespace endor { - class AnimationTimeline + namespace dom { - public: - AnimationTimeline() = default; - virtual ~AnimationTimeline() = default; - - virtual bool isDocumentTimeline() const - { - return false; - } - virtual bool isScrollTimeline() const - { - return false; - } - virtual bool isViewTimeline() const - { - return false; - } - virtual bool isActive() const - { - return current_time_ != std::nullopt; - } - virtual bool isResolved() const + class AnimationTimeline { - return true; - } + public: + AnimationTimeline() = default; + virtual ~AnimationTimeline() = default; - std::optional currentTime() const - { - if (!isActive()) - return std::nullopt; - return std::chrono::duration_cast(current_time_->time_since_epoch()).count(); - } + virtual bool isDocumentTimeline() const + { + return false; + } + virtual bool isScrollTimeline() const + { + return false; + } + virtual bool isViewTimeline() const + { + return false; + } + virtual bool isActive() const + { + return current_time_ != std::nullopt; + } + virtual bool isResolved() const + { + return true; + } - virtual void animationAttached(std::shared_ptr); - virtual void animationDetached(std::shared_ptr); - virtual void detachInvalidAnimations(); + std::optional currentTime() const + { + if (!isActive()) + return std::nullopt; + return std::chrono::duration_cast(current_time_->time_since_epoch()).count(); + } - virtual void serviceAnimations(TimingUpdateReason); - virtual void scheduleNextService() = 0; + virtual void animationAttached(std::shared_ptr); + virtual void animationDetached(std::shared_ptr); + virtual void detachInvalidAnimations(); - virtual bool hasAnimations() const - { - return !animations_.empty(); - } + virtual void serviceAnimations(TimingUpdateReason); + virtual void scheduleNextService() = 0; - protected: - void updateCurrentTime() - { - current_time_ = std::chrono::high_resolution_clock::now(); - } + virtual bool hasAnimations() const + { + return !animations_.empty(); + } + + protected: + void updateCurrentTime() + { + current_time_ = std::chrono::high_resolution_clock::now(); + } - protected: - std::optional> current_time_; + protected: + std::optional> current_time_; - private: - std::vector> animations_; - }; -} + private: + std::vector> animations_; + }; + } +} // namespace endor diff --git a/src/client/animation/css/css_animation.cpp b/src/client/animation/css/css_animation.cpp index f56fe1487..94969df3b 100644 --- a/src/client/animation/css/css_animation.cpp +++ b/src/client/animation/css/css_animation.cpp @@ -1,18 +1,21 @@ #include "./css_animation.hpp" -namespace dom +namespace endor { - using namespace std; - - void CSSAnimation::clearOwningElement() + namespace dom { - owning_element_.reset(); - } + using namespace std; - shared_ptr CSSAnimation::owningElement() const - { - if (owning_element_.expired()) - return nullptr; - return owning_element_.lock(); + void CSSAnimation::clearOwningElement() + { + owning_element_.reset(); + } + + shared_ptr CSSAnimation::owningElement() const + { + if (owning_element_.expired()) + return nullptr; + return owning_element_.lock(); + } } -} +} // namespace endor diff --git a/src/client/animation/css/css_animation.hpp b/src/client/animation/css/css_animation.hpp index 1be75d3f0..979eb4757 100644 --- a/src/client/animation/css/css_animation.hpp +++ b/src/client/animation/css/css_animation.hpp @@ -2,21 +2,24 @@ #include -namespace dom +namespace endor { - class Element; - class CSSAnimation : public Animation + namespace dom { - public: - bool isCSSAnimation() const override + class Element; + class CSSAnimation : public Animation { - return true; - } + public: + bool isCSSAnimation() const override + { + return true; + } - void clearOwningElement() override; - std::shared_ptr owningElement() const override; + void clearOwningElement() override; + std::shared_ptr owningElement() const override; - private: - std::weak_ptr owning_element_; - }; -} \ No newline at end of file + private: + std::weak_ptr owning_element_; + }; + } +} // namespace endor \ No newline at end of file diff --git a/src/client/animation/css/css_animations.cpp b/src/client/animation/css/css_animations.cpp index 44501d68c..18353d7f3 100644 --- a/src/client/animation/css/css_animations.cpp +++ b/src/client/animation/css/css_animations.cpp @@ -4,63 +4,66 @@ #include "./css_animations.hpp" -namespace dom +namespace endor { - using namespace std; - - CSSAnimations::CSSAnimations() - { - } - - void CSSAnimations::clearTransitions() + namespace dom { - transitions_.clear(); - } - - size_t CSSAnimations::setTransitions(const client_cssom::ComputedStyle &style, - shared_ptr timeline) - { - clearTransitions(); + using namespace std; - int len = style.getTransitionPropertiesCount(); - for (size_t index = 0; index < len; ++index) + CSSAnimations::CSSAnimations() { - auto transition_property = style.getTransitionProperty(index); - if (TR_UNLIKELY(!transition_property.has_value())) - continue; + } - auto property = transition_property->property; - auto effect = make_unique(*transition_property); - auto animation = Animation::MakeAnimation(move(effect), timeline); - auto animatables = AnimatableProperties::FromTransitionProperty(property); - auto transition_animation = make_shared(animation, animatables); - transitions_.emplace(property.toCss(), transition_animation); + void CSSAnimations::clearTransitions() + { + transitions_.clear(); } - return transitions_.size(); - } + size_t CSSAnimations::setTransitions(const client_cssom::ComputedStyle &style, + shared_ptr timeline) + { + clearTransitions(); - bool CSSAnimations::updateFrameToStyle(client_cssom::ComputedStyle &style) - { - bool updated = false; + int len = style.getTransitionPropertiesCount(); + for (size_t index = 0; index < len; ++index) + { + auto transition_property = style.getTransitionProperty(index); + if (TR_UNLIKELY(!transition_property.has_value())) + continue; - // Update the CSS animations. - for (const auto &animation : running_animations_) - { - if (animation == nullptr) - continue; - animation->updateFrameToStyle(style); + auto property = transition_property->property; + auto effect = make_unique(*transition_property); + auto animation = Animation::MakeAnimation(move(effect), timeline); + auto animatables = AnimatableProperties::FromTransitionProperty(property); + auto transition_animation = make_shared(animation, animatables); + transitions_.emplace(property.toCss(), transition_animation); + } + + return transitions_.size(); } - // Update the CSS transitions. - for (auto &transition : transitions_) + bool CSSAnimations::updateFrameToStyle(client_cssom::ComputedStyle &style) { - auto running_transition = transition.second; - if (TR_UNLIKELY(running_transition == nullptr)) - continue; - if (running_transition->updateFrameToStyle(style) > 0) - updated = true; + bool updated = false; + + // Update the CSS animations. + for (const auto &animation : running_animations_) + { + if (animation == nullptr) + continue; + animation->updateFrameToStyle(style); + } + + // Update the CSS transitions. + for (auto &transition : transitions_) + { + auto running_transition = transition.second; + if (TR_UNLIKELY(running_transition == nullptr)) + continue; + if (running_transition->updateFrameToStyle(style) > 0) + updated = true; + } + return updated; } - return updated; } -} +} // namespace endor diff --git a/src/client/animation/css/css_animations.hpp b/src/client/animation/css/css_animations.hpp index 3bd6a10ec..a300076bd 100644 --- a/src/client/animation/css/css_animations.hpp +++ b/src/client/animation/css/css_animations.hpp @@ -11,100 +11,103 @@ #include #include -namespace dom +namespace endor { - // A map of animatable properties. - class AnimatableProperties : public std::vector + namespace dom { - using std::vector::vector; - - public: - static AnimatableProperties FromTransitionProperty( - const client_cssom::values::computed::TransitionProperty &property) + // A map of animatable properties. + class AnimatableProperties : public std::vector { - return AnimatableProperties{{property.toCss()}}; - } - }; + using std::vector::vector; - class CSSAnimations final - { - public: - // Represents animations specified via CSS `animation-*` properties. - class RunningAnimation - { public: - RunningAnimation(std::shared_ptr animation, - std::string name) - : animation(animation) - , name(name) + static AnimatableProperties FromTransitionProperty( + const client_cssom::values::computed::TransitionProperty &property) { + return AnimatableProperties{{property.toCss()}}; } - - inline bool updateFrameToStyle(client_cssom::ComputedStyle &style) - { - return animation->updateFrameToStyle(style); - } - - public: - std::shared_ptr animation; - std::string name; }; - // Represents transitions specified via CSS `transition-*` properties. - class RunningTransition + class CSSAnimations final { public: - RunningTransition(std::shared_ptr animation, - const AnimatableProperties properties) - : animation(animation) - , properties(properties) + // Represents animations specified via CSS `animation-*` properties. + class RunningAnimation { - } + public: + RunningAnimation(std::shared_ptr animation, + std::string name) + : animation(animation) + , name(name) + { + } + + inline bool updateFrameToStyle(client_cssom::ComputedStyle &style) + { + return animation->updateFrameToStyle(style); + } + + public: + std::shared_ptr animation; + std::string name; + }; - inline size_t updateFrameToStyle(client_cssom::ComputedStyle &style) + // Represents transitions specified via CSS `transition-*` properties. + class RunningTransition { - size_t updated_count = 0; - for (const auto &property : properties) + public: + RunningTransition(std::shared_ptr animation, + const AnimatableProperties properties) + : animation(animation) + , properties(properties) { - if (animation->updatePropertyToStyle(style, property)) - updated_count++; } - return updated_count; - } - public: - std::shared_ptr animation; - const AnimatableProperties properties; - }; + inline size_t updateFrameToStyle(client_cssom::ComputedStyle &style) + { + size_t updated_count = 0; + for (const auto &property : properties) + { + if (animation->updatePropertyToStyle(style, property)) + updated_count++; + } + return updated_count; + } - public: - CSSAnimations(); - ~CSSAnimations() = default; + public: + std::shared_ptr animation; + const AnimatableProperties properties; + }; - public: - bool isEmpty() const - { - return running_animations_.empty() && - transitions_.empty(); - } + public: + CSSAnimations(); + ~CSSAnimations() = default; - const std::vector> &runningAnimations() const - { - return running_animations_; - } - const std::optional transition(const std::string &name) const - { - auto it = transitions_.find(name); - if (it != transitions_.end()) - return *it->second; - return std::nullopt; - } - void clearTransitions(); - size_t setTransitions(const client_cssom::ComputedStyle &, std::shared_ptr); - bool updateFrameToStyle(client_cssom::ComputedStyle &); + public: + bool isEmpty() const + { + return running_animations_.empty() && + transitions_.empty(); + } + + const std::vector> &runningAnimations() const + { + return running_animations_; + } + const std::optional transition(const std::string &name) const + { + auto it = transitions_.find(name); + if (it != transitions_.end()) + return *it->second; + return std::nullopt; + } + void clearTransitions(); + size_t setTransitions(const client_cssom::ComputedStyle &, std::shared_ptr); + bool updateFrameToStyle(client_cssom::ComputedStyle &); - private: - std::vector> running_animations_; - std::unordered_map> transitions_; - }; -} + private: + std::vector> running_animations_; + std::unordered_map> transitions_; + }; + } +} // namespace endor diff --git a/src/client/animation/css/css_transition.cpp b/src/client/animation/css/css_transition.cpp index 8baab05d9..e05d50793 100644 --- a/src/client/animation/css/css_transition.cpp +++ b/src/client/animation/css/css_transition.cpp @@ -1,18 +1,21 @@ #include "./css_transition.hpp" -namespace dom +namespace endor { - using namespace std; - - void CSSTransition::clearOwningElement() + namespace dom { - owning_element_.reset(); - } + using namespace std; - shared_ptr CSSTransition::owningElement() const - { - if (owning_element_.expired()) - return nullptr; - return owning_element_.lock(); + void CSSTransition::clearOwningElement() + { + owning_element_.reset(); + } + + shared_ptr CSSTransition::owningElement() const + { + if (owning_element_.expired()) + return nullptr; + return owning_element_.lock(); + } } -} +} // namespace endor diff --git a/src/client/animation/css/css_transition.hpp b/src/client/animation/css/css_transition.hpp index eab7a6e91..7aaad96b8 100644 --- a/src/client/animation/css/css_transition.hpp +++ b/src/client/animation/css/css_transition.hpp @@ -3,25 +3,28 @@ #include #include -namespace dom +namespace endor { - class Element; - class CSSTransition : public Animation + namespace dom { - using Animation::Animation; - - public: - bool isCSSTransition() const override + class Element; + class CSSTransition : public Animation { - return true; - } + using Animation::Animation; + + public: + bool isCSSTransition() const override + { + return true; + } - void clearOwningElement() override; - // The owning element of a transition refers to the element or pseudo-element to which the `transition-property` - // property was applied that generated the animation. - std::shared_ptr owningElement() const override; + void clearOwningElement() override; + // The owning element of a transition refers to the element or pseudo-element to which the `transition-property` + // property was applied that generated the animation. + std::shared_ptr owningElement() const override; - private: - std::weak_ptr owning_element_; - }; -} + private: + std::weak_ptr owning_element_; + }; + } +} // namespace endor diff --git a/src/client/animation/document_timeline.cpp b/src/client/animation/document_timeline.cpp index d2b2c7873..f4ce1e45a 100644 --- a/src/client/animation/document_timeline.cpp +++ b/src/client/animation/document_timeline.cpp @@ -1,19 +1,22 @@ #include "./document_timeline.hpp" -namespace dom +namespace endor { - DocumentTimeline::DocumentTimeline() - : AnimationTimeline() + namespace dom { - } + DocumentTimeline::DocumentTimeline() + : AnimationTimeline() + { + } - DocumentTimeline::DocumentTimeline(const DocumentTimeline::DocumentTimelineInit init) - : AnimationTimeline() - { - } + DocumentTimeline::DocumentTimeline(const DocumentTimeline::DocumentTimelineInit init) + : AnimationTimeline() + { + } - void DocumentTimeline::scheduleNextService() - { - // TODO(yorkie): implement the animation scheduling. + void DocumentTimeline::scheduleNextService() + { + // TODO(yorkie): implement the animation scheduling. + } } -} +} // namespace endor diff --git a/src/client/animation/document_timeline.hpp b/src/client/animation/document_timeline.hpp index e6c03144b..5c5367e48 100644 --- a/src/client/animation/document_timeline.hpp +++ b/src/client/animation/document_timeline.hpp @@ -2,35 +2,38 @@ #include "./animation_timeline.hpp" -namespace dom +namespace endor { - class DocumentTimeline : public AnimationTimeline + namespace dom { - friend class RenderHTMLDocument; - - public: - struct DocumentTimelineInit + class DocumentTimeline : public AnimationTimeline { - long originTime = 0; - }; + friend class RenderHTMLDocument; - DocumentTimeline(); - DocumentTimeline(const DocumentTimelineInit init); + public: + struct DocumentTimelineInit + { + long originTime = 0; + }; - bool isDocumentTimeline() const override - { - return true; - } + DocumentTimeline(); + DocumentTimeline(const DocumentTimelineInit init); - void scheduleNextService() override; + bool isDocumentTimeline() const override + { + return true; + } - private: - void updateCurrentTime() - { - AnimationTimeline::updateCurrentTime(); - } + void scheduleNextService() override; + + private: + void updateCurrentTime() + { + AnimationTimeline::updateCurrentTime(); + } - private: - long origin_time_ = 0; - }; -} + private: + long origin_time_ = 0; + }; + } +} // namespace endor diff --git a/src/client/animation/element_animations.cpp b/src/client/animation/element_animations.cpp index b2e7fe9da..d27167367 100644 --- a/src/client/animation/element_animations.cpp +++ b/src/client/animation/element_animations.cpp @@ -1,24 +1,27 @@ #include #include "./element_animations.hpp" -namespace dom +namespace endor { - using namespace std; - - ElementAnimations::ElementAnimations(shared_ptr target_element) - : target_element_(target_element) + namespace dom { - assert(target_element != nullptr && "The target element must not be null."); - } + using namespace std; - bool ElementAnimations::updateFrameToStyle(client_cssom::ComputedStyle &style) - { - bool updated = css_animations_.updateFrameToStyle(style); - for (const auto &animation : animations_) + ElementAnimations::ElementAnimations(shared_ptr target_element) + : target_element_(target_element) + { + assert(target_element != nullptr && "The target element must not be null."); + } + + bool ElementAnimations::updateFrameToStyle(client_cssom::ComputedStyle &style) { - if (animation->updateFrameToStyle(style) == true && !updated) - updated = true; + bool updated = css_animations_.updateFrameToStyle(style); + for (const auto &animation : animations_) + { + if (animation->updateFrameToStyle(style) == true && !updated) + updated = true; + } + return updated; } - return updated; } -} +} // namespace endor diff --git a/src/client/animation/element_animations.hpp b/src/client/animation/element_animations.hpp index 28472bd4b..defafee48 100644 --- a/src/client/animation/element_animations.hpp +++ b/src/client/animation/element_animations.hpp @@ -7,40 +7,43 @@ #include "./animation.hpp" #include "./css/css_animations.hpp" -namespace dom +namespace endor { - class Element; - class ElementAnimations final + namespace dom { - public: - // Create a new `ElementAnimations` object for the given target element. - ElementAnimations(std::shared_ptr target_element); - ElementAnimations(const ElementAnimations &) = delete; - ElementAnimations &operator=(const ElementAnimations &) = delete; - ~ElementAnimations() = default; - - public: - bool isEmpty() const - { - return css_animations_.isEmpty() && animations_.empty(); - } - const CSSAnimations &cssAnimations() const + class Element; + class ElementAnimations final { - return css_animations_; - } - CSSAnimations &cssAnimations() - { - return css_animations_; - } + public: + // Create a new `ElementAnimations` object for the given target element. + ElementAnimations(std::shared_ptr target_element); + ElementAnimations(const ElementAnimations &) = delete; + ElementAnimations &operator=(const ElementAnimations &) = delete; + ~ElementAnimations() = default; + + public: + bool isEmpty() const + { + return css_animations_.isEmpty() && animations_.empty(); + } + const CSSAnimations &cssAnimations() const + { + return css_animations_; + } + CSSAnimations &cssAnimations() + { + return css_animations_; + } - // Update the element's animations to the given computed style, and returns whether the style is updated via - // animations. - bool updateFrameToStyle(client_cssom::ComputedStyle &); + // Update the element's animations to the given computed style, and returns whether the style is updated via + // animations. + bool updateFrameToStyle(client_cssom::ComputedStyle &); - private: - std::weak_ptr target_element_; - CSSAnimations css_animations_; - std::vector> animations_; - // TODO(yorkie): worklet animations? - }; -} + private: + std::weak_ptr target_element_; + CSSAnimations css_animations_; + std::vector> animations_; + // TODO(yorkie): worklet animations? + }; + } +} // namespace endor diff --git a/src/client/animation/keyframe.cpp b/src/client/animation/keyframe.cpp index 1e75d8921..ef0bb8651 100644 --- a/src/client/animation/keyframe.cpp +++ b/src/client/animation/keyframe.cpp @@ -1,11 +1,14 @@ #include "./keyframe.hpp" -namespace dom +namespace endor { - Keyframe::Keyframe() - : offset_(0.0) - , timing_function_(nullptr) - , composite_(KeyframeEffect::CompositeReplace) + namespace dom { + Keyframe::Keyframe() + : offset_(0.0) + , timing_function_(nullptr) + , composite_(KeyframeEffect::CompositeReplace) + { + } } -} +} // namespace endor diff --git a/src/client/animation/keyframe.hpp b/src/client/animation/keyframe.hpp index 9982b9831..0c751b00b 100644 --- a/src/client/animation/keyframe.hpp +++ b/src/client/animation/keyframe.hpp @@ -6,24 +6,27 @@ #include "./keyframe_effect.hpp" #include "./timing_function.hpp" -namespace dom +namespace endor { - class Keyframe + namespace dom { - public: - Keyframe(); - Keyframe(const Keyframe &other) - : properties_(other.properties_) - , offset_(other.offset_) - , timing_function_(other.timing_function_ ? other.timing_function_->clone() : nullptr) - , composite_(other.composite_) + class Keyframe { - } + public: + Keyframe(); + Keyframe(const Keyframe &other) + : properties_(other.properties_) + , offset_(other.offset_) + , timing_function_(other.timing_function_ ? other.timing_function_->clone() : nullptr) + , composite_(other.composite_) + { + } - private: - client_cssom::ComputedStyle properties_; - double offset_ = 0.0; - std::unique_ptr timing_function_; - KeyframeEffect::Composite composite_ = KeyframeEffect::CompositeReplace; - }; -} + private: + client_cssom::ComputedStyle properties_; + double offset_ = 0.0; + std::unique_ptr timing_function_; + KeyframeEffect::Composite composite_ = KeyframeEffect::CompositeReplace; + }; + } +} // namespace endor diff --git a/src/client/animation/keyframe_effect.cpp b/src/client/animation/keyframe_effect.cpp index 80bb1f70c..be29a4ab9 100644 --- a/src/client/animation/keyframe_effect.cpp +++ b/src/client/animation/keyframe_effect.cpp @@ -3,39 +3,42 @@ #include "./keyframe_effect.hpp" #include "./keyframes.hpp" -namespace dom +namespace endor { - using namespace std; - - KeyframeEffect::KeyframeEffect(shared_ptr target, - optional keyframes, - const KeyframeEffectOptions options) - : AnimationEffect() - , target_(target) - , keyframes_(make_unique(*keyframes)) - , composite_(options.composite.value_or(CompositeReplace)) - , iteration_composite_(options.iterationComposite.value_or(CompositeReplace)) - , pseudo_element_str_(options.pseudoElement) + namespace dom { - } + using namespace std; - Keyframes KeyframeEffect::getKeyframes() const - { - return *keyframes_; - } + KeyframeEffect::KeyframeEffect(shared_ptr target, + optional keyframes, + const KeyframeEffectOptions options) + : AnimationEffect() + , target_(target) + , keyframes_(make_unique(*keyframes)) + , composite_(options.composite.value_or(CompositeReplace)) + , iteration_composite_(options.iterationComposite.value_or(CompositeReplace)) + , pseudo_element_str_(options.pseudoElement) + { + } - void KeyframeEffect::setKeyframes(optional keyframes) - { - if (keyframes.has_value()) - keyframes_ = make_unique(*keyframes); - else - keyframes_->empty(); - } + Keyframes KeyframeEffect::getKeyframes() const + { + return *keyframes_; + } - shared_ptr KeyframeEffect::effectTarget() const - { - if (target_.expired()) - return nullptr; - return target_.lock(); + void KeyframeEffect::setKeyframes(optional keyframes) + { + if (keyframes.has_value()) + keyframes_ = make_unique(*keyframes); + else + keyframes_->empty(); + } + + shared_ptr KeyframeEffect::effectTarget() const + { + if (target_.expired()) + return nullptr; + return target_.lock(); + } } -} +} // namespace endor diff --git a/src/client/animation/keyframe_effect.hpp b/src/client/animation/keyframe_effect.hpp index 87876c5f7..fbcbf8e91 100644 --- a/src/client/animation/keyframe_effect.hpp +++ b/src/client/animation/keyframe_effect.hpp @@ -7,58 +7,61 @@ #include "./animation_effect.hpp" -namespace dom +namespace endor { - class Keyframes; - class KeyframeEffect : public AnimationEffect + namespace dom { - public: - enum Composite + class Keyframes; + class KeyframeEffect : public AnimationEffect { - CompositeReplace, - CompositeAdd, - CompositeAccumulate - }; + public: + enum Composite + { + CompositeReplace, + CompositeAdd, + CompositeAccumulate + }; - struct KeyframeEffectOptions - { - std::optional delay; - std::optional duration; - std::optional endDelay; + struct KeyframeEffectOptions + { + std::optional delay; + std::optional duration; + std::optional endDelay; - std::optional iterations; - std::optional iterationStart; + std::optional iterations; + std::optional iterationStart; - std::optional fill; - std::optional direction; - std::unique_ptr easing = nullptr; + std::optional fill; + std::optional direction; + std::unique_ptr easing = nullptr; - std::optional composite; - std::optional iterationComposite; - std::optional pseudoElement; - }; + std::optional composite; + std::optional iterationComposite; + std::optional pseudoElement; + }; - KeyframeEffect(std::shared_ptr target, std::optional, const KeyframeEffectOptions); + KeyframeEffect(std::shared_ptr target, std::optional, const KeyframeEffectOptions); - public: - Keyframes getKeyframes() const; - void setKeyframes(std::optional); + public: + Keyframes getKeyframes() const; + void setKeyframes(std::optional); - std::shared_ptr effectTarget() const; - Composite composite() const - { - return composite_; - } - Composite iterationComposite() const - { - return iteration_composite_; - } + std::shared_ptr effectTarget() const; + Composite composite() const + { + return composite_; + } + Composite iterationComposite() const + { + return iteration_composite_; + } - private: - Composite composite_ = CompositeReplace; - Composite iteration_composite_ = CompositeReplace; - std::weak_ptr target_; - std::optional pseudo_element_str_; - std::unique_ptr keyframes_; - }; -} + private: + Composite composite_ = CompositeReplace; + Composite iteration_composite_ = CompositeReplace; + std::weak_ptr target_; + std::optional pseudo_element_str_; + std::unique_ptr keyframes_; + }; + } +} // namespace endor diff --git a/src/client/animation/keyframes.hpp b/src/client/animation/keyframes.hpp index 7576e93f2..961ad9cc4 100644 --- a/src/client/animation/keyframes.hpp +++ b/src/client/animation/keyframes.hpp @@ -3,26 +3,29 @@ #include #include "./keyframe.hpp" -namespace dom +namespace endor { - class Keyframes : std::vector + namespace dom { - public: - static Keyframes Empty() + class Keyframes : std::vector { - return Keyframes(); - } + public: + static Keyframes Empty() + { + return Keyframes(); + } - public: - Keyframes() - : std::vector() - { - } + public: + Keyframes() + : std::vector() + { + } - public: - void empty() - { - std::vector::clear(); - } - }; -} + public: + void empty() + { + std::vector::clear(); + } + }; + } +} // namespace endor diff --git a/src/client/animation/timing_function.cpp b/src/client/animation/timing_function.cpp index fd37ce74f..73af3004f 100644 --- a/src/client/animation/timing_function.cpp +++ b/src/client/animation/timing_function.cpp @@ -1,35 +1,38 @@ #include "./timing_function.hpp" -namespace dom +namespace endor { - using namespace std; - - unique_ptr TimingFunction::clone() const + namespace dom { - if (isLinear()) + using namespace std; + + unique_ptr TimingFunction::clone() const { - return LinearTimingFunction::Clone(this); + if (isLinear()) + { + return LinearTimingFunction::Clone(this); + } + else + { + throw runtime_error("Unsupported TimingFunction type for clone"); + } } - else + + string LinearTimingFunction::toString() const { - throw runtime_error("Unsupported TimingFunction type for clone"); + return "linear"; } - } - - string LinearTimingFunction::toString() const - { - return "linear"; - } - double LinearTimingFunction::evaluate(double fraction) const - { - // Linear timing function is a simple linear interpolation - return fraction; - } + double LinearTimingFunction::evaluate(double fraction) const + { + // Linear timing function is a simple linear interpolation + return fraction; + } - void LinearTimingFunction::range(double *min, double *max) const - { - *min = 0.0; - *max = 1.0; + void LinearTimingFunction::range(double *min, double *max) const + { + *min = 0.0; + *max = 1.0; + } } -} +} // namespace endor diff --git a/src/client/animation/timing_function.hpp b/src/client/animation/timing_function.hpp index 05ed65a52..24a2307fe 100644 --- a/src/client/animation/timing_function.hpp +++ b/src/client/animation/timing_function.hpp @@ -5,100 +5,103 @@ #include #include -namespace dom +namespace endor { - enum class TimingFunctionType + namespace dom { - Linear, - CubicBezier, - Steps - }; + enum class TimingFunctionType + { + Linear, + CubicBezier, + Steps + }; - class TimingFunction - { - protected: - TimingFunction(TimingFunctionType type) - : type_(type) + class TimingFunction { - } + protected: + TimingFunction(TimingFunctionType type) + : type_(type) + { + } - public: - virtual ~TimingFunction() = default; - std::unique_ptr clone() const; + public: + virtual ~TimingFunction() = default; + std::unique_ptr clone() const; - public: - virtual bool isLinear() const - { - return false; - } - virtual bool isCubicBezier() const - { - return false; - } - virtual bool isSteps() const - { - return false; - } + public: + virtual bool isLinear() const + { + return false; + } + virtual bool isCubicBezier() const + { + return false; + } + virtual bool isSteps() const + { + return false; + } - virtual std::string toString() const = 0; - virtual double evaluate(double fraction) const = 0; - virtual void range(double *min, double *max) const = 0; + virtual std::string toString() const = 0; + virtual double evaluate(double fraction) const = 0; + virtual void range(double *min, double *max) const = 0; - protected: - TimingFunctionType type_; - }; + protected: + TimingFunctionType type_; + }; - class LinearTimingFunction : public TimingFunction - { - public: - static std::unique_ptr Clone(const TimingFunction *easing) + class LinearTimingFunction : public TimingFunction { - auto linear_function = static_cast(easing); - return std::unique_ptr(new LinearTimingFunction(*linear_function)); - } - static std::unique_ptr Create(std::vector points) - { - return std::unique_ptr(new LinearTimingFunction(std::move(points))); - } + public: + static std::unique_ptr Clone(const TimingFunction *easing) + { + auto linear_function = static_cast(easing); + return std::unique_ptr(new LinearTimingFunction(*linear_function)); + } + static std::unique_ptr Create(std::vector points) + { + return std::unique_ptr(new LinearTimingFunction(std::move(points))); + } - private: - LinearTimingFunction() - : TimingFunction(TimingFunctionType::Linear) - { - } - explicit LinearTimingFunction(std::vector points) - : TimingFunction(TimingFunctionType::Linear) - , points_(std::move(points)) - { - } - LinearTimingFunction(const LinearTimingFunction &other) - : TimingFunction(other.type_) - , points_(other.points_) - { - } + private: + LinearTimingFunction() + : TimingFunction(TimingFunctionType::Linear) + { + } + explicit LinearTimingFunction(std::vector points) + : TimingFunction(TimingFunctionType::Linear) + , points_(std::move(points)) + { + } + LinearTimingFunction(const LinearTimingFunction &other) + : TimingFunction(other.type_) + , points_(other.points_) + { + } - public: - ~LinearTimingFunction() override = default; + public: + ~LinearTimingFunction() override = default; - public: - bool isLinear() const override final - { - return true; - } + public: + bool isLinear() const override final + { + return true; + } - std::string toString() const override; - double evaluate(double fraction) const override; - void range(double *min, double *max) const override; + std::string toString() const override; + double evaluate(double fraction) const override; + void range(double *min, double *max) const override; - private: - std::vector points_; - }; + private: + std::vector points_; + }; - // class CubicBezierTimingFunction : public TimingFunction - // { - // }; + // class CubicBezierTimingFunction : public TimingFunction + // { + // }; - // class StepsTimingFunction : public TimingFunction - // { - // }; -} + // class StepsTimingFunction : public TimingFunction + // { + // }; + } +} // namespace endor diff --git a/src/client/browser/location.hpp b/src/client/browser/location.hpp index 381114e07..5a9cc6954 100644 --- a/src/client/browser/location.hpp +++ b/src/client/browser/location.hpp @@ -5,58 +5,61 @@ #include #include -namespace browser +namespace endor { - /** + namespace browser + { + /** * @class Location * The `Location` class represents the URL of the current document and provides methods to interact with it. */ - class Location final : public scripting_base::JSObjectHolder - { - public: - /** + class Location final : public scripting_base::JSObjectHolder + { + public: + /** * Default constructor for `Location`. */ - Location() = default; + Location() = default; - /** + /** * Constructs a `Location` object from a URL string. * * @param input The URL string to parse. * @throws std::invalid_argument if the URL is invalid. */ - explicit Location(const std::string &input) - { - try - { - auto urlObject = crates::Url::Parse(input); - host = urlObject.host; - hostname = urlObject.hostname; - href = urlObject.href; - origin = urlObject.origin; - pathname = urlObject.pathname; - port = urlObject.port; - protocol = urlObject.protocol; - search = urlObject.search; - hash = urlObject.hash; - } - catch (const std::exception &e) + explicit Location(const std::string &input) { - throw std::invalid_argument("Invalid URL: " + input); + try + { + auto urlObject = crates::Url::Parse(input); + host = urlObject.host; + hostname = urlObject.hostname; + href = urlObject.href; + origin = urlObject.origin; + pathname = urlObject.pathname; + port = urlObject.port; + protocol = urlObject.protocol; + search = urlObject.search; + hash = urlObject.hash; + } + catch (const std::exception &e) + { + throw std::invalid_argument("Invalid URL: " + input); + } } - } - public: - std::string host; // The hostname and port of the URL - std::string hostname; // The hostname of the URL - std::string href; // The entire URL - std::string origin; // The origin of the URL - std::string pathname; // The path of the URL - std::string password; // The password specified in the URL - int port = 0; // The port number of the URL - std::string protocol; // The protocol of the URL - std::string search; // The query string of the URL - std::string username; // The username specified in the URL - std::string hash; // The fragment identifier of the URL - }; -} // namespace browser + public: + std::string host; // The hostname and port of the URL + std::string hostname; // The hostname of the URL + std::string href; // The entire URL + std::string origin; // The origin of the URL + std::string pathname; // The path of the URL + std::string password; // The password specified in the URL + int port = 0; // The port number of the URL + std::string protocol; // The protocol of the URL + std::string search; // The query string of the URL + std::string username; // The username specified in the URL + std::string hash; // The fragment identifier of the URL + }; + } // namespace browser +} // namespace endor diff --git a/src/client/browser/navigator.cpp b/src/client/browser/navigator.cpp index 92860e722..4019863e5 100644 --- a/src/client/browser/navigator.cpp +++ b/src/client/browser/navigator.cpp @@ -4,162 +4,165 @@ using namespace std; -namespace browser +namespace endor { - Navigator::Navigator(TrClientContextPerProcess *client_context) - : user_agent_("Mozilla/5.0 (JSAR-Runtime) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") - , platform_("JSAR") - , vendor_("M-CreativeLab") - , language_("en-US") - , hardware_concurrency_(static_cast(thread::hardware_concurrency())) - , online_(true) - , cookie_enabled_(true) - , do_not_track_(false) - , gl_context_(client_context->createHostWebGLContext()) - , xr_system_(nullptr) - { - // If the scripting event loop is already ready, initialize the XRSystem immediately - if (client_context->isScriptingEventLoopReady()) + namespace browser + { + Navigator::Navigator(TrClientContextPerProcess *client_context) + : user_agent_("Mozilla/5.0 (JSAR-Runtime) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") + , platform_("JSAR") + , vendor_("M-CreativeLab") + , language_("en-US") + , hardware_concurrency_(static_cast(thread::hardware_concurrency())) + , online_(true) + , cookie_enabled_(true) + , do_not_track_(false) + , gl_context_(client_context->createHostWebGLContext()) + , xr_system_(nullptr) { - initXRSystem(client_context); + // If the scripting event loop is already ready, initialize the XRSystem immediately + if (client_context->isScriptingEventLoopReady()) + { + initXRSystem(client_context); + } + else + { + client_context->addEventListener( + TrClientContextEventType::ScriptingEventLoopReady, + [this](auto _type, auto _event) + { + initXRSystem(TrClientContextPerProcess::Get()); + }); + } } - else + + string Navigator::userAgent() const { - client_context->addEventListener( - TrClientContextEventType::ScriptingEventLoopReady, - [this](auto _type, auto _event) - { - initXRSystem(TrClientContextPerProcess::Get()); - }); + return user_agent_; } - } - string Navigator::userAgent() const - { - return user_agent_; - } - - string Navigator::platform() const - { - return platform_; - } + string Navigator::platform() const + { + return platform_; + } - string Navigator::vendor() const - { - return vendor_; - } + string Navigator::vendor() const + { + return vendor_; + } - string Navigator::vendorSub() const - { - return ""; - } + string Navigator::vendorSub() const + { + return ""; + } - string Navigator::product() const - { - return "JSAR"; - } + string Navigator::product() const + { + return "JSAR"; + } - string Navigator::productSub() const - { - return "20030107"; - } + string Navigator::productSub() const + { + return "20030107"; + } - bool Navigator::isOnline() const - { - return online_; - } + bool Navigator::isOnline() const + { + return online_; + } - bool Navigator::isCookieEnabled() const - { - return cookie_enabled_; - } + bool Navigator::isCookieEnabled() const + { + return cookie_enabled_; + } - bool Navigator::isDoNotTrack() const - { - return do_not_track_; - } + bool Navigator::isDoNotTrack() const + { + return do_not_track_; + } - int Navigator::hardwareConcurrency() const - { - return hardware_concurrency_; - } + int Navigator::hardwareConcurrency() const + { + return hardware_concurrency_; + } - long long Navigator::maxTouchPoints() const - { - return 1; // Default single touch support - } + long long Navigator::maxTouchPoints() const + { + return 1; // Default single touch support + } - string Navigator::language() const - { - return language_; - } + string Navigator::language() const + { + return language_; + } - vector Navigator::languages() const - { - return languages_; - } + vector Navigator::languages() const + { + return languages_; + } - bool Navigator::isJavaEnabled() const - { - return false; // Java not supported in JSAR Runtime - } + bool Navigator::isJavaEnabled() const + { + return false; // Java not supported in JSAR Runtime + } - string Navigator::colorScheme() const - { - return "light"; // Default color scheme - } + string Navigator::colorScheme() const + { + return "light"; // Default color scheme + } - bool Navigator::hasWebGL() const - { - return gl_context_ != nullptr; - } + bool Navigator::hasWebGL() const + { + return gl_context_ != nullptr; + } - bool Navigator::hasWebXR() const - { - return xr_system_ != nullptr; - } + bool Navigator::hasWebXR() const + { + return xr_system_ != nullptr; + } - bool Navigator::hasServiceWorker() const - { - return true; // Service Workers supported - } + bool Navigator::hasServiceWorker() const + { + return true; // Service Workers supported + } - bool Navigator::hasGeolocation() const - { - return true; // Geolocation API supported - } + bool Navigator::hasGeolocation() const + { + return true; // Geolocation API supported + } - bool Navigator::hasMediaDevices() const - { - return true; // MediaDevices API supported - } + bool Navigator::hasMediaDevices() const + { + return true; // MediaDevices API supported + } - bool Navigator::hasPermissions() const - { - return true; // Permissions API supported - } + bool Navigator::hasPermissions() const + { + return true; // Permissions API supported + } - bool Navigator::hasBattery() const - { - return true; // Battery API supported - } + bool Navigator::hasBattery() const + { + return true; // Battery API supported + } - bool Navigator::hasClipboard() const - { - return true; // Clipboard API supported - } + bool Navigator::hasClipboard() const + { + return true; // Clipboard API supported + } - long long Navigator::getStorageQuota() const - { - return 1024 * 1024 * 1024; // 1GB default storage quota - } + long long Navigator::getStorageQuota() const + { + return 1024 * 1024 * 1024; // 1GB default storage quota + } - void Navigator::initXRSystem(TrClientContextPerProcess *client_context) - { - auto xrDevice = client_context->getXRDeviceClient(); - uv_loop_t *loop = client_context->getScriptingEventLoop(); - assert(loop != nullptr && "Event loop must be ready when creating XRSystem."); + void Navigator::initXRSystem(TrClientContextPerProcess *client_context) + { + auto xrDevice = client_context->getXRDeviceClient(); + uv_loop_t *loop = client_context->getScriptingEventLoop(); + assert(loop != nullptr && "Event loop must be ready when creating XRSystem."); - xr_system_ = xrDevice->createXRSystem(loop); + xr_system_ = xrDevice->createXRSystem(loop); + } } -} +} // namespace endor diff --git a/src/client/browser/navigator.hpp b/src/client/browser/navigator.hpp index ed080af9b..cde12a2f9 100644 --- a/src/client/browser/navigator.hpp +++ b/src/client/browser/navigator.hpp @@ -8,87 +8,90 @@ #include #include -namespace browser +namespace endor { - /** + namespace browser + { + /** * Navigator class implementing the Web API Navigator interface. * * Provides information about the browser and system capabilities, * following the MDN Web API specification: * https://developer.mozilla.org/en-US/docs/Web/API/Navigator */ - class Navigator : public scripting_base::JSObjectHolder - { - public: - Navigator(TrClientContextPerProcess *client_context); - ~Navigator() = default; + class Navigator : public scripting_base::JSObjectHolder + { + public: + Navigator(TrClientContextPerProcess *client_context); + ~Navigator() = default; - // Browser identification - std::string userAgent() const; - std::string platform() const; - std::string vendor() const; - std::string vendorSub() const; - std::string product() const; - std::string productSub() const; + // Browser identification + std::string userAgent() const; + std::string platform() const; + std::string vendor() const; + std::string vendorSub() const; + std::string product() const; + std::string productSub() const; - // Browser capabilities - bool isOnline() const; - bool isCookieEnabled() const; - bool isDoNotTrack() const; - int hardwareConcurrency() const; - long long maxTouchPoints() const; + // Browser capabilities + bool isOnline() const; + bool isCookieEnabled() const; + bool isDoNotTrack() const; + int hardwareConcurrency() const; + long long maxTouchPoints() const; - // Language support - std::string language() const; - std::vector languages() const; + // Language support + std::string language() const; + std::vector languages() const; - // User preferences - bool isJavaEnabled() const; - std::string colorScheme() const; + // User preferences + bool isJavaEnabled() const; + std::string colorScheme() const; - // Platform features - bool hasWebGL() const; - bool hasWebXR() const; - bool hasServiceWorker() const; - bool hasGeolocation() const; - bool hasMediaDevices() const; - bool hasPermissions() const; + // Platform features + bool hasWebGL() const; + bool hasWebXR() const; + bool hasServiceWorker() const; + bool hasGeolocation() const; + bool hasMediaDevices() const; + bool hasPermissions() const; - // Battery API - bool hasBattery() const; + // Battery API + bool hasBattery() const; - // Clipboard API - bool hasClipboard() const; + // Clipboard API + bool hasClipboard() const; - // Storage - long long getStorageQuota() const; + // Storage + long long getStorageQuota() const; - // Components - inline std::shared_ptr getWebGLContext() const - { - return gl_context_; - } - inline std::shared_ptr getXRSystem() const - { - return xr_system_; - } + // Components + inline std::shared_ptr getWebGLContext() const + { + return gl_context_; + } + inline std::shared_ptr getXRSystem() const + { + return xr_system_; + } - private: - void initXRSystem(TrClientContextPerProcess *client_context); + private: + void initXRSystem(TrClientContextPerProcess *client_context); - private: - std::string user_agent_; - std::string platform_; - std::string vendor_; - std::string language_; - std::vector languages_; - int hardware_concurrency_; - bool online_; - bool cookie_enabled_; - bool do_not_track_; + private: + std::string user_agent_; + std::string platform_; + std::string vendor_; + std::string language_; + std::vector languages_; + int hardware_concurrency_; + bool online_; + bool cookie_enabled_; + bool do_not_track_; - // Components - std::shared_ptr gl_context_; - std::shared_ptr xr_system_; - }; -} + // Components + std::shared_ptr gl_context_; + std::shared_ptr xr_system_; + }; + } +} // namespace endor diff --git a/src/client/browser/types.hpp b/src/client/browser/types.hpp index 686ea1b5f..bef2a973a 100644 --- a/src/client/browser/types.hpp +++ b/src/client/browser/types.hpp @@ -1,7 +1,10 @@ #pragma once -namespace browser +namespace endor { - class Window; - class Location; -} + namespace browser + { + class Window; + class Location; + } +} // namespace endor diff --git a/src/client/browser/window.cpp b/src/client/browser/window.cpp index 3149b2f88..dd574e4ac 100644 --- a/src/client/browser/window.cpp +++ b/src/client/browser/window.cpp @@ -9,188 +9,191 @@ #include "./window.hpp" -namespace browser +namespace endor { - using namespace std; - using namespace client_cssom; - using namespace client_cssom::rules; - using namespace client_cssom::values; - - Window::Window(TrClientContextPerProcess *client_context) - : dom::DOMEventTarget() - , client_context_(client_context) - , navigator_(make_shared(client_context)) + namespace browser { - } - - const ComputedStyle &Window::getComputedStyle(shared_ptr elementOrTextNode, - optional pseudoElt) const - { - assert(elementOrTextNode != nullptr && elementOrTextNode->isElementOrText() && - "The element or text node must not be null and must be an element or text node."); - - shared_ptr computedStyle = document_->styleCache().findStyle(elementOrTextNode); - if (computedStyle != nullptr) + using namespace std; + using namespace client_cssom; + using namespace client_cssom::rules; + using namespace client_cssom::values; + + Window::Window(TrClientContextPerProcess *client_context) + : dom::DOMEventTarget() + , client_context_(client_context) + , navigator_(make_shared(client_context)) { - return *computedStyle; } - else + + const ComputedStyle &Window::getComputedStyle(shared_ptr elementOrTextNode, + optional pseudoElt) const { - auto newComputedStyle = createComputedStyle(elementOrTextNode, pseudoElt, true /* writeCache */); - return *newComputedStyle; // Return the newly created computed style. + assert(elementOrTextNode != nullptr && elementOrTextNode->isElementOrText() && + "The element or text node must not be null and must be an element or text node."); + + shared_ptr computedStyle = document_->styleCache().findStyle(elementOrTextNode); + if (computedStyle != nullptr) + { + return *computedStyle; + } + else + { + auto newComputedStyle = createComputedStyle(elementOrTextNode, pseudoElt, true /* writeCache */); + return *newComputedStyle; // Return the newly created computed style. + } } - } - const shared_ptr Window::createComputedStyle(shared_ptr elementOrTextNode, - optional pseudoElt, - bool writeCache) const - { - assert(elementOrTextNode != nullptr && elementOrTextNode->isElementOrText() && - "The element or text node must not be null and must be an element or text node."); + const shared_ptr Window::createComputedStyle(shared_ptr elementOrTextNode, + optional pseudoElt, + bool writeCache) const + { + assert(elementOrTextNode != nullptr && elementOrTextNode->isElementOrText() && + "The element or text node must not be null and must be an element or text node."); - computed::Context context = computed::Context::From(elementOrTextNode); - shared_ptr computedStyle = document_->styleCache().createStyle(elementOrTextNode, - false, - writeCache); - computedStyle->update(context); + computed::Context context = computed::Context::From(elementOrTextNode); + shared_ptr computedStyle = document_->styleCache().createStyle(elementOrTextNode, + false, + writeCache); + computedStyle->update(context); - if (elementOrTextNode->isText()) - return computedStyle; // If it's a text node, return the computed style directly. + if (elementOrTextNode->isText()) + return computedStyle; // If it's a text node, return the computed style directly. - // Get the HTML element from the node. - auto htmlElement = dynamic_pointer_cast(elementOrTextNode); - assert(htmlElement != nullptr && "The node must be an HTMLElement."); + // Get the HTML element from the node. + auto htmlElement = dynamic_pointer_cast(elementOrTextNode); + assert(htmlElement != nullptr && "The node must be an HTMLElement."); - // Update the style from the stylesheets. - const auto &stylesheets = elementOrTextNode->getOwnerDocumentChecked().styleSheets(); - for (auto stylesheet : stylesheets) - { - for (auto rule : stylesheet->cssRules()) + // Update the style from the stylesheets. + const auto &stylesheets = elementOrTextNode->getOwnerDocumentChecked().styleSheets(); + for (auto stylesheet : stylesheets) { - auto styleRule = dynamic_pointer_cast(rule); - if (styleRule != nullptr) + for (auto rule : stylesheet->cssRules()) { - if (selectors::matchesSelectorList(styleRule->selectors(), htmlElement)) - computedStyle->update(styleRule->style(), context); + auto styleRule = dynamic_pointer_cast(rule); + if (styleRule != nullptr) + { + if (selectors::matchesSelectorList(styleRule->selectors(), htmlElement)) + computedStyle->update(styleRule->style(), context); + } + // TODO: handle other types of rules, such as `CSSImportRule`, `CSSMediaRule`, etc. } - // TODO: handle other types of rules, such as `CSSImportRule`, `CSSMediaRule`, etc. } + + // Update the style from the element's inline style. + auto elementStyle = htmlElement->style(); + computedStyle->update(elementStyle, context); // Override the style from the element's. + return computedStyle; } - // Update the style from the element's inline style. - auto elementStyle = htmlElement->style(); - computedStyle->update(elementStyle, context); // Override the style from the element's. - return computedStyle; - } + void Window::applyViewportMeta(const dom::ViewportMeta &viewport_meta) + { + bool dimensions_changed = false; - void Window::applyViewportMeta(const dom::ViewportMeta &viewport_meta) - { - bool dimensions_changed = false; + // Apply width using Device class + if (viewport_meta.device_width) + { + float device_width = client_cssom::Device::DeviceWidth; + if (device_width != inner_width_) + { + inner_width_ = device_width; + outer_width_ = device_width; + device_.setViewportWidth(device_width, true); + dimensions_changed = true; + } + } + else if (viewport_meta.width) + { + float new_width = *viewport_meta.width; + if (new_width != inner_width_) + { + inner_width_ = new_width; + outer_width_ = new_width; + device_.setViewportWidth(new_width, false); + dimensions_changed = true; + } + } - // Apply width using Device class - if (viewport_meta.device_width) - { - float device_width = client_cssom::Device::DeviceWidth; - if (device_width != inner_width_) + // Apply height using Device class + if (viewport_meta.device_height) { - inner_width_ = device_width; - outer_width_ = device_width; - device_.setViewportWidth(device_width, true); - dimensions_changed = true; + float device_height = client_cssom::Device::DeviceHeight; + if (device_height != inner_height_) + { + inner_height_ = device_height; + outer_height_ = device_height; + device_.setViewportHeight(device_height, true); + dimensions_changed = true; + } } - } - else if (viewport_meta.width) - { - float new_width = *viewport_meta.width; - if (new_width != inner_width_) + else if (viewport_meta.height) { - inner_width_ = new_width; - outer_width_ = new_width; - device_.setViewportWidth(new_width, false); - dimensions_changed = true; + float new_height = *viewport_meta.height; + if (new_height != inner_height_) + { + inner_height_ = new_height; + outer_height_ = new_height; + device_.setViewportHeight(new_height, false); + dimensions_changed = true; + } } - } - // Apply height using Device class - if (viewport_meta.device_height) - { - float device_height = client_cssom::Device::DeviceHeight; - if (device_height != inner_height_) + // Apply scaling factors using Device class + if (viewport_meta.initial_scale) { - inner_height_ = device_height; - outer_height_ = device_height; - device_.setViewportHeight(device_height, true); - dimensions_changed = true; + device_.setInitialScale(*viewport_meta.initial_scale); } - } - else if (viewport_meta.height) - { - float new_height = *viewport_meta.height; - if (new_height != inner_height_) + if (viewport_meta.minimum_scale) { - inner_height_ = new_height; - outer_height_ = new_height; - device_.setViewportHeight(new_height, false); - dimensions_changed = true; + device_.setMinimumScale(*viewport_meta.minimum_scale); + } + if (viewport_meta.maximum_scale) + { + device_.setMaximumScale(*viewport_meta.maximum_scale); + } + if (viewport_meta.user_scalable) + { + device_.setUserScalable(*viewport_meta.user_scalable); } - } - // Apply scaling factors using Device class - if (viewport_meta.initial_scale) - { - device_.setInitialScale(*viewport_meta.initial_scale); + if (dimensions_changed) + { + // Notify the client context about viewport changes if needed + // This would trigger a re-layout of the document + if (client_context_) + { + // TODO(yorkie): Add RPC call to notify about viewport size change + } + } } - if (viewport_meta.minimum_scale) + + long Window::requestAnimationFrame(AnimationFrameCallback callback) { - device_.setMinimumScale(*viewport_meta.minimum_scale); + assert(animation_frame_provider_ != nullptr && animation_frame_provider_->isStarted() && + "AnimationFrameProvider must be started before requesting animation frames."); + return animation_frame_provider_->requestAnimationFrame(callback); } - if (viewport_meta.maximum_scale) + + void Window::cancelAnimationFrame(long handle) { - device_.setMaximumScale(*viewport_meta.maximum_scale); + if (animation_frame_provider_ == nullptr || !animation_frame_provider_->isStarted()) + return; + animation_frame_provider_->cancelAnimationFrame(handle); } - if (viewport_meta.user_scalable) + + void Window::startAnimationFrameProvider() { - device_.setUserScalable(*viewport_meta.user_scalable); + assert((animation_frame_provider_ == nullptr || !animation_frame_provider_->isStarted()) && + "AnimationFrameProvider must be null and not started."); + animation_frame_provider_ = make_shared(); + animation_frame_provider_->start(); } - if (dimensions_changed) + void Window::configureDocument(shared_ptr document) { - // Notify the client context about viewport changes if needed - // This would trigger a re-layout of the document - if (client_context_) - { - // TODO(yorkie): Add RPC call to notify about viewport size change - } + assert(is_document_configured_ == false); + document_ = document; + location_ = make_shared(document_->baseURI); + is_document_configured_ = true; } } - - long Window::requestAnimationFrame(AnimationFrameCallback callback) - { - assert(animation_frame_provider_ != nullptr && animation_frame_provider_->isStarted() && - "AnimationFrameProvider must be started before requesting animation frames."); - return animation_frame_provider_->requestAnimationFrame(callback); - } - - void Window::cancelAnimationFrame(long handle) - { - if (animation_frame_provider_ == nullptr || !animation_frame_provider_->isStarted()) - return; - animation_frame_provider_->cancelAnimationFrame(handle); - } - - void Window::startAnimationFrameProvider() - { - assert((animation_frame_provider_ == nullptr || !animation_frame_provider_->isStarted()) && - "AnimationFrameProvider must be null and not started."); - animation_frame_provider_ = make_shared(); - animation_frame_provider_->start(); - } - - void Window::configureDocument(shared_ptr document) - { - assert(is_document_configured_ == false); - document_ = document; - location_ = make_shared(document_->baseURI); - is_document_configured_ = true; - } -} +} // namespace endor diff --git a/src/client/browser/window.hpp b/src/client/browser/window.hpp index bc2f727f3..4bee8ded4 100644 --- a/src/client/browser/window.hpp +++ b/src/client/browser/window.hpp @@ -20,262 +20,265 @@ #include // Forward declaration to avoid circular dependency -namespace dom -{ - struct ViewportMeta; -} -namespace browser +namespace endor { - /** + namespace dom + { + struct ViewportMeta; + } + + namespace browser + { + /** * @enum WindowTarget * The `WindowTarget` enum represents the target browsing context for opening a URL. */ - enum class WindowTarget - { - /** + enum class WindowTarget + { + /** * Open the URL in the current browsing context. */ - Self, - /** + Self, + /** * Open the URL in a new browsing context, it will create a new volume to render. */ - Blank, - /** + Blank, + /** * Open the URL in a new external browsing context, namely open the page in the classic Web browser in the system. */ - BlankClassic, - /** + BlankClassic, + /** * Open the URL in the parent browsing context, if the parent is from external classic browser, it will open the URL in the classic browser. */ - Parent, - /** + Parent, + /** * The topmost browsing context. */ - Top - }; + Top + }; - /** + /** * Convert the window target to the string. * * @param target The window target. * @returns The string representation of the window target. * @throws std::invalid_argument if the target is invalid. */ - inline std::string to_string(WindowTarget target) - { - switch (target) + inline std::string to_string(WindowTarget target) { - case WindowTarget::Self: - return "_self"; - case WindowTarget::Blank: - return "_blank"; - case WindowTarget::BlankClassic: - return "_blankClassic"; - case WindowTarget::Parent: - return "_parent"; - case WindowTarget::Top: - return "_top"; - default: - throw std::invalid_argument("Invalid window open target: " + std::to_string(static_cast(target))); + switch (target) + { + case WindowTarget::Self: + return "_self"; + case WindowTarget::Blank: + return "_blank"; + case WindowTarget::BlankClassic: + return "_blankClassic"; + case WindowTarget::Parent: + return "_parent"; + case WindowTarget::Top: + return "_top"; + default: + throw std::invalid_argument("Invalid window open target: " + std::to_string(static_cast(target))); + } } - } - /** + /** * @class WindowFeatures * The `WindowFeatures` class represents the features of a new window. */ - class WindowFeatures final - { - public: - WindowFeatures() = default; + class WindowFeatures final + { + public: + WindowFeatures() = default; - public: - bool popup = false; // Whether the window should be opened as a popup - bool noopener = false; // Whether the opener should be set to null - bool noreferrer = false; // Whether the referrer should be omitted - std::optional width; // The width of the window - std::optional height; // The height of the window - std::optional depth; // The depth of the window - std::optional left; // The left position of the window - std::optional top; // The top position of the window - }; + public: + bool popup = false; // Whether the window should be opened as a popup + bool noopener = false; // Whether the opener should be set to null + bool noreferrer = false; // Whether the referrer should be omitted + std::optional width; // The width of the window + std::optional height; // The height of the window + std::optional depth; // The depth of the window + std::optional left; // The left position of the window + std::optional top; // The top position of the window + }; - /** + /** * @class Window * The `Window` class represents a browser window and provides methods to interact with it. */ - class Window : public dom::DOMEventTarget, - public std::enable_shared_from_this - { - friend class dom::Document; + class Window : public dom::DOMEventTarget, + public std::enable_shared_from_this + { + friend class dom::Document; - public: - Window(TrClientContextPerProcess *clientContext = TrClientContextPerProcess::Get()); + public: + Window(TrClientContextPerProcess *clientContext = TrClientContextPerProcess::Get()); - public: - inline const std::string &name() const - { - return name_; - } - inline const std::string &origin() const - { - return origin_; - } - inline const client_cssom::Device &device() const - { - return device_; - } - inline client_cssom::Device &device() - { - return device_; - } + public: + inline const std::string &name() const + { + return name_; + } + inline const std::string &origin() const + { + return origin_; + } + inline const client_cssom::Device &device() const + { + return device_; + } + inline client_cssom::Device &device() + { + return device_; + } - inline bool fullscreen() const - { - return fullscreen_; - } - inline float innerWidth() const - { - return inner_width_; - } - inline float innerHeight() const - { - return inner_height_; - } - inline float innerDepth() const - { - return inner_depth_; - } - inline float outerWidth() const - { - return outer_width_; - } - inline float outerHeight() const - { - return outer_height_; - } - inline float outerDepth() const - { - return outer_depth_; - } + inline bool fullscreen() const + { + return fullscreen_; + } + inline float innerWidth() const + { + return inner_width_; + } + inline float innerHeight() const + { + return inner_height_; + } + inline float innerDepth() const + { + return inner_depth_; + } + inline float outerWidth() const + { + return outer_width_; + } + inline float outerHeight() const + { + return outer_height_; + } + inline float outerDepth() const + { + return outer_depth_; + } - inline float scrollX() const - { - return scroll_x_; - } - inline float scrollY() const - { - return scroll_y_; - } + inline float scrollX() const + { + return scroll_x_; + } + inline float scrollY() const + { + return scroll_y_; + } - inline float devicePixelRatio() const - { - return device_.devicePixelRatio(); - } - inline float &devicePixelRatio() - { - return device_.devicePixelRatio(); - } + inline float devicePixelRatio() const + { + return device_.devicePixelRatio(); + } + inline float &devicePixelRatio() + { + return device_.devicePixelRatio(); + } - inline TrViewport viewport() const - { - auto viewport = device_.viewportSize(); - return TrViewport(viewport.x, viewport.y, 0, 0); - } + inline TrViewport viewport() const + { + auto viewport = device_.viewportSize(); + return TrViewport(viewport.x, viewport.y, 0, 0); + } - inline std::shared_ptr self() - { - return shared_from_this(); - } - inline std::shared_ptr document() const - { - return document_; - } - inline std::shared_ptr location() const - { - return location_; - } - inline std::shared_ptr navigator() const - { - return navigator_; - } + inline std::shared_ptr self() + { + return shared_from_this(); + } + inline std::shared_ptr document() const + { + return document_; + } + inline std::shared_ptr location() const + { + return location_; + } + inline std::shared_ptr navigator() const + { + return navigator_; + } - public: - /** + public: + /** * Displays an alert dialog with the specified message. * * @param message The message to display. */ - inline void alert(const std::string &message) - { - client_context_->makeRpcCall("window.alert", {message}); - } + inline void alert(const std::string &message) + { + client_context_->makeRpcCall("window.alert", {message}); + } - /** + /** * Sends a confirmation dialog with the specified message. */ - inline bool confirm(const std::string &message) - { - client_context_->makeRpcCall("window.confirm", {message}); - // TODO(yorkie): Return the actual result from the RPC call. - return true; - } + inline bool confirm(const std::string &message) + { + client_context_->makeRpcCall("window.confirm", {message}); + // TODO(yorkie): Return the actual result from the RPC call. + return true; + } - /** + /** * Closes the current window. */ - inline void close() - { - client_context_->makeRpcCall("window.close", {}); - } + inline void close() + { + client_context_->makeRpcCall("window.close", {}); + } - /** + /** * Opens a new window with the specified URL and target. * * @param url The URL to open. * @param target The target browsing context. * @param features The features of the new window. */ - inline void open(const std::string &url, WindowTarget target, const WindowFeatures &features = WindowFeatures()) - { - client_context_->makeRpcCall("window.open", {url, to_string(target)}); - } + inline void open(const std::string &url, WindowTarget target, const WindowFeatures &features = WindowFeatures()) + { + client_context_->makeRpcCall("window.open", {url, to_string(target)}); + } - /** + /** * Displays a prompt dialog with the specified message and default value. * * @param message The message to display. * @param defaultValue The default value for the input field. */ - inline void prompt(const std::string &message, const std::string &defaultValue) - { - client_context_->makeRpcCall("window.prompt", {message, defaultValue}); - } + inline void prompt(const std::string &message, const std::string &defaultValue) + { + client_context_->makeRpcCall("window.prompt", {message, defaultValue}); + } - using AnimationFrameCallback = std::function; + using AnimationFrameCallback = std::function; - /** + /** * Schedules a callback to be invoked before the next repaint. * * @param AnimationFrameCallback The callback to be invoked. * @return A long integer representing the request ID, which can be used to cancel the request. */ - long requestAnimationFrame(AnimationFrameCallback); + long requestAnimationFrame(AnimationFrameCallback); - /** + /** * Cancels a previously scheduled animation frame request. * * @param handle The request ID returned by `requestAnimationFrame`. */ - void cancelAnimationFrame(long handle); + void cancelAnimationFrame(long handle); - /** + /** * Starts the animation frame provider if it is not already started. */ - void startAnimationFrameProvider(); + void startAnimationFrameProvider(); - /** + /** * Gets an object containing the values of all CSS properties of an element, after applying active stylesheets and * resolving any basic computation those values may contain. * @@ -284,11 +287,11 @@ namespace browser * * @todo Implement the pseudo-element support. */ - const client_cssom::ComputedStyle & - getComputedStyle(std::shared_ptr elementOrTextNode, - std::optional pseudoElt = std::nullopt) const; + const client_cssom::ComputedStyle & + getComputedStyle(std::shared_ptr elementOrTextNode, + std::optional pseudoElt = std::nullopt) const; - /** + /** * Creates a new computed style for the specified element or text node. * * @param elementOrTextNode The element or text node to create the computed style for. @@ -297,40 +300,41 @@ namespace browser * * @return A shared pointer to the created computed style. */ - const std::shared_ptr - createComputedStyle(std::shared_ptr elementOrTextNode, - std::optional pseudoElt = std::nullopt, - bool writeCache = true) const; + const std::shared_ptr + createComputedStyle(std::shared_ptr elementOrTextNode, + std::optional pseudoElt = std::nullopt, + bool writeCache = true) const; - /** + /** * Apply viewport meta settings to the window dimensions */ - void applyViewportMeta(const dom::ViewportMeta &viewport_meta); + void applyViewportMeta(const dom::ViewportMeta &viewport_meta); - private: - // Configure the document to the window. - void configureDocument(std::shared_ptr document); + private: + // Configure the document to the window. + void configureDocument(std::shared_ptr document); - private: // Window properties - std::string name_ = ""; - std::string origin_ = ""; - client_cssom::Device device_ = client_cssom::Device(); - bool fullscreen_ = false; - float inner_width_ = client_cssom::ScreenWidth; - float inner_height_ = client_cssom::ScreenHeight; - float inner_depth_ = client_cssom::VolumeDepth; - float outer_width_ = client_cssom::ScreenWidth; - float outer_height_ = client_cssom::ScreenHeight; - float outer_depth_ = client_cssom::VolumeDepth; - float scroll_x_ = 0.0f; - float scroll_y_ = 0.0f; - std::shared_ptr document_; - std::shared_ptr location_; - std::shared_ptr navigator_; - std::shared_ptr animation_frame_provider_; + private: // Window properties + std::string name_ = ""; + std::string origin_ = ""; + client_cssom::Device device_ = client_cssom::Device(); + bool fullscreen_ = false; + float inner_width_ = client_cssom::ScreenWidth; + float inner_height_ = client_cssom::ScreenHeight; + float inner_depth_ = client_cssom::VolumeDepth; + float outer_width_ = client_cssom::ScreenWidth; + float outer_height_ = client_cssom::ScreenHeight; + float outer_depth_ = client_cssom::VolumeDepth; + float scroll_x_ = 0.0f; + float scroll_y_ = 0.0f; + std::shared_ptr document_; + std::shared_ptr location_; + std::shared_ptr navigator_; + std::shared_ptr animation_frame_provider_; - private: - TrClientContextPerProcess *client_context_; // The client context for making RPC calls - bool is_document_configured_ = false; - }; -} // namespace browser + private: + TrClientContextPerProcess *client_context_; // The client context for making RPC calls + bool is_document_configured_ = false; + }; + } // namespace browser +} // namespace endor diff --git a/src/client/builtin_scene/asset.hpp b/src/client/builtin_scene/asset.hpp index fcb11f92e..382b1f1f9 100644 --- a/src/client/builtin_scene/asset.hpp +++ b/src/client/builtin_scene/asset.hpp @@ -5,94 +5,97 @@ #include #include "./ecs.hpp" -namespace builtin_scene::asset +namespace endor { - typedef uint32_t AssetId; - constexpr AssetId MAX_ASSET_ID = 10000; + namespace builtin_scene::asset + { + typedef uint32_t AssetId; + constexpr AssetId MAX_ASSET_ID = 10000; - /** + /** * A specialized `Resource` class for managing one kind of assets, such as meshes, textures, etc. * * @tparam T The type of the asset. */ - template - class Assets : public ecs::Resource - { - public: - Assets() = default; + template + class Assets : public ecs::Resource + { + public: + Assets() = default; - public: - /** + public: + /** * Add a new asset to the assets. * * @tparam S The type of the asset to add. * @param asset The asset to add. */ - template - std::shared_ptr add(std::shared_ptr asset) - { - static TrIdGenerator assetIdGen(0x1); - AssetId assetId = assetIdGen.get(); - if (assetId >= MAX_ASSET_ID) - throw std::runtime_error("The asset id is out of range."); + template + std::shared_ptr add(std::shared_ptr asset) + { + static TrIdGenerator assetIdGen(0x1); + AssetId assetId = assetIdGen.get(); + if (assetId >= MAX_ASSET_ID) + throw std::runtime_error("The asset id is out of range."); - assets_[assetId] = dynamic_pointer_cast(asset); - return asset; - } - /** + assets_[assetId] = dynamic_pointer_cast(asset); + return asset; + } + /** * Check if the assets contain the asset of the given id. * * @param assetId The id of the asset to check. * @returns `true` if the assets contain the asset of the given id, `false` otherwise. */ - inline bool contains(AssetId assetId) - { - return assets_.find(assetId) != assets_.end(); - } - /** + inline bool contains(AssetId assetId) + { + return assets_.find(assetId) != assets_.end(); + } + /** * Get the asset of the given id. * * @param assetId The id of the asset to get. * @returns The asset of the given id. */ - inline std::shared_ptr &get(AssetId assetId) - { - return assets_[assetId]; - } - /** + inline std::shared_ptr &get(AssetId assetId) + { + return assets_[assetId]; + } + /** * Remove the asset of the given id. * * @param assetId The id of the asset to remove. */ - inline void remove(AssetId assetId) - { - assets_.erase(assetId); - } - /** + inline void remove(AssetId assetId) + { + assets_.erase(assetId); + } + /** * Clear all the assets. */ - inline void clear() - { - assets_.clear(); - } - /** + inline void clear() + { + assets_.clear(); + } + /** * Check if the assets are empty. * * @returns `true` if the assets are empty, `false` otherwise. */ - inline bool empty() - { - return assets_.empty(); - } - /** + inline bool empty() + { + return assets_.empty(); + } + /** * @returns The number of assets in the assets. */ - inline void length() - { - return assets_.size(); - } + inline void length() + { + return assets_.size(); + } - private: - std::unordered_map> assets_{}; - }; -} // namespace builtin_scene::asset + private: + std::unordered_map> assets_{}; + }; + } // namespace builtin_scene::asset +} // namespace endor diff --git a/src/client/builtin_scene/bounding_box.hpp b/src/client/builtin_scene/bounding_box.hpp index c6a893c5c..c8b2a457c 100644 --- a/src/client/builtin_scene/bounding_box.hpp +++ b/src/client/builtin_scene/bounding_box.hpp @@ -2,114 +2,117 @@ #include -namespace builtin_scene +namespace endor { - /** + namespace builtin_scene + { + /** * The component representing the bounding box of an entity. * * NOTE: This component used the pixel unit for the length storage, remember to convert it to the meter unit when to use it * in drawing or physics calculations. */ - class BoundingBox : public ecs::Component - { - public: - BoundingBox() - : ecs::Component() - , width_(0.0f) - , height_(0.0f) - , depth_(0.0f) - { - } - BoundingBox(float width, float height, float depth) - : ecs::Component() - , width_(width) - , height_(height) - , depth_(depth) - { - assert(width_ >= 0.0f); - assert(height_ >= 0.0f); - assert(depth_ >= 0.0f); - } - BoundingBox(glm::vec3 min, glm::vec3 max) - : BoundingBox(max.x - min.x, max.y - min.y, max.z - min.z) + class BoundingBox : public ecs::Component { - } + public: + BoundingBox() + : ecs::Component() + , width_(0.0f) + , height_(0.0f) + , depth_(0.0f) + { + } + BoundingBox(float width, float height, float depth) + : ecs::Component() + , width_(width) + , height_(height) + , depth_(depth) + { + assert(width_ >= 0.0f); + assert(height_ >= 0.0f); + assert(depth_ >= 0.0f); + } + BoundingBox(glm::vec3 min, glm::vec3 max) + : BoundingBox(max.x - min.x, max.y - min.y, max.z - min.z) + { + } - public: - // The width in pixel. - inline float width() const - { - return width_; - } - // The height in pixel. - inline float height() const - { - return height_; - } - // The depth in pixel. - inline float depth() const - { - return depth_; - } - // The size in pixel. - inline glm::vec3 size() const - { - return glm::vec3(width_, height_, depth_); - } - // The min point of the bounding box. - inline glm::vec3 min() const - { - return glm::vec3(-width_ / 2.0f, -height_ / 2.0f, -depth_ / 2.0f); - } - // The max point of the bounding box. - inline glm::vec3 max() const - { - return glm::vec3(width_ / 2.0f, height_ / 2.0f, depth_ / 2.0f); - } + public: + // The width in pixel. + inline float width() const + { + return width_; + } + // The height in pixel. + inline float height() const + { + return height_; + } + // The depth in pixel. + inline float depth() const + { + return depth_; + } + // The size in pixel. + inline glm::vec3 size() const + { + return glm::vec3(width_, height_, depth_); + } + // The min point of the bounding box. + inline glm::vec3 min() const + { + return glm::vec3(-width_ / 2.0f, -height_ / 2.0f, -depth_ / 2.0f); + } + // The max point of the bounding box. + inline glm::vec3 max() const + { + return glm::vec3(width_ / 2.0f, height_ / 2.0f, depth_ / 2.0f); + } - /** + /** * Update the size of the bounding box. * * @param width The width in pixel. * @param height The height in pixel. * @param depth The depth in pixel. */ - void updateSize(float width, float height, float depth) - { - width_ = width; - height_ = height; - depth_ = depth; - } - /** + void updateSize(float width, float height, float depth) + { + width_ = width; + height_ = height; + depth_ = depth; + } + /** * Update the size of the bounding box using a 3D vector. * * @param size The size in pixel. */ - inline void updateSize(const glm::vec3 &size) - { - updateSize(size.x, size.y, size.z); - } + inline void updateSize(const glm::vec3 &size) + { + updateSize(size.x, size.y, size.z); + } - glm::vec3 diff(const BoundingBox &other) const - { - return glm::vec3(width_ - other.width_, height_ - other.height_, depth_ - other.depth_); - } + glm::vec3 diff(const BoundingBox &other) const + { + return glm::vec3(width_ - other.width_, height_ - other.height_, depth_ - other.depth_); + } - public: - /** + public: + /** * Calculate the difference between this bounding box and the other, such as: `bbox1 - bbox2` means the difference between `bbox1` * and `bbox2`. * * @returns The difference between this bounding box and the other. */ - glm::vec3 operator-(const BoundingBox &other) const - { - return diff(other); - } + glm::vec3 operator-(const BoundingBox &other) const + { + return diff(other); + } - private: - float width_; - float height_; - float depth_; - }; -} + private: + float width_; + float height_; + float depth_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/camera.hpp b/src/client/builtin_scene/camera.hpp index 35ff7d8de..62072f62f 100644 --- a/src/client/builtin_scene/camera.hpp +++ b/src/client/builtin_scene/camera.hpp @@ -7,62 +7,65 @@ #include "./transform.hpp" #include "./xr.hpp" -namespace builtin_scene +namespace endor { - class Camera : public ecs::Component + namespace builtin_scene { - public: - using ecs::Component::Component; - - public: - bool active() + class Camera : public ecs::Component { - return active_; - } + public: + using ecs::Component::Component; - private: - bool active_ = false; - std::optional viewport_ = std::nullopt; - }; + public: + bool active() + { + return active_; + } - class CameraStartupSystem : public ecs::System - { - public: - using ecs::System::System; + private: + bool active_ = false; + std::optional viewport_ = std::nullopt; + }; - public: - const std::string name() const override + class CameraStartupSystem : public ecs::System { - return "CameraStartupSystem"; - } - void onExecute() override - { - // Create the camera for rendering. - spawn(Camera(), Transform()); - } - }; + public: + using ecs::System::System; - class CameraUpdateSystem : public ecs::System - { - public: - using ecs::System::System; + public: + const std::string name() const override + { + return "CameraStartupSystem"; + } + void onExecute() override + { + // Create the camera for rendering. + spawn(Camera(), Transform()); + } + }; - public: - const std::string name() const override - { - return "CameraUpdateSystem"; - } - void onExecute() override + class CameraUpdateSystem : public ecs::System { - auto cameraEntity = firstEntity(); - if (!cameraEntity.has_value()) - throw std::runtime_error("Camera not found."); + public: + using ecs::System::System; - auto camera = getComponent(cameraEntity.value()); - if (camera != nullptr && camera->active()) + public: + const std::string name() const override { - // Update and render the camera. + return "CameraUpdateSystem"; + } + void onExecute() override + { + auto cameraEntity = firstEntity(); + if (!cameraEntity.has_value()) + throw std::runtime_error("Camera not found."); + + auto camera = getComponent(cameraEntity.value()); + if (camera != nullptr && camera->active()) + { + // Update and render the camera. + } } - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/client_renderer.cpp b/src/client/builtin_scene/client_renderer.cpp index 71663c43c..e75e4578c 100644 --- a/src/client/builtin_scene/client_renderer.cpp +++ b/src/client/builtin_scene/client_renderer.cpp @@ -10,236 +10,238 @@ #include "./materials.hpp" #include "./camera.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace ecs; - - void RenderStartupSystem::onExecute() + namespace builtin_scene { - } + using namespace std; + using namespace ecs; - void RenderSystem::onExecute() - { - if (renderer_ == nullptr) [[unlikely]] + void RenderStartupSystem::onExecute() { - renderer_ = getResource(); - assert(renderer_ != nullptr); // The renderer must be valid. } - if (xrExperience_ == nullptr) [[unlikely]] + void RenderSystem::onExecute() { - xrExperience_ = getResource(); - } - if (xrExperience_ != nullptr) - { - auto xrViewerPose = xrExperience_->viewerPose(); - if (xrViewerPose != nullptr) + if (renderer_ == nullptr) [[unlikely]] { - auto &views = xrViewerPose->views(); - if (!xrExperience_->multiviewEnabled()) - { - for (auto view : views) - render(XRRenderTarget(view)); - } - else + renderer_ = getResource(); + assert(renderer_ != nullptr); // The renderer must be valid. + } + + if (xrExperience_ == nullptr) [[unlikely]] + { + xrExperience_ = getResource(); + } + if (xrExperience_ != nullptr) + { + auto xrViewerPose = xrExperience_->viewerPose(); + if (xrViewerPose != nullptr) { - render(XRRenderTarget(views)); + auto &views = xrViewerPose->views(); + if (!xrExperience_->multiviewEnabled()) + { + for (auto view : views) + render(XRRenderTarget(view)); + } + else + { + render(XRRenderTarget(views)); + } + return; } - return; } - } - // Fallback to the default rendering - render(nullopt); - } + // Fallback to the default rendering + render(nullopt); + } - void RenderSystem::render(optional renderTarget) - { - auto roots = queryEntities([](const hierarchy::Root &root) -> bool - { return root.renderable == true; }); - if (roots.size() > 0) + void RenderSystem::render(optional renderTarget) { - if (renderTarget != nullopt) + auto roots = queryEntities([](const hierarchy::Root &root) -> bool + { return root.renderable == true; }); + if (roots.size() > 0) { - if (!renderTarget->isMultiview()) - renderer_->setViewport(renderTarget->view()->viewport()); - } + if (renderTarget != nullopt) + { + if (!renderTarget->isMultiview()) + renderer_->setViewport(renderTarget->view()->viewport()); + } - // Update data for meshes(including instanced), materials, particles, etc. - for (const auto &root : roots) - traverseAndUpdate(root, renderTarget); + // Update data for meshes(including instanced), materials, particles, etc. + for (const auto &root : roots) + traverseAndUpdate(root, renderTarget); - // Render the objects (opaques, transparents). - renderPass(roots, RenderPass::kOpaques, renderTarget); - renderPass(roots, RenderPass::kTransparents, renderTarget); + // Render the objects (opaques, transparents). + renderPass(roots, RenderPass::kOpaques, renderTarget); + renderPass(roots, RenderPass::kTransparents, renderTarget); - // TODO(yorkie): Render the particles and other post-processing effects. + // TODO(yorkie): Render the particles and other post-processing effects. + } } - } - - struct RenderableMeshEntity - { - EntityId id; - shared_ptr meshComponent; - shared_ptr materialComponent; - }; - void RenderSystem::renderPass(vector &roots, RenderPass renderPass, optional renderTarget) - { - assert(roots.size() > 0 && "The roots must not be empty."); + struct RenderableMeshEntity + { + EntityId id; + shared_ptr meshComponent; + shared_ptr materialComponent; + }; - vector entities; // TODO(yorkie): cache this? - auto addRenderableItem = [this, &renderPass, &entities](EntityId entity) -> bool + void RenderSystem::renderPass(vector &roots, RenderPass renderPass, optional renderTarget) { - // Render the mesh if it exists - auto mesh = getComponent(entity); - if (mesh != nullptr) + assert(roots.size() > 0 && "The roots must not be empty."); + + vector entities; // TODO(yorkie): cache this? + auto addRenderableItem = [this, &renderPass, &entities](EntityId entity) -> bool { - // If the mesh exists but rendering is disabled, we need to skip its rendering and its children. - if (mesh->isRenderingDisabled()) - return false; + // Render the mesh if it exists + auto mesh = getComponent(entity); + if (mesh != nullptr) + { + // If the mesh exists but rendering is disabled, we need to skip its rendering and its children. + if (mesh->isRenderingDisabled()) + return false; - auto material = getComponent(entity); - if (material && material->matchesPass(renderPass)) - entities.push_back({entity, mesh, material}); - } + auto material = getComponent(entity); + if (material && material->matchesPass(renderPass)) + entities.push_back({entity, mesh, material}); + } - // TODO: support other renderable components (e.g., particles, etc.) - return true; - }; + // TODO: support other renderable components (e.g., particles, etc.) + return true; + }; - // Search for renderable items in the hierarchy - for (const auto &root : roots) - traverse(root, addRenderableItem); + // Search for renderable items in the hierarchy + for (const auto &root : roots) + traverse(root, addRenderableItem); - // Render items - if (entities.size() > 0) - { - onBeforeRender(renderPass, renderTarget); - for (const auto &entity : entities) + // Render items + if (entities.size() > 0) { - renderMesh(entity.id, - entity.meshComponent, - entity.materialComponent, - renderPass, - renderTarget); + onBeforeRender(renderPass, renderTarget); + for (const auto &entity : entities) + { + renderMesh(entity.id, + entity.meshComponent, + entity.materialComponent, + renderPass, + renderTarget); + } + onAfterRender(renderPass, renderTarget); } - onAfterRender(renderPass, renderTarget); } - } - void RenderSystem::renderMesh(const EntityId &entity, - shared_ptr meshComponent, - shared_ptr materialComponent, - RenderPass renderPass, - optional renderTarget) - { - if (!meshComponent->initialized()) - renderer_->initializeMesh3d(meshComponent); - if (!materialComponent->initialized()) - renderer_->initializeMeshMaterial3d(meshComponent, materialComponent); - - // Update the mesh3d and material if needed - renderer_->tryUpdateMeshMaterial3d(meshComponent, materialComponent); - - // Draw - shared_ptr parentTransform = nullptr; - auto parentComponent = getComponent(entity); - if (parentComponent != nullptr) - parentTransform = getComponent(parentComponent->parent()); - renderer_->drawMesh3d(entity, - meshComponent, - materialComponent, - getComponent(entity), - parentTransform, - renderPass, - renderTarget); - } - - void RenderSystem::onBeforeRender(const RenderPass renderPass, optional renderTarget) - { - renderer_->onBeforeRender(renderPass, renderTarget); - } + void RenderSystem::renderMesh(const EntityId &entity, + shared_ptr meshComponent, + shared_ptr materialComponent, + RenderPass renderPass, + optional renderTarget) + { + if (!meshComponent->initialized()) + renderer_->initializeMesh3d(meshComponent); + if (!materialComponent->initialized()) + renderer_->initializeMeshMaterial3d(meshComponent, materialComponent); + + // Update the mesh3d and material if needed + renderer_->tryUpdateMeshMaterial3d(meshComponent, materialComponent); + + // Draw + shared_ptr parentTransform = nullptr; + auto parentComponent = getComponent(entity); + if (parentComponent != nullptr) + parentTransform = getComponent(parentComponent->parent()); + renderer_->drawMesh3d(entity, + meshComponent, + materialComponent, + getComponent(entity), + parentTransform, + renderPass, + renderTarget); + } - void RenderSystem::onAfterRender(const RenderPass renderPass, optional renderTarget) - { - renderer_->onAfterRender(renderPass, renderTarget); - } + void RenderSystem::onBeforeRender(const RenderPass renderPass, optional renderTarget) + { + renderer_->onBeforeRender(renderPass, renderTarget); + } - void RenderSystem::traverse(ecs::EntityId root, std::function &&exec) - { - // Pre-order traversal - if (!exec(root)) - return; + void RenderSystem::onAfterRender(const RenderPass renderPass, optional renderTarget) + { + renderer_->onAfterRender(renderPass, renderTarget); + } - auto children = getComponent(root); - if (children != nullptr) + void RenderSystem::traverse(ecs::EntityId root, std::function &&exec) { - for (const auto &child : children->children()) - traverse(child, std::move(exec)); + // Pre-order traversal + if (!exec(root)) + return; + + auto children = getComponent(root); + if (children != nullptr) + { + for (const auto &child : children->children()) + traverse(child, std::move(exec)); + } } - } - size_t RenderSystem::traverseAndUpdate(ecs::EntityId root, std::optional renderTarget) - { - size_t num = 0; - auto updateEntity = [this, &renderTarget, &num](EntityId entity) -> bool + size_t RenderSystem::traverseAndUpdate(ecs::EntityId root, std::optional renderTarget) { - auto mesh = getComponent(entity); - if (mesh != nullptr) + size_t num = 0; + auto updateEntity = [this, &renderTarget, &num](EntityId entity) -> bool { - // Skip updating data if the mesh rendering is disabled. - if (mesh->isRenderingDisabled()) - return false; + auto mesh = getComponent(entity); + if (mesh != nullptr) + { + // Skip updating data if the mesh rendering is disabled. + if (mesh->isRenderingDisabled()) + return false; - // Update the data for renderable mesh components. - // TODO(yorkie): update transformation matrix here? - if (mesh->is()) - updateInstancedMeshData(*mesh, renderTarget); + // Update the data for renderable mesh components. + // TODO(yorkie): update transformation matrix here? + if (mesh->is()) + updateInstancedMeshData(*mesh, renderTarget); - num++; - } - return true; // Continue traversing children - }; + num++; + } + return true; // Continue traversing children + }; - traverse(root, updateEntity); - return num; - } + traverse(root, updateEntity); + return num; + } - glm::mat4 RenderSystem::getTransformationMatrix(ecs::EntityId id) - { - Transform &transform = const_cast(getComponentChecked(id)); - shared_ptr parentTransform = nullptr; - shared_ptr parentComponent = getComponent(id); - if (parentComponent != nullptr) - parentTransform = getComponent(parentComponent->parent()); - - // The world-space transformation matrix for this entity. - glm::mat4 baseMatrixInWorldSpace = transform.matrix(); - - // Compute the final post transform. - // - // The post transform is updated by the CSS `transform` project, it's stored as reference-space. The following block - // is used to compute the final post transform in the node chain from the current node. - glm::mat4 postMat = glm::mat4(1.0f); + glm::mat4 RenderSystem::getTransformationMatrix(ecs::EntityId id) { - if (parentTransform != nullptr) - postMat = parentTransform->getOrInitPostTransform().accumulatedMatrix(); - auto &postTransform = transform.getOrInitPostTransform(); - postMat = postTransform.matrix() * postMat; - postTransform.setAccumulatedMatrix(postMat); - } + Transform &transform = const_cast(getComponentChecked(id)); + shared_ptr parentTransform = nullptr; + shared_ptr parentComponent = getComponent(id); + if (parentComponent != nullptr) + parentTransform = getComponent(parentComponent->parent()); + + // The world-space transformation matrix for this entity. + glm::mat4 baseMatrixInWorldSpace = transform.matrix(); + + // Compute the final post transform. + // + // The post transform is updated by the CSS `transform` project, it's stored as reference-space. The following block + // is used to compute the final post transform in the node chain from the current node. + glm::mat4 postMat = glm::mat4(1.0f); + { + if (parentTransform != nullptr) + postMat = parentTransform->getOrInitPostTransform().accumulatedMatrix(); + auto &postTransform = transform.getOrInitPostTransform(); + postMat = postTransform.matrix() * postMat; + postTransform.setAccumulatedMatrix(postMat); + } - // Returns the transformation matrix is the world-space base matrix with the post transformation. - return postMat * baseMatrixInWorldSpace; - } + // Returns the transformation matrix is the world-space base matrix with the post transformation. + return postMat * baseMatrixInWorldSpace; + } - void RenderSystem::updateInstancedMeshData(const Mesh3d &meshComponent, optional renderTarget) - { - InstancedMeshBase &instancedMesh = meshComponent.getHandleCheckedAsRef(); + void RenderSystem::updateInstancedMeshData(const Mesh3d &meshComponent, optional renderTarget) + { + InstancedMeshBase &instancedMesh = meshComponent.getHandleCheckedAsRef(); - /** + /** * This function should return a `boolean` value, that indicates whether the `InstancedMesh` needs to refresh its * renderable lists (opaque and transparent) after this iteration. Refreshing the renderable lists is used to update * the instances for opaque and transparent rendering passes, so we only need to achieve it when the lists might be @@ -260,140 +262,141 @@ namespace builtin_scene * transparent color, no texture, and no borders to draw. If an instance's `maybeInvisible` state changes, * it might affect whether the instance should be rendered, so we need to refresh the renderable lists in that case. */ - auto updateInstanceData = [this, &renderTarget](ecs::EntityId id, Instance &instance) -> bool - { - bool needsUpdate = false; - auto transformComponent = getComponent(id); - auto webContentComponent = getComponent(id); - - // Record the maybe invisible state before updating - bool maybeInvisibleBeforeUpdating = instance.maybeInvisible(); - - // Update for instance transformation - // TODO(yorkie): consider improving the performance of getting transformation matrix - if (TR_LIKELY(transformComponent != nullptr)) + auto updateInstanceData = [this, &renderTarget](ecs::EntityId id, Instance &instance) -> bool { - auto currentMatrix = getTransformationMatrix(id); - instance.setTransform(currentMatrix); - transformComponent->setComputedMatrix(currentMatrix); - } + bool needsUpdate = false; + auto transformComponent = getComponent(id); + auto webContentComponent = getComponent(id); - // Update for web content component - if (TR_LIKELY(webContentComponent != nullptr)) - { - needsUpdate |= instance.setEnabled(true); - needsUpdate |= instance.setOpaque(webContentComponent->isOpaque()); - needsUpdate |= instance.setRenderLayer(webContentComponent->layer()); - needsUpdate |= instance.setIsContainer(webContentComponent->isScrollableContainer()); - needsUpdate |= instance.setBelongsToContainerId(webContentComponent->belongsToScrollableContainer()); - - // Update instance render queue - auto elementComponent = getComponent(id); - if (elementComponent != nullptr && - elementComponent->node != nullptr && - elementComponent->node->isElementOrText()) - { - if (instance.setRenderQueue(elementComponent->node->computeRenderQueue())) - needsUpdate = true; - } + // Record the maybe invisible state before updating + bool maybeInvisibleBeforeUpdating = instance.maybeInvisible(); - // Update instance border data from computed style - const auto &style = webContentComponent->style(); - const auto &fragment = webContentComponent->fragment(); - const auto &borderWidth = fragment->border(); - - // Update instance basic shape data - instance.setBorderRadius(webContentComponent->borderRadius()); - instance.setDimensions(fragment->contentWidth() + borderWidth.left() + borderWidth.right(), - fragment->contentHeight() + borderWidth.top() + borderWidth.bottom()); - - // Extract border width (top, right, bottom, left) - instance.setBorderWidth(borderWidth.top(), - borderWidth.right(), - borderWidth.bottom(), - borderWidth.left()); - - // Extract border color (use top border color for now, could be extended for per-side colors) - const auto &borderColor = style.borderColor(); - const auto &topBorderColor = borderColor.top(); - if (topBorderColor.isAbsolute()) - { - SkColor skColor = topBorderColor.getAbsoluteColor(); - instance.setBorderColor(SkColorGetR(skColor) / 255.0f, - SkColorGetG(skColor) / 255.0f, - SkColorGetB(skColor) / 255.0f, - SkColorGetA(skColor) / 255.0f); - } - else + // Update for instance transformation + // TODO(yorkie): consider improving the performance of getting transformation matrix + if (TR_LIKELY(transformComponent != nullptr)) { - // Default to transparent if color is not absolute - instance.setBorderColor(0.0f, 0.0f, 0.0f, 0.0f); + auto currentMatrix = getTransformationMatrix(id); + instance.setTransform(currentMatrix); + transformComponent->setComputedMatrix(currentMatrix); } - // Extract border style (use top border style, convert to float) - // TODO(yorkie): support per-side border styles - const auto &borderStyle = style.borderStyle().top(); - uint32_t borderStyleValue = 0; // none - if (borderStyle.isSolid()) - borderStyleValue = 1; // solid - else if (borderStyle.isDashed()) - borderStyleValue = 2; // dashed - instance.setBorderStyle(borderStyleValue); - - // Update instance texture data - auto textureRect = webContentComponent->textureRect(); - if (textureRect != nullptr) + // Update for web content component + if (TR_LIKELY(webContentComponent != nullptr)) { - int texturePad = webContentComponent->texturePad(); - instance.setColor(glm::vec4(1.0f, 1.0f, 1.0f, 0.0f)); + needsUpdate |= instance.setEnabled(true); + needsUpdate |= instance.setOpaque(webContentComponent->isOpaque()); + needsUpdate |= instance.setRenderLayer(webContentComponent->layer()); + needsUpdate |= instance.setIsContainer(webContentComponent->isScrollableContainer()); + needsUpdate |= instance.setBelongsToContainerId(webContentComponent->belongsToScrollableContainer()); + + // Update instance render queue + auto elementComponent = getComponent(id); + if (elementComponent != nullptr && + elementComponent->node != nullptr && + elementComponent->node->isElementOrText()) + { + if (instance.setRenderQueue(elementComponent->node->computeRenderQueue())) + needsUpdate = true; + } - // Calculate left and right eye texture coordinates for spatial images - Instance::TextureOffset uvOffset = textureRect->getUvOffset(texturePad); - Instance::TextureOffset uvOffsetR = uvOffset; - Instance::TextureScale uvScale = textureRect->getUvScale(texturePad); + // Update instance border data from computed style + const auto &style = webContentComponent->style(); + const auto &fragment = webContentComponent->fragment(); + const auto &borderWidth = fragment->border(); + + // Update instance basic shape data + instance.setBorderRadius(webContentComponent->borderRadius()); + instance.setDimensions(fragment->contentWidth() + borderWidth.left() + borderWidth.right(), + fragment->contentHeight() + borderWidth.top() + borderWidth.bottom()); + + // Extract border width (top, right, bottom, left) + instance.setBorderWidth(borderWidth.top(), + borderWidth.right(), + borderWidth.bottom(), + borderWidth.left()); + + // Extract border color (use top border color for now, could be extended for per-side colors) + const auto &borderColor = style.borderColor(); + const auto &topBorderColor = borderColor.top(); + if (topBorderColor.isAbsolute()) + { + SkColor skColor = topBorderColor.getAbsoluteColor(); + instance.setBorderColor(SkColorGetR(skColor) / 255.0f, + SkColorGetG(skColor) / 255.0f, + SkColorGetB(skColor) / 255.0f, + SkColorGetA(skColor) / 255.0f); + } + else + { + // Default to transparent if color is not absolute + instance.setBorderColor(0.0f, 0.0f, 0.0f, 0.0f); + } - // For spatial images, the texture atlas system handles left/right eye regions - // via instance data coordinates set in setSpatialTexture method - if (webContentComponent->isSpatialized()) + // Extract border style (use top border style, convert to float) + // TODO(yorkie): support per-side border styles + const auto &borderStyle = style.borderStyle().top(); + uint32_t borderStyleValue = 0; // none + if (borderStyle.isSolid()) + borderStyleValue = 1; // solid + else if (borderStyle.isDashed()) + borderStyleValue = 2; // dashed + instance.setBorderStyle(borderStyleValue); + + // Update instance texture data + auto textureRect = webContentComponent->textureRect(); + if (textureRect != nullptr) { - assert(renderTarget != nullopt && - "The render target must be valid for spatialized images."); + int texturePad = webContentComponent->texturePad(); + instance.setColor(glm::vec4(1.0f, 1.0f, 1.0f, 0.0f)); - if (renderTarget->isMultiview()) - { - uvOffsetR.setForRight(uvScale); - uvScale.setHalfWidth(); - } - else + // Calculate left and right eye texture coordinates for spatial images + Instance::TextureOffset uvOffset = textureRect->getUvOffset(texturePad); + Instance::TextureOffset uvOffsetR = uvOffset; + Instance::TextureScale uvScale = textureRect->getUvScale(texturePad); + + // For spatial images, the texture atlas system handles left/right eye regions + // via instance data coordinates set in setSpatialTexture method + if (webContentComponent->isSpatialized()) { - auto view = renderTarget->view(); - if (view != nullptr && view->eye() == client_xr::XREye::kRight) - uvOffset.setForRight(uvScale); - uvScale.setHalfWidth(); + assert(renderTarget != nullopt && + "The render target must be valid for spatialized images."); + + if (renderTarget->isMultiview()) + { + uvOffsetR.setForRight(uvScale); + uvScale.setHalfWidth(); + } + else + { + auto view = renderTarget->view(); + if (view != nullptr && view->eye() == client_xr::XREye::kRight) + uvOffset.setForRight(uvScale); + uvScale.setHalfWidth(); + } } + instance.setTexture(uvOffset, + uvOffsetR, + uvScale, + textureRect->layer); + } + else + { + instance.setColor(webContentComponent->backgroundColor()); + instance.disableTexture(); } - instance.setTexture(uvOffset, - uvOffsetR, - uvScale, - textureRect->layer); - } - else - { - instance.setColor(webContentComponent->backgroundColor()); - instance.disableTexture(); - } - // Sync the SDF texture using state - instance.setSDFTextureEnabled(webContentComponent->isSDFTexture()); - } + // Sync the SDF texture using state + instance.setSDFTextureEnabled(webContentComponent->isSDFTexture()); + } - // When the visible state changes, we need to update the renderable list. - if (maybeInvisibleBeforeUpdating != instance.maybeInvisible()) - needsUpdate = true; + // When the visible state changes, we need to update the renderable list. + if (maybeInvisibleBeforeUpdating != instance.maybeInvisible()) + needsUpdate = true; - // Return if the instance data needs to trigger the renderable list update. - return needsUpdate; - }; - instancedMesh.iterateInstances(updateInstanceData); + // Return if the instance data needs to trigger the renderable list update. + return needsUpdate; + }; + instancedMesh.iterateInstances(updateInstanceData); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/client_renderer.hpp b/src/client/builtin_scene/client_renderer.hpp index b3e3d5f98..dd5db4dbc 100644 --- a/src/client/builtin_scene/client_renderer.hpp +++ b/src/client/builtin_scene/client_renderer.hpp @@ -10,47 +10,49 @@ #include "./renderer/render_pass.hpp" #include "./renderer/scene_renderer.hpp" -namespace builtin_scene +namespace endor { - class RenderStartupSystem final : public ecs::System + namespace builtin_scene { - using ecs::System::System; - - public: - const std::string name() const override + class RenderStartupSystem final : public ecs::System { - return "RenderStartupSystem"; - } - void onExecute() override; - }; + using ecs::System::System; - class RenderSystem final : public ecs::System - { - using ecs::System::System; + public: + const std::string name() const override + { + return "RenderStartupSystem"; + } + void onExecute() override; + }; - public: - const std::string name() const override + class RenderSystem final : public ecs::System { - return "RenderSystem"; - } - void onExecute() override; + using ecs::System::System; + + public: + const std::string name() const override + { + return "RenderSystem"; + } + void onExecute() override; - private: - /** + private: + /** * Render the scene with the given renderer. * * @param renderTarget The XR render target. */ - void render(std::optional renderTarget = std::nullopt); - /** + void render(std::optional renderTarget = std::nullopt); + /** * Render objects and instances in the given render pass. * * @param roots The root entities to traverse and render. * @param renderPass The render pass to use, which can be used to filter the objects or instances to render. * @param renderTarget The XR render target to draw to. */ - void renderPass(vector &roots, RenderPass, optional); - /** + void renderPass(vector &roots, RenderPass, optional); + /** * Render the mesh with the given renderer. * * @param entity The entity to render. @@ -59,26 +61,27 @@ namespace builtin_scene * @param renderPass The render pass to use, which can be used to filter the objects or instances to render. * @param renderTarget The XR render target. */ - void renderMesh(const ecs::EntityId &, - std::shared_ptr, - std::shared_ptr, - const RenderPass, - std::optional); + void renderMesh(const ecs::EntityId &, + std::shared_ptr, + std::shared_ptr, + const RenderPass, + std::optional); - void onBeforeRender(const RenderPass, std::optional); - void onAfterRender(const RenderPass, std::optional); + void onBeforeRender(const RenderPass, std::optional); + void onAfterRender(const RenderPass, std::optional); - // Traverse the entity hierarchy in pre-order and execute the given function for each entity. - void traverse(ecs::EntityId root, std::function &&); - size_t traverseAndUpdate(ecs::EntityId root, std::optional); + // Traverse the entity hierarchy in pre-order and execute the given function for each entity. + void traverse(ecs::EntityId root, std::function &&); + size_t traverseAndUpdate(ecs::EntityId root, std::optional); - glm::mat4 getTransformationMatrix(ecs::EntityId); - void updateInstancedMeshData(const Mesh3d &, std::optional); + glm::mat4 getTransformationMatrix(ecs::EntityId); + void updateInstancedMeshData(const Mesh3d &, std::optional); - private: - std::shared_ptr renderer_ = nullptr; - std::shared_ptr xrExperience_ = nullptr; - }; + private: + std::shared_ptr renderer_ = nullptr; + std::shared_ptr xrExperience_ = nullptr; + }; -} + } +} // namespace endor diff --git a/src/client/builtin_scene/compressed_splats.cpp b/src/client/builtin_scene/compressed_splats.cpp index 0c03c65c2..950864ca4 100644 --- a/src/client/builtin_scene/compressed_splats.cpp +++ b/src/client/builtin_scene/compressed_splats.cpp @@ -2,449 +2,452 @@ #include #include "./compressed_splats.hpp" -namespace builtin_scene::compressed_splat_utils +namespace endor { - std::array getSplatTexCoord(uint32_t splatIndex) + namespace builtin_scene::compressed_splat_utils { - uint32_t x = splatIndex & COMPRESSED_SPLAT_TEX_WIDTH_MASK; // x = index & (1024 - 1) - uint32_t y = splatIndex >> COMPRESSED_SPLAT_TEX_WIDTH_BITS; // y = index >> 10 - return {x, y}; - } - - std::array getTextureSize(uint32_t numSplats) - { - const uint32_t width = COMPRESSED_SPLAT_TEX_WIDTH; - uint32_t height = (numSplats + width - 1) / width; // Ceiling division - - // Round height up to next power of 2 for optimal GPU performance - uint32_t powerOf2Height = 1; - while (powerOf2Height < height) - powerOf2Height <<= 1; - - uint32_t maxSplats = width * powerOf2Height; - return {width, powerOf2Height, maxSplats}; - } - - // Helper functions for half-float conversion - uint16_t floatToHalf(float f) - { - // Simple float to half-float conversion - union - { - float f; - uint32_t i; - } fi = {f}; - uint32_t sign = (fi.i >> 16) & 0x8000; - int32_t exponent = ((fi.i >> 23) & 0xFF) - 127 + 15; - uint32_t mantissa = fi.i & 0x7FFFFF; - - if (exponent <= 0) + std::array getSplatTexCoord(uint32_t splatIndex) { - // Denormalized or zero - if (exponent < -10) - return (uint16_t)sign; // Zero - mantissa = (mantissa | 0x800000) >> (1 - exponent); - return (uint16_t)(sign | (mantissa >> 13)); + uint32_t x = splatIndex & COMPRESSED_SPLAT_TEX_WIDTH_MASK; // x = index & (1024 - 1) + uint32_t y = splatIndex >> COMPRESSED_SPLAT_TEX_WIDTH_BITS; // y = index >> 10 + return {x, y}; } - else if (exponent >= 31) + + std::array getTextureSize(uint32_t numSplats) { - // Infinity or NaN - return (uint16_t)(sign | 0x7C00 | ((mantissa != 0) ? 0x200 : 0)); + const uint32_t width = COMPRESSED_SPLAT_TEX_WIDTH; + uint32_t height = (numSplats + width - 1) / width; // Ceiling division + + // Round height up to next power of 2 for optimal GPU performance + uint32_t powerOf2Height = 1; + while (powerOf2Height < height) + powerOf2Height <<= 1; + + uint32_t maxSplats = width * powerOf2Height; + return {width, powerOf2Height, maxSplats}; } - else + + // Helper functions for half-float conversion + uint16_t floatToHalf(float f) { - // Normal - return (uint16_t)(sign | (exponent << 10) | (mantissa >> 13)); + // Simple float to half-float conversion + union + { + float f; + uint32_t i; + } fi = {f}; + uint32_t sign = (fi.i >> 16) & 0x8000; + int32_t exponent = ((fi.i >> 23) & 0xFF) - 127 + 15; + uint32_t mantissa = fi.i & 0x7FFFFF; + + if (exponent <= 0) + { + // Denormalized or zero + if (exponent < -10) + return (uint16_t)sign; // Zero + mantissa = (mantissa | 0x800000) >> (1 - exponent); + return (uint16_t)(sign | (mantissa >> 13)); + } + else if (exponent >= 31) + { + // Infinity or NaN + return (uint16_t)(sign | 0x7C00 | ((mantissa != 0) ? 0x200 : 0)); + } + else + { + // Normal + return (uint16_t)(sign | (exponent << 10) | (mantissa >> 13)); + } } - } - - float halfToFloat(uint16_t h) - { - // Simple half-float to float conversion - uint32_t sign = (h & 0x8000) << 16; - int32_t exponent = (h >> 10) & 0x1F; - uint32_t mantissa = h & 0x3FF; - if (exponent == 0) + float halfToFloat(uint16_t h) { - if (mantissa == 0) + // Simple half-float to float conversion + uint32_t sign = (h & 0x8000) << 16; + int32_t exponent = (h >> 10) & 0x1F; + uint32_t mantissa = h & 0x3FF; + + if (exponent == 0) { - // Zero - union + if (mantissa == 0) { - uint32_t i; - float f; - } fi = {sign}; - return fi.f; + // Zero + union + { + uint32_t i; + float f; + } fi = {sign}; + return fi.f; + } + else + { + // Denormalized + exponent = 1; + while ((mantissa & 0x400) == 0) + { + mantissa <<= 1; + exponent--; + } + mantissa &= 0x3FF; + exponent += 127 - 15; + } + } + else if (exponent == 31) + { + // Infinity or NaN + exponent = 255; } else { - // Denormalized - exponent = 1; - while ((mantissa & 0x400) == 0) - { - mantissa <<= 1; - exponent--; - } - mantissa &= 0x3FF; + // Normal exponent += 127 - 15; } + + union + { + uint32_t i; + float f; + } fi = {sign | (exponent << 23) | (mantissa << 13)}; + return fi.f; } - else if (exponent == 31) - { - // Infinity or NaN - exponent = 255; - } - else + + // Octahedral mapping functions for quaternion compression + std::array octWrap(float x, float y) { - // Normal - exponent += 127 - 15; + return { + (1.0f - std::abs(y)) * (x >= 0.0f ? 1.0f : -1.0f), + (1.0f - std::abs(x)) * (y >= 0.0f ? 1.0f : -1.0f)}; } - union + std::array quatToOct(float x, float y, float z, float w) { - uint32_t i; - float f; - } fi = {sign | (exponent << 23) | (mantissa << 13)}; - return fi.f; - } + // Normalize quaternion + float length = std::sqrt(x * x + y * y + z * z + w * w); + if (length > 0.0f) + { + x /= length; + y /= length; + z /= length; + w /= length; + } - // Octahedral mapping functions for quaternion compression - std::array octWrap(float x, float y) - { - return { - (1.0f - std::abs(y)) * (x >= 0.0f ? 1.0f : -1.0f), - (1.0f - std::abs(x)) * (y >= 0.0f ? 1.0f : -1.0f)}; - } + // Ensure w is positive + if (w < 0.0f) + { + x = -x; + y = -y; + z = -z; + w = -w; + } - std::array quatToOct(float x, float y, float z, float w) - { - // Normalize quaternion - float length = std::sqrt(x * x + y * y + z * z + w * w); - if (length > 0.0f) - { - x /= length; - y /= length; - z /= length; - w /= length; - } + // Project to octahedron + float norm = std::abs(x) + std::abs(y) + std::abs(z); + float octX = x / norm; + float octY = y / norm; - // Ensure w is positive - if (w < 0.0f) - { - x = -x; - y = -y; - z = -z; - w = -w; - } + if (z < 0.0f) + { + auto wrapped = octWrap(octX, octY); + octX = wrapped[0]; + octY = wrapped[1]; + } - // Project to octahedron - float norm = std::abs(x) + std::abs(y) + std::abs(z); - float octX = x / norm; - float octY = y / norm; + return {octX, octY}; + } - if (z < 0.0f) + std::array octToQuat(float octX, float octY) { - auto wrapped = octWrap(octX, octY); - octX = wrapped[0]; - octY = wrapped[1]; - } + float z = 1.0f - std::abs(octX) - std::abs(octY); + float x = octX; + float y = octY; - return {octX, octY}; - } + if (z < 0.0f) + { + auto wrapped = octWrap(octX, octY); + x = wrapped[0]; + y = wrapped[1]; + } - std::array octToQuat(float octX, float octY) - { - float z = 1.0f - std::abs(octX) - std::abs(octY); - float x = octX; - float y = octY; + // Normalize + float length = std::sqrt(x * x + y * y + z * z); + if (length > 0.0f) + { + x /= length; + y /= length; + z /= length; + } - if (z < 0.0f) - { - auto wrapped = octWrap(octX, octY); - x = wrapped[0]; - y = wrapped[1]; + // Reconstruct w + float w = std::sqrt(std::max(0.0f, 1.0f - (x * x + y * y + z * z))); + + return {x, y, z, w}; } - // Normalize - float length = std::sqrt(x * x + y * y + z * z); - if (length > 0.0f) + inline float clampf(float v, float lo, float hi) { - x /= length; - y /= length; - z /= length; + return std::max(lo, std::min(hi, v)); } - // Reconstruct w - float w = std::sqrt(std::max(0.0f, 1.0f - (x * x + y * y + z * z))); + uint32_t encodeQuatOctXy88R8(float x, float y, float z, float w) + { + // Normalize quaternion + const float len = std::sqrt(x * x + y * y + z * z + w * w); + if (len > 0.0f) + { + x /= len; + y /= len; + z /= len; + w /= len; + } - return {x, y, z, w}; - } + // Force minimal representation (w >= 0) + if (w < 0.0f) + { + x = -x; + y = -y; + z = -z; + w = -w; + } - inline float clampf(float v, float lo, float hi) - { - return std::max(lo, std::min(hi, v)); - } + // Compute rotation angle theta in [0, pi] + constexpr float PI = 3.14159265358979323846f; + const float w_clamped = clampf(w, -1.0f, 1.0f); + const float theta = 2.0f * std::acos(w_clamped); - uint32_t encodeQuatOctXy88R8(float x, float y, float z, float w) - { - // Normalize quaternion - const float len = std::sqrt(x * x + y * y + z * z + w * w); - if (len > 0.0f) - { - x /= len; - y /= len; - z /= len; - w /= len; - } + // Recover rotation axis (default to (1,0,0) for near-zero rotation) + const float xyz_norm = std::sqrt(x * x + y * y + z * z); + float ax, ay, az; + if (xyz_norm < 1e-6f) + { + ax = 1.0f; + ay = 0.0f; + az = 0.0f; + } + else + { + ax = x / xyz_norm; + ay = y / xyz_norm; + az = z / xyz_norm; + } - // Force minimal representation (w >= 0) - if (w < 0.0f) - { - x = -x; - y = -y; - z = -z; - w = -w; - } + // Folded Octahedral Mapping + const float sum = std::fabs(ax) + std::fabs(ay) + std::fabs(az); + float p_x = ax / sum; + float p_y = ay / sum; - // Compute rotation angle theta in [0, pi] - constexpr float PI = 3.14159265358979323846f; - const float w_clamped = clampf(w, -1.0f, 1.0f); - const float theta = 2.0f * std::acos(w_clamped); + if (az < 0.0f) + { + const float tmp = p_x; + p_x = (1.0f - std::fabs(p_y)) * (tmp >= 0.0f ? 1.0f : -1.0f); + p_y = (1.0f - std::fabs(tmp)) * (p_y >= 0.0f ? 1.0f : -1.0f); + } - // Recover rotation axis (default to (1,0,0) for near-zero rotation) - const float xyz_norm = std::sqrt(x * x + y * y + z * z); - float ax, ay, az; - if (xyz_norm < 1e-6f) - { - ax = 1.0f; - ay = 0.0f; - az = 0.0f; + // Remap from [-1,1] to [0,1] + const float u_f = p_x * 0.5f + 0.5f; + const float v_f = p_y * 0.5f + 0.5f; + + // Quantize to 8 bits (0..255) + int quantU = static_cast(std::round(u_f * 255.0f)); + int quantV = static_cast(std::round(v_f * 255.0f)); + int angleInt = static_cast(std::round(theta * (255.0f / PI))); + + // Clamp to valid range + quantU = std::min(255, std::max(0, quantU)); + quantV = std::min(255, std::max(0, quantV)); + angleInt = std::min(255, std::max(0, angleInt)); + + // Pack into 24 bits: [16–23]: angle, [8–15]: V, [0–7]: U + const uint32_t packed = (static_cast(angleInt) << 16) | + (static_cast(quantV) << 8) | + static_cast(quantU); + return packed; } - else + + float compressColor(float r, float g, float b, float a) { - ax = x / xyz_norm; - ay = y / xyz_norm; - az = z / xyz_norm; + // Clamp values to [0,1] range + r = std::max(0.0f, std::min(1.0f, r)); + g = std::max(0.0f, std::min(1.0f, g)); + b = std::max(0.0f, std::min(1.0f, b)); + a = std::max(0.0f, std::min(1.0f, a)); + + // Convert to 8-bit integers for maximum precision + uint32_t ir = (uint32_t)(r * 255.0f); + uint32_t ig = (uint32_t)(g * 255.0f); + uint32_t ib = (uint32_t)(b * 255.0f); + uint32_t ia = (uint32_t)(a * 255.0f); + + // Pack into 32-bit RGBA value + uint32_t packed = (ir) | (ig << 8) | (ib << 16) | (ia << 24); + + // Convert to float using bit reinterpretation + float result; + std::memcpy(&result, &packed, sizeof(float)); + return result; } - // Folded Octahedral Mapping - const float sum = std::fabs(ax) + std::fabs(ay) + std::fabs(az); - float p_x = ax / sum; - float p_y = ay / sum; - - if (az < 0.0f) + std::array decompressColor(float compressed) { - const float tmp = p_x; - p_x = (1.0f - std::fabs(p_y)) * (tmp >= 0.0f ? 1.0f : -1.0f); - p_y = (1.0f - std::fabs(tmp)) * (p_y >= 0.0f ? 1.0f : -1.0f); + // Extract packed value + uint32_t packed; + std::memcpy(&packed, &compressed, sizeof(uint32_t)); + + // Unpack RGBA components + uint32_t ir = packed & 0xFF; + uint32_t ig = (packed >> 8) & 0xFF; + uint32_t ib = (packed >> 16) & 0xFF; + uint32_t ia = (packed >> 24) & 0xFF; + + // Convert back to normalized float + float r = (float)ir / 255.0f; + float g = (float)ig / 255.0f; + float b = (float)ib / 255.0f; + float a = (float)ia / 255.0f; + + return {r, g, b, a}; } - // Remap from [-1,1] to [0,1] - const float u_f = p_x * 0.5f + 0.5f; - const float v_f = p_y * 0.5f + 0.5f; - - // Quantize to 8 bits (0..255) - int quantU = static_cast(std::round(u_f * 255.0f)); - int quantV = static_cast(std::round(v_f * 255.0f)); - int angleInt = static_cast(std::round(theta * (255.0f / PI))); - - // Clamp to valid range - quantU = std::min(255, std::max(0, quantU)); - quantV = std::min(255, std::max(0, quantV)); - angleInt = std::min(255, std::max(0, angleInt)); - - // Pack into 24 bits: [16–23]: angle, [8–15]: V, [0–7]: U - const uint32_t packed = (static_cast(angleInt) << 16) | - (static_cast(quantV) << 8) | - static_cast(quantU); - return packed; - } - - float compressColor(float r, float g, float b, float a) - { - // Clamp values to [0,1] range - r = std::max(0.0f, std::min(1.0f, r)); - g = std::max(0.0f, std::min(1.0f, g)); - b = std::max(0.0f, std::min(1.0f, b)); - a = std::max(0.0f, std::min(1.0f, a)); - - // Convert to 8-bit integers for maximum precision - uint32_t ir = (uint32_t)(r * 255.0f); - uint32_t ig = (uint32_t)(g * 255.0f); - uint32_t ib = (uint32_t)(b * 255.0f); - uint32_t ia = (uint32_t)(a * 255.0f); - - // Pack into 32-bit RGBA value - uint32_t packed = (ir) | (ig << 8) | (ib << 16) | (ia << 24); - - // Convert to float using bit reinterpretation - float result; - std::memcpy(&result, &packed, sizeof(float)); - return result; - } - - std::array decompressColor(float compressed) - { - // Extract packed value - uint32_t packed; - std::memcpy(&packed, &compressed, sizeof(uint32_t)); - - // Unpack RGBA components - uint32_t ir = packed & 0xFF; - uint32_t ig = (packed >> 8) & 0xFF; - uint32_t ib = (packed >> 16) & 0xFF; - uint32_t ia = (packed >> 24) & 0xFF; - - // Convert back to normalized float - float r = (float)ir / 255.0f; - float g = (float)ig / 255.0f; - float b = (float)ib / 255.0f; - float a = (float)ia / 255.0f; - - return {r, g, b, a}; - } - - std::array compressPositionHalf(float x, float y, float z) - { - // Pack position into two words using half-floats - // word0: pos.xy as half-floats (16 bits each) - // word1: pos.z as half-float in lower 16 bits + std::array compressPositionHalf(float x, float y, float z) + { + // Pack position into two words using half-floats + // word0: pos.xy as half-floats (16 bits each) + // word1: pos.z as half-float in lower 16 bits - uint16_t hx = floatToHalf(x); - uint16_t hy = floatToHalf(y); - uint16_t hz = floatToHalf(z); + uint16_t hx = floatToHalf(x); + uint16_t hy = floatToHalf(y); + uint16_t hz = floatToHalf(z); - // Pack xy into word0 - uint32_t word0 = (uint32_t)hx | ((uint32_t)hy << 16); + // Pack xy into word0 + uint32_t word0 = (uint32_t)hx | ((uint32_t)hy << 16); - // Pack z into lower 16 bits of word1 - uint32_t word1 = (uint32_t)hz; + // Pack z into lower 16 bits of word1 + uint32_t word1 = (uint32_t)hz; - // Convert to floats using bit reinterpretation - float fword0, fword1; - std::memcpy(&fword0, &word0, sizeof(float)); - std::memcpy(&fword1, &word1, sizeof(float)); + // Convert to floats using bit reinterpretation + float fword0, fword1; + std::memcpy(&fword0, &word0, sizeof(float)); + std::memcpy(&fword1, &word1, sizeof(float)); - return {fword0, fword1}; - } + return {fword0, fword1}; + } - std::array decompressPositionHalf(float word0, float word1) - { - // Extract packed values - uint32_t w0, w1; - std::memcpy(&w0, &word0, sizeof(uint32_t)); - std::memcpy(&w1, &word1, sizeof(uint32_t)); - - // Unpack half-floats - uint16_t hx = w0 & 0xFFFF; - uint16_t hy = (w0 >> 16) & 0xFFFF; - uint16_t hz = w1 & 0xFFFF; - - // Convert back to floats - float x = halfToFloat(hx); - float y = halfToFloat(hy); - float z = halfToFloat(hz); - - return {x, y, z}; - } + std::array decompressPositionHalf(float word0, float word1) + { + // Extract packed values + uint32_t w0, w1; + std::memcpy(&w0, &word0, sizeof(uint32_t)); + std::memcpy(&w1, &word1, sizeof(uint32_t)); + + // Unpack half-floats + uint16_t hx = w0 & 0xFFFF; + uint16_t hy = (w0 >> 16) & 0xFFFF; + uint16_t hz = w1 & 0xFFFF; + + // Convert back to floats + float x = halfToFloat(hx); + float y = halfToFloat(hy); + float z = halfToFloat(hz); + + return {x, y, z}; + } - glm::uvec3 compressScaleLog(float x, float y, float z, const float minLogScale[3], const float maxLogScale[3]) - { - // Apply log2 compression to scale values - float logX = (x > 0.0f) ? std::log2(x) : -10.0f; // Use -10 for very small scales - float logY = (y > 0.0f) ? std::log2(y) : -10.0f; - float logZ = (z > 0.0f) ? std::log2(z) : -10.0f; - - // Normalize to [0,1] range using provided log scale bounds - float nx = (maxLogScale[0] > minLogScale[0]) ? (logX - minLogScale[0]) / (maxLogScale[0] - minLogScale[0]) : 0.5f; - float ny = (maxLogScale[1] > minLogScale[1]) ? (logY - minLogScale[1]) / (maxLogScale[1] - minLogScale[1]) : 0.5f; - float nz = (maxLogScale[2] > minLogScale[2]) ? (logZ - minLogScale[2]) / (maxLogScale[2] - minLogScale[2]) : 0.5f; - - // Clamp to [0,1] range - nx = std::max(0.0f, std::min(1.0f, nx)); - ny = std::max(0.0f, std::min(1.0f, ny)); - nz = std::max(0.0f, std::min(1.0f, nz)); - - // Convert to 8-bit integers (stored in lower 24 bits) - uint32_t ix = (uint32_t)(nx * 255.0f); - uint32_t iy = (uint32_t)(ny * 255.0f); - uint32_t iz = (uint32_t)(nz * 255.0f); - return {ix, iy, iz}; - } + glm::uvec3 compressScaleLog(float x, float y, float z, const float minLogScale[3], const float maxLogScale[3]) + { + // Apply log2 compression to scale values + float logX = (x > 0.0f) ? std::log2(x) : -10.0f; // Use -10 for very small scales + float logY = (y > 0.0f) ? std::log2(y) : -10.0f; + float logZ = (z > 0.0f) ? std::log2(z) : -10.0f; + + // Normalize to [0,1] range using provided log scale bounds + float nx = (maxLogScale[0] > minLogScale[0]) ? (logX - minLogScale[0]) / (maxLogScale[0] - minLogScale[0]) : 0.5f; + float ny = (maxLogScale[1] > minLogScale[1]) ? (logY - minLogScale[1]) / (maxLogScale[1] - minLogScale[1]) : 0.5f; + float nz = (maxLogScale[2] > minLogScale[2]) ? (logZ - minLogScale[2]) / (maxLogScale[2] - minLogScale[2]) : 0.5f; + + // Clamp to [0,1] range + nx = std::max(0.0f, std::min(1.0f, nx)); + ny = std::max(0.0f, std::min(1.0f, ny)); + nz = std::max(0.0f, std::min(1.0f, nz)); + + // Convert to 8-bit integers (stored in lower 24 bits) + uint32_t ix = (uint32_t)(nx * 255.0f); + uint32_t iy = (uint32_t)(ny * 255.0f); + uint32_t iz = (uint32_t)(nz * 255.0f); + return {ix, iy, iz}; + } - std::array decompressScaleLog(float word2, const float minLogScale[3], const float maxLogScale[3]) - { - // Extract packed value - uint32_t packed; - std::memcpy(&packed, &word2, sizeof(uint32_t)); - - // Unpack x,y,z components from lower 24 bits (8 bits each) - uint32_t ix = packed & 0xFF; - uint32_t iy = (packed >> 8) & 0xFF; - uint32_t iz = (packed >> 16) & 0xFF; - - // Convert back to normalized float [0,1] - float nx = (float)ix / 255.0f; - float ny = (float)iy / 255.0f; - float nz = (float)iz / 255.0f; - - // Denormalize using provided log scale bounds - float logX = minLogScale[0] + nx * (maxLogScale[0] - minLogScale[0]); - float logY = minLogScale[1] + ny * (maxLogScale[1] - minLogScale[1]); - float logZ = minLogScale[2] + nz * (maxLogScale[2] - minLogScale[2]); - - // Convert back from log2 to linear scale - float x = std::exp2(logX); - float y = std::exp2(logY); - float z = std::exp2(logZ); - return {x, y, z}; - } + std::array decompressScaleLog(float word2, const float minLogScale[3], const float maxLogScale[3]) + { + // Extract packed value + uint32_t packed; + std::memcpy(&packed, &word2, sizeof(uint32_t)); + + // Unpack x,y,z components from lower 24 bits (8 bits each) + uint32_t ix = packed & 0xFF; + uint32_t iy = (packed >> 8) & 0xFF; + uint32_t iz = (packed >> 16) & 0xFF; + + // Convert back to normalized float [0,1] + float nx = (float)ix / 255.0f; + float ny = (float)iy / 255.0f; + float nz = (float)iz / 255.0f; + + // Denormalize using provided log scale bounds + float logX = minLogScale[0] + nx * (maxLogScale[0] - minLogScale[0]); + float logY = minLogScale[1] + ny * (maxLogScale[1] - minLogScale[1]); + float logZ = minLogScale[2] + nz * (maxLogScale[2] - minLogScale[2]); + + // Convert back from log2 to linear scale + float x = std::exp2(logX); + float y = std::exp2(logY); + float z = std::exp2(logZ); + return {x, y, z}; + } - CompressedSplat convertSplat( - float px, float py, float pz, // position - float sx, - float sy, - float sz, // scale - float qx, - float qy, - float qz, - float qw, // quaternion - float r, - float g, - float b, - float a, // color + opacity - const SplatNormalizationParams &normParams // normalization parameters - ) - { - CompressedSplat compressed; - - // Compress position using half-floats - auto posWords = compressPositionHalf(px, py, pz); - auto uQuat = encodeQuatOctXy88R8(qx, qy, qz, qw); - auto scaleBits = compressScaleLog(sx, sy, sz, normParams.scaleMin, normParams.scaleMax); - float colorBits = compressColor(r, g, b, a); - - auto uQuatX = uQuat & 0xFFFF; - auto uQuatY = (uQuat >> 8) & 0xFF; - auto uQuatZ = (uQuat >> 16) & 0xFF; - - uint32_t word0; - compressed.word[0] = posWords[0]; // word0: pos.x (half) + pos.y (half) - - // word1: pos.z (lower 16 bits) + quaternion upper 16 bits - uint32_t word1; - std::memcpy(&word1, &posWords[1], sizeof(float)); - word1 = (word1 & 0xFFFF) | (uQuatX << 16) | (uQuatY << 24); - std::memcpy(&compressed.word[1], &word1, sizeof(float)); - - // word2: quaternion lower 8 bits + scale.xyz (8 bits each) - uint32_t word2 = scaleBits.x | (scaleBits.y << 8) | (scaleBits.z << 16) | (uQuatZ << 24); - std::memcpy(&compressed.word[2], &word2, sizeof(float)); - - // word3: RGBA color (unchanged) - compressed.word[3] = colorBits; - return compressed; + CompressedSplat convertSplat( + float px, float py, float pz, // position + float sx, + float sy, + float sz, // scale + float qx, + float qy, + float qz, + float qw, // quaternion + float r, + float g, + float b, + float a, // color + opacity + const SplatNormalizationParams &normParams // normalization parameters + ) + { + CompressedSplat compressed; + + // Compress position using half-floats + auto posWords = compressPositionHalf(px, py, pz); + auto uQuat = encodeQuatOctXy88R8(qx, qy, qz, qw); + auto scaleBits = compressScaleLog(sx, sy, sz, normParams.scaleMin, normParams.scaleMax); + float colorBits = compressColor(r, g, b, a); + + auto uQuatX = uQuat & 0xFFFF; + auto uQuatY = (uQuat >> 8) & 0xFF; + auto uQuatZ = (uQuat >> 16) & 0xFF; + + uint32_t word0; + compressed.word[0] = posWords[0]; // word0: pos.x (half) + pos.y (half) + + // word1: pos.z (lower 16 bits) + quaternion upper 16 bits + uint32_t word1; + std::memcpy(&word1, &posWords[1], sizeof(float)); + word1 = (word1 & 0xFFFF) | (uQuatX << 16) | (uQuatY << 24); + std::memcpy(&compressed.word[1], &word1, sizeof(float)); + + // word2: quaternion lower 8 bits + scale.xyz (8 bits each) + uint32_t word2 = scaleBits.x | (scaleBits.y << 8) | (scaleBits.z << 16) | (uQuatZ << 24); + std::memcpy(&compressed.word[2], &word2, sizeof(float)); + + // word3: RGBA color (unchanged) + compressed.word[3] = colorBits; + return compressed; + } } -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/compressed_splats.hpp b/src/client/builtin_scene/compressed_splats.hpp index e5f827d86..de6e241c6 100644 --- a/src/client/builtin_scene/compressed_splats.hpp +++ b/src/client/builtin_scene/compressed_splats.hpp @@ -6,72 +6,75 @@ #include #include -namespace builtin_scene +namespace endor { - // Compressed splat storage constants (power of 2 textures) - constexpr uint32_t COMPRESSED_SPLAT_TEX_WIDTH_BITS = 10u; // 1024 width - constexpr uint32_t COMPRESSED_SPLAT_TEX_WIDTH = 1u << COMPRESSED_SPLAT_TEX_WIDTH_BITS; - constexpr uint32_t COMPRESSED_SPLAT_TEX_WIDTH_MASK = COMPRESSED_SPLAT_TEX_WIDTH - 1u; - - // Compressed splat structure: 1 texel (4 floats) per splat - // word0: pos.xy as half-floats (16 bits each) - // word1: pos.z as half-float (16 bits) + upper 16 bits of quaternion - // word2: lower 8 bits of quaternion + scale.xyz (8 bits each) - // word3: RGBA color (8 bits each) - struct CompressedSplat + namespace builtin_scene { - float word[4]; // word0, word1, word2, word3 - }; + // Compressed splat storage constants (power of 2 textures) + constexpr uint32_t COMPRESSED_SPLAT_TEX_WIDTH_BITS = 10u; // 1024 width + constexpr uint32_t COMPRESSED_SPLAT_TEX_WIDTH = 1u << COMPRESSED_SPLAT_TEX_WIDTH_BITS; + constexpr uint32_t COMPRESSED_SPLAT_TEX_WIDTH_MASK = COMPRESSED_SPLAT_TEX_WIDTH - 1u; - // Scale normalization parameters (for log-scale range) - struct SplatNormalizationParams - { - float scaleMin[3]; // Minimum log2 scale values for normalization - float scaleMax[3]; // Maximum log2 scale values for normalization - }; + // Compressed splat structure: 1 texel (4 floats) per splat + // word0: pos.xy as half-floats (16 bits each) + // word1: pos.z as half-float (16 bits) + upper 16 bits of quaternion + // word2: lower 8 bits of quaternion + scale.xyz (8 bits each) + // word3: RGBA color (8 bits each) + struct CompressedSplat + { + float word[4]; // word0, word1, word2, word3 + }; - // Utility functions for compressed splat storage - namespace compressed_splat_utils - { - // Calculate texture coordinates for a splat index using bit operations - std::array getSplatTexCoord(uint32_t splatIndex); + // Scale normalization parameters (for log-scale range) + struct SplatNormalizationParams + { + float scaleMin[3]; // Minimum log2 scale values for normalization + float scaleMax[3]; // Maximum log2 scale values for normalization + }; + + // Utility functions for compressed splat storage + namespace compressed_splat_utils + { + // Calculate texture coordinates for a splat index using bit operations + std::array getSplatTexCoord(uint32_t splatIndex); - // Calculate required texture size for given number of splats - std::array getTextureSize(uint32_t numSplats); + // Calculate required texture size for given number of splats + std::array getTextureSize(uint32_t numSplats); - // Compress RGBA color to single float using normalized encoding - float compressColor(float r, float g, float b, float a); + // Compress RGBA color to single float using normalized encoding + float compressColor(float r, float g, float b, float a); - // Decompress color from single float to RGBA - std::array decompressColor(float compressed); + // Decompress color from single float to RGBA + std::array decompressColor(float compressed); - // Compress position (x,y,z) using half-floats (no normalization needed) - std::array compressPositionHalf(float x, float y, float z); + // Compress position (x,y,z) using half-floats (no normalization needed) + std::array compressPositionHalf(float x, float y, float z); - // Decompress position from half-floats to (x,y,z) - std::array decompressPositionHalf(float word0, float word1); + // Decompress position from half-floats to (x,y,z) + std::array decompressPositionHalf(float word0, float word1); - // Compress scale (x,y,z) using log2 compression + 8-bit packing - glm::uvec3 compressScaleLog(float x, float y, float z, const float minLogScale[3], const float maxLogScale[3]); + // Compress scale (x,y,z) using log2 compression + 8-bit packing + glm::uvec3 compressScaleLog(float x, float y, float z, const float minLogScale[3], const float maxLogScale[3]); - // Decompress scale from 8-bit log values to (x,y,z) - std::array decompressScaleLog(float word2, const float minLogScale[3], const float maxLogScale[3]); + // Decompress scale from 8-bit log values to (x,y,z) + std::array decompressScaleLog(float word2, const float minLogScale[3], const float maxLogScale[3]); - // Convert from Splat struct to CompressedSplat with new format - CompressedSplat convertSplat( - float px, float py, float pz, // position - float sx, - float sy, - float sz, // scale - float qx, - float qy, - float qz, - float qw, // quaternion - float r, - float g, - float b, - float a, // color + opacity - const SplatNormalizationParams &normParams // normalization parameters - ); + // Convert from Splat struct to CompressedSplat with new format + CompressedSplat convertSplat( + float px, float py, float pz, // position + float sx, + float sy, + float sz, // scale + float qx, + float qy, + float qz, + float qw, // quaternion + float r, + float g, + float b, + float a, // color + opacity + const SplatNormalizationParams &normParams // normalization parameters + ); + } } -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/css_border_data_texture.cpp b/src/client/builtin_scene/css_border_data_texture.cpp index 63f343d05..10d14330d 100644 --- a/src/client/builtin_scene/css_border_data_texture.cpp +++ b/src/client/builtin_scene/css_border_data_texture.cpp @@ -4,189 +4,192 @@ #include "./css_border_data_texture.hpp" #include "./instanced_mesh.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace client_graphics; - - CSSBorderDataTexture::CSSBorderDataTexture() - : glContext_(nullptr) - , borderDataTexture_(nullptr) - , currentTextureHeight_(0) + namespace builtin_scene { - } - - CSSBorderDataTexture::~CSSBorderDataTexture() - { - // Texture cleanup is handled by WebGL context - } + using namespace std; + using namespace client_graphics; - bool CSSBorderDataTexture::initialize(shared_ptr glContext) - { - assert(glContext != nullptr && "WebGL2Context must not be null"); - - glContext_ = glContext; - borderDataTexture_ = glContext_->createTexture(); - assert(borderDataTexture_ && "Failed to create WebGL texture"); - - // Initialize with default size of 100 instances - currentTextureHeight_ = 100; - textureData_.resize(5 * 4 * currentTextureHeight_, 0.0f); // 5 columns × 4 components × height - - // Setup texture parameters - glContext_->activeTexture(WebGLTextureUnit::kTexture1); - glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); - - // Use nearest filtering for precise texel fetch - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureMinFilter, - WEBGL_NEAREST); - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureMagFilter, - WEBGL_NEAREST); - - // Clamp to edge to avoid sampling issues - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureWrapS, - WEBGL_CLAMP_TO_EDGE); - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureWrapT, - WEBGL_CLAMP_TO_EDGE); - - // Use texStorage2D to allocate immutable storage - glContext_->texStorage2D(WebGLTexture2DTarget::kTexture2D, - 1, // mip levels - WEBGL2_RGBA32F, // internal format - high precision float - 5, // width (5 columns) - currentTextureHeight_); // height - return true; - } - - void CSSBorderDataTexture::updateBorderData(const vector> &instances) - { - if (TR_UNLIKELY(!isInitialized())) - return; - - size_t instanceCount = instances.size(); - if (instanceCount == 0) - return; + CSSBorderDataTexture::CSSBorderDataTexture() + : glContext_(nullptr) + , borderDataTexture_(nullptr) + , currentTextureHeight_(0) + { + } - // Ensure texture is large enough - ensureTextureSize(instanceCount); + CSSBorderDataTexture::~CSSBorderDataTexture() + { + // Texture cleanup is handled by WebGL context + } - // Extract border data from instances - for (size_t i = 0; i < instanceCount; ++i) + bool CSSBorderDataTexture::initialize(shared_ptr glContext) { - const auto &instance = instances[i]; - if (!instance) - continue; + assert(glContext != nullptr && "WebGL2Context must not be null"); + + glContext_ = glContext; + borderDataTexture_ = glContext_->createTexture(); + assert(borderDataTexture_ && "Failed to create WebGL texture"); + + // Initialize with default size of 100 instances + currentTextureHeight_ = 100; + textureData_.resize(5 * 4 * currentTextureHeight_, 0.0f); // 5 columns × 4 components × height + + // Setup texture parameters + glContext_->activeTexture(WebGLTextureUnit::kTexture1); + glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); + + // Use nearest filtering for precise texel fetch + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureMinFilter, + WEBGL_NEAREST); + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureMagFilter, + WEBGL_NEAREST); + + // Clamp to edge to avoid sampling issues + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureWrapS, + WEBGL_CLAMP_TO_EDGE); + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureWrapT, + WEBGL_CLAMP_TO_EDGE); + + // Use texStorage2D to allocate immutable storage + glContext_->texStorage2D(WebGLTexture2DTarget::kTexture2D, + 1, // mip levels + WEBGL2_RGBA32F, // internal format - high precision float + 5, // width (5 columns) + currentTextureHeight_); // height + return true; + } - glm::vec4 borderWidth; - glm::vec4 borderColors[4]; - extractInstanceBorderData(*instance, borderWidth, borderColors); + void CSSBorderDataTexture::updateBorderData(const vector> &instances) + { + if (TR_UNLIKELY(!isInitialized())) + return; - // Store in texture data buffer - size_t rowOffset = i * 5 * 4; // Each row has 5 columns × 4 components + size_t instanceCount = instances.size(); + if (instanceCount == 0) + return; - // Column 0: border widths (top, right, bottom, left) - textureData_[rowOffset + 0] = borderWidth.x; // top - textureData_[rowOffset + 1] = borderWidth.y; // right - textureData_[rowOffset + 2] = borderWidth.z; // bottom - textureData_[rowOffset + 3] = borderWidth.w; // left + // Ensure texture is large enough + ensureTextureSize(instanceCount); - // Columns 1-4: border colors for each side - for (int side = 0; side < 4; ++side) + // Extract border data from instances + for (size_t i = 0; i < instanceCount; ++i) { - size_t colorOffset = rowOffset + (side + 1) * 4; - textureData_[colorOffset + 0] = borderColors[side].r; - textureData_[colorOffset + 1] = borderColors[side].g; - textureData_[colorOffset + 2] = borderColors[side].b; - textureData_[colorOffset + 3] = borderColors[side].a; + const auto &instance = instances[i]; + if (!instance) + continue; + + glm::vec4 borderWidth; + glm::vec4 borderColors[4]; + extractInstanceBorderData(*instance, borderWidth, borderColors); + + // Store in texture data buffer + size_t rowOffset = i * 5 * 4; // Each row has 5 columns × 4 components + + // Column 0: border widths (top, right, bottom, left) + textureData_[rowOffset + 0] = borderWidth.x; // top + textureData_[rowOffset + 1] = borderWidth.y; // right + textureData_[rowOffset + 2] = borderWidth.z; // bottom + textureData_[rowOffset + 3] = borderWidth.w; // left + + // Columns 1-4: border colors for each side + for (int side = 0; side < 4; ++side) + { + size_t colorOffset = rowOffset + (side + 1) * 4; + textureData_[colorOffset + 0] = borderColors[side].r; + textureData_[colorOffset + 1] = borderColors[side].g; + textureData_[colorOffset + 2] = borderColors[side].b; + textureData_[colorOffset + 3] = borderColors[side].a; + } } - } - // Upload data to GPU - glContext_->activeTexture(WebGLTextureUnit::kTexture1); - glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); - glContext_->texSubImage2D(WebGLTexture2DTarget::kTexture2D, - 0, // mip level - 0, - 0, // x, y offset - 5, // width - instanceCount, // height - WebGLTextureFormat::kRGBA, // format - WebGLPixelType::kFloat, // type - reinterpret_cast(textureData_.data())); // data - } + // Upload data to GPU + glContext_->activeTexture(WebGLTextureUnit::kTexture1); + glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); + glContext_->texSubImage2D(WebGLTexture2DTarget::kTexture2D, + 0, // mip level + 0, + 0, // x, y offset + 5, // width + instanceCount, // height + WebGLTextureFormat::kRGBA, // format + WebGLPixelType::kFloat, // type + reinterpret_cast(textureData_.data())); // data + } - void CSSBorderDataTexture::bind(WebGLTextureUnit textureUnit) - { - if (TR_UNLIKELY(!isInitialized())) - return; + void CSSBorderDataTexture::bind(WebGLTextureUnit textureUnit) + { + if (TR_UNLIKELY(!isInitialized())) + return; - glContext_->activeTexture(textureUnit); - glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); - } + glContext_->activeTexture(textureUnit); + glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); + } - void CSSBorderDataTexture::unbind(WebGLTextureUnit textureUnit) - { - if (TR_UNLIKELY(!isInitialized())) - return; + void CSSBorderDataTexture::unbind(WebGLTextureUnit textureUnit) + { + if (TR_UNLIKELY(!isInitialized())) + return; - glContext_->activeTexture(textureUnit); - glContext_->bindTexture(WebGLTextureTarget::kTexture2D, nullptr); - } + glContext_->activeTexture(textureUnit); + glContext_->bindTexture(WebGLTextureTarget::kTexture2D, nullptr); + } - void CSSBorderDataTexture::ensureTextureSize(size_t instanceCount) - { - if (instanceCount <= currentTextureHeight_) - return; - - // Resize texture data buffer - size_t newHeight = instanceCount; - textureData_.resize(5 * 4 * newHeight, 0.0f); - - // Create a new texture with the new size since texStorage2D creates immutable storage - borderDataTexture_ = glContext_->createTexture(); - - // Setup texture parameters - glContext_->activeTexture(WebGLTextureUnit::kTexture1); - glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); - - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureMinFilter, - WEBGL_NEAREST); - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureMagFilter, - WEBGL_NEAREST); - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureWrapS, - WEBGL_CLAMP_TO_EDGE); - glContext_->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureWrapT, - WEBGL_CLAMP_TO_EDGE); - - // Allocate new immutable texture storage - glContext_->texStorage2D(WebGLTexture2DTarget::kTexture2D, - 1, // mip levels - WEBGL2_RGBA32F, // internal format - 5, // width - newHeight); // height - - currentTextureHeight_ = newHeight; - } + void CSSBorderDataTexture::ensureTextureSize(size_t instanceCount) + { + if (instanceCount <= currentTextureHeight_) + return; + + // Resize texture data buffer + size_t newHeight = instanceCount; + textureData_.resize(5 * 4 * newHeight, 0.0f); + + // Create a new texture with the new size since texStorage2D creates immutable storage + borderDataTexture_ = glContext_->createTexture(); + + // Setup texture parameters + glContext_->activeTexture(WebGLTextureUnit::kTexture1); + glContext_->bindTexture(WebGLTextureTarget::kTexture2D, borderDataTexture_); + + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureMinFilter, + WEBGL_NEAREST); + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureMagFilter, + WEBGL_NEAREST); + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureWrapS, + WEBGL_CLAMP_TO_EDGE); + glContext_->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureWrapT, + WEBGL_CLAMP_TO_EDGE); + + // Allocate new immutable texture storage + glContext_->texStorage2D(WebGLTexture2DTarget::kTexture2D, + 1, // mip levels + WEBGL2_RGBA32F, // internal format + 5, // width + newHeight); // height + + currentTextureHeight_ = newHeight; + } - void CSSBorderDataTexture::extractInstanceBorderData(const Instance &instance, - glm::vec4 &borderWidth, - glm::vec4 borderColors[4]) - { - // Extract border width and colors from instance - borderWidth = instance.getBorderWidths(); - - const glm::vec4 *instanceBorderColors = instance.getBorderColors(); - borderColors[0] = instanceBorderColors[0]; - borderColors[1] = instanceBorderColors[1]; - borderColors[2] = instanceBorderColors[2]; - borderColors[3] = instanceBorderColors[3]; + void CSSBorderDataTexture::extractInstanceBorderData(const Instance &instance, + glm::vec4 &borderWidth, + glm::vec4 borderColors[4]) + { + // Extract border width and colors from instance + borderWidth = instance.getBorderWidths(); + + const glm::vec4 *instanceBorderColors = instance.getBorderColors(); + borderColors[0] = instanceBorderColors[0]; + borderColors[1] = instanceBorderColors[1]; + borderColors[2] = instanceBorderColors[2]; + borderColors[3] = instanceBorderColors[3]; + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/css_border_data_texture.hpp b/src/client/builtin_scene/css_border_data_texture.hpp index 77f19c0a2..e492912ed 100644 --- a/src/client/builtin_scene/css_border_data_texture.hpp +++ b/src/client/builtin_scene/css_border_data_texture.hpp @@ -5,12 +5,14 @@ #include #include -namespace builtin_scene +namespace endor { - // Forward declaration - class Instance; + namespace builtin_scene + { + // Forward declaration + class Instance; - /** + /** * Manages the border data texture for SDF border rendering. * Stores border widths and colors in a texture-based format to avoid UBO limits * and ensure compatibility with macOS OpenGL (which doesn't support SSBOs). @@ -19,78 +21,79 @@ namespace builtin_scene * - Column 0: border widths (top, right, bottom, left) * - Columns 1-4: border colors for each side (top, right, bottom, left) */ - class CSSBorderDataTexture - { - public: - CSSBorderDataTexture(); - ~CSSBorderDataTexture(); + class CSSBorderDataTexture + { + public: + CSSBorderDataTexture(); + ~CSSBorderDataTexture(); - /** + /** * Initialize the border data texture. * @param glContext The WebGL2 context to use for texture creation * @return true if initialization was successful, false otherwise */ - bool initialize(std::shared_ptr glContext); + bool initialize(std::shared_ptr glContext); - /** + /** * Update the border data texture with data from the given instances. * @param instances List of instances to extract border data from */ - void updateBorderData(const std::vector> &instances); + void updateBorderData(const std::vector> &instances); - /** + /** * Bind the border data texture to the specified texture unit. * @param textureUnit The texture unit to bind to (typically 1) */ - void bind(client_graphics::WebGLTextureUnit textureUnit); + void bind(client_graphics::WebGLTextureUnit textureUnit); - /** + /** * Unbind the border data texture from the specified texture unit. * @param textureUnit The texture unit to unbind from (typically 1) */ - void unbind(client_graphics::WebGLTextureUnit textureUnit); + void unbind(client_graphics::WebGLTextureUnit textureUnit); - /** + /** * Get the underlying WebGL texture. * @return The border data texture */ - std::shared_ptr getTexture() const - { - return borderDataTexture_; - } + std::shared_ptr getTexture() const + { + return borderDataTexture_; + } - /** + /** * Check if the texture has been initialized. * @return true if initialized, false otherwise */ - bool isInitialized() const - { - return borderDataTexture_ != nullptr; - } + bool isInitialized() const + { + return borderDataTexture_ != nullptr; + } - private: - /** + private: + /** * Resize the texture if needed to accommodate the given number of instances. * @param instanceCount Number of instances that need border data storage */ - void ensureTextureSize(size_t instanceCount); + void ensureTextureSize(size_t instanceCount); - /** + /** * Extract border data from a single instance. * @param instance The instance to extract border data from * @param borderWidth Output parameter for border width * @param borderColors Output parameter for border colors (4 sides) */ - void extractInstanceBorderData(const Instance &instance, - glm::vec4 &borderWidth, - glm::vec4 borderColors[4]); + void extractInstanceBorderData(const Instance &instance, + glm::vec4 &borderWidth, + glm::vec4 borderColors[4]); - private: - std::shared_ptr glContext_; - std::shared_ptr borderDataTexture_; - size_t currentTextureHeight_; + private: + std::shared_ptr glContext_; + std::shared_ptr borderDataTexture_; + size_t currentTextureHeight_; - // Buffer for texture data (5 columns × N rows of RGBA32F data) - std::vector textureData_; - }; -} + // Buffer for texture data (5 columns × N rows of RGBA32F data) + std::vector textureData_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/ecs-inl.hpp b/src/client/builtin_scene/ecs-inl.hpp index 9de4a1585..d303376a8 100644 --- a/src/client/builtin_scene/ecs-inl.hpp +++ b/src/client/builtin_scene/ecs-inl.hpp @@ -2,130 +2,93 @@ #include "./ecs.hpp" -namespace builtin_scene::ecs +namespace endor { - template - void PluginsManager::registerPlugin() + namespace builtin_scene::ecs { - static const PluginName name = typeid(T); - if (pluginIds_.find(name) != pluginIds_.end()) - throw std::runtime_error("Plugin(" + std::string(name.name()) + ") already registered."); - pluginIds_.insert({name, nextPluginId_}); - plugins_.insert({name, std::make_shared()}); - nextPluginId_ += 1; - } - - template - uint32_t PluginsManager::getPluginId() - { - static const PluginName name = typeid(T); - auto it = pluginIds_.find(name); - if (TR_UNLIKELY(it == pluginIds_.end())) - throw std::runtime_error("Plugin(" + std::string(name.name()) + ") not found."); - return it->second; - } - - template - std::shared_ptr PluginsManager::getPlugin() - { - static const PluginName name = typeid(T); - auto it = plugins_.find(name); - if (TR_UNLIKELY(it == plugins_.end())) - throw std::runtime_error("Plugin(" + std::string(name.name()) + ") not found."); - return std::static_pointer_cast(it->second); - } - - template - void ResourcesManager::addResource(std::shared_ptr resource) - { - static const ResourceName name = typeid(ResourceType); - if (resources_.find(name) != resources_.end()) - throw std::runtime_error("Resource(" + std::string(name.name()) + ") already registered."); - resources_.insert({name, resource}); - } - - template - void ResourcesManager::removeResource() - { - static const ResourceName name = typeid(ResourceType); - if (resources_.find(name) == resources_.end()) - throw std::runtime_error("Resource(" + std::string(name.name()) + ") not found."); - resources_.erase(name); - } - - template - std::shared_ptr ResourcesManager::getResource() - { - static const ResourceName name = typeid(ResourceType); - auto it = resources_.find(name); - if (TR_UNLIKELY(it == resources_.end())) - return nullptr; - return std::static_pointer_cast(it->second); - } - - template - std::shared_ptr ComponentSet::insert(EntityId entity, std::shared_ptr component) - { - std::unique_lock lock(componentMutex_); - - if (entityToIndexMap_.find(entity) != entityToIndexMap_.end()) - throw std::runtime_error("Entity already has a component of this type."); - - auto index = size_; - entityToIndexMap_[entity] = index; - indexToEntityMap_[index] = entity; - components_[index] = component; - size_ += 1; - return component; - } + template + void PluginsManager::registerPlugin() + { + static const PluginName name = typeid(T); + if (pluginIds_.find(name) != pluginIds_.end()) + throw std::runtime_error("Plugin(" + std::string(name.name()) + ") already registered."); + pluginIds_.insert({name, nextPluginId_}); + plugins_.insert({name, std::make_shared()}); + nextPluginId_ += 1; + } - template - void ComponentSet::remove(EntityId entity) - { - std::unique_lock lock(componentMutex_); + template + uint32_t PluginsManager::getPluginId() + { + static const PluginName name = typeid(T); + auto it = pluginIds_.find(name); + if (TR_UNLIKELY(it == pluginIds_.end())) + throw std::runtime_error("Plugin(" + std::string(name.name()) + ") not found."); + return it->second; + } - if (entityToIndexMap_.find(entity) == entityToIndexMap_.end()) - throw std::runtime_error("Entity does not have a component of this type."); + template + std::shared_ptr PluginsManager::getPlugin() + { + static const PluginName name = typeid(T); + auto it = plugins_.find(name); + if (TR_UNLIKELY(it == plugins_.end())) + throw std::runtime_error("Plugin(" + std::string(name.name()) + ") not found."); + return std::static_pointer_cast(it->second); + } - size_t indexToRemove = entityToIndexMap_[entity]; - size_t lastIndex = size_ - 1; - components_[indexToRemove] = components_[lastIndex]; + template + void ResourcesManager::addResource(std::shared_ptr resource) + { + static const ResourceName name = typeid(ResourceType); + if (resources_.find(name) != resources_.end()) + throw std::runtime_error("Resource(" + std::string(name.name()) + ") already registered."); + resources_.insert({name, resource}); + } - EntityId lastEntity = indexToEntityMap_[lastIndex]; - entityToIndexMap_[lastEntity] = indexToRemove; - indexToEntityMap_[indexToRemove] = lastEntity; + template + void ResourcesManager::removeResource() + { + static const ResourceName name = typeid(ResourceType); + if (resources_.find(name) == resources_.end()) + throw std::runtime_error("Resource(" + std::string(name.name()) + ") not found."); + resources_.erase(name); + } - entityToIndexMap_.erase(entity); - indexToEntityMap_.erase(lastIndex); - entityToComponentCache_.erase(entity); - size_ -= 1; - } + template + std::shared_ptr ResourcesManager::getResource() + { + static const ResourceName name = typeid(ResourceType); + auto it = resources_.find(name); + if (TR_UNLIKELY(it == resources_.end())) + return nullptr; + return std::static_pointer_cast(it->second); + } - template - std::shared_ptr ComponentSet::get(EntityId entity) - { - std::unique_lock lock(componentMutex_); + template + std::shared_ptr ComponentSet::insert(EntityId entity, std::shared_ptr component) + { + std::unique_lock lock(componentMutex_); - auto cachedComponent = entityToComponentCache_[entity]; - if (!cachedComponent.expired()) - return cachedComponent.lock(); + if (entityToIndexMap_.find(entity) != entityToIndexMap_.end()) + throw std::runtime_error("Entity already has a component of this type."); - if (entityToIndexMap_.find(entity) == entityToIndexMap_.end()) - return nullptr; + auto index = size_; + entityToIndexMap_[entity] = index; + indexToEntityMap_[index] = entity; + components_[index] = component; + size_ += 1; + return component; + } - std::shared_ptr component = components_[entityToIndexMap_[entity]]; - entityToComponentCache_[entity] = component; // TODO: support LRU cache. - return component; - } + template + void ComponentSet::remove(EntityId entity) + { + std::unique_lock lock(componentMutex_); - template - void ComponentSet::onEntityDestroyed(EntityId entity) - { - std::unique_lock lock(componentMutex_); + if (entityToIndexMap_.find(entity) == entityToIndexMap_.end()) + throw std::runtime_error("Entity does not have a component of this type."); - if (entityToIndexMap_.find(entity) != entityToIndexMap_.end()) - { - // Internal remove without additional lock (we already have it) size_t indexToRemove = entityToIndexMap_[entity]; size_t lastIndex = size_ - 1; components_[indexToRemove] = components_[lastIndex]; @@ -139,161 +102,201 @@ namespace builtin_scene::ecs entityToComponentCache_.erase(entity); size_ -= 1; } - } - template - void ComponentsManager::registerComponent() - { - static const ComponentName name = typeid(ComponentType); - if (componentIds_.find(name) != componentIds_.end()) - throw std::runtime_error("Component(" + std::string(name.name()) + ") already registered."); + template + std::shared_ptr ComponentSet::get(EntityId entity) + { + std::unique_lock lock(componentMutex_); - componentIds_.insert({name, nextComponentId_}); - componentSets_.insert({name, ComponentSet::Make()}); - nextComponentId_ += 1; - } + auto cachedComponent = entityToComponentCache_[entity]; + if (!cachedComponent.expired()) + return cachedComponent.lock(); - template - ComponentId ComponentsManager::getComponentId() - { - static const ComponentName name = typeid(ComponentType); - auto it = componentIds_.find(name); - if (it == componentIds_.end()) - throw std::runtime_error("Component(" + std::string(name.name()) + ") not found."); - return it->second; - } + if (entityToIndexMap_.find(entity) == entityToIndexMap_.end()) + return nullptr; - template - EntityId App::spawn(ComponentTypeList... components) - { - std::unique_lock lock(mutexForEntities_); + std::shared_ptr component = components_[entityToIndexMap_[entity]]; + entityToComponentCache_[entity] = component; // TODO: support LRU cache. + return component; + } - auto newEntity = std::make_shared(); - EntityId entityId = newEntity->id(); - entities_.push_back(std::make_pair(entityId, newEntity)); + template + void ComponentSet::onEntityDestroyed(EntityId entity) + { + std::unique_lock lock(componentMutex_); - // Add the components to the entity. - (componentsMgr_.addComponent(entityId, components), ...); - return entityId; - } + if (entityToIndexMap_.find(entity) != entityToIndexMap_.end()) + { + // Internal remove without additional lock (we already have it) + size_t indexToRemove = entityToIndexMap_[entity]; + size_t lastIndex = size_ - 1; + components_[indexToRemove] = components_[lastIndex]; + + EntityId lastEntity = indexToEntityMap_[lastIndex]; + entityToIndexMap_[lastEntity] = indexToRemove; + indexToEntityMap_[indexToRemove] = lastEntity; + + entityToIndexMap_.erase(entity); + indexToEntityMap_.erase(lastIndex); + entityToComponentCache_.erase(entity); + size_ -= 1; + } + } - template - std::vector App::queryEntities(std::function filter) - { - std::shared_lock lock(mutexForEntities_); - std::vector result; - auto componentSet = componentsMgr_.getComponentSet(); - for (auto &pair : entities_) + template + void ComponentsManager::registerComponent() + { + static const ComponentName name = typeid(ComponentType); + if (componentIds_.find(name) != componentIds_.end()) + throw std::runtime_error("Component(" + std::string(name.name()) + ") already registered."); + + componentIds_.insert({name, nextComponentId_}); + componentSets_.insert({name, ComponentSet::Make()}); + nextComponentId_ += 1; + } + + template + ComponentId ComponentsManager::getComponentId() { - EntityId entityId = pair.first; - if (componentSet->contains(entityId)) + static const ComponentName name = typeid(ComponentType); + auto it = componentIds_.find(name); + if (it == componentIds_.end()) + throw std::runtime_error("Component(" + std::string(name.name()) + ") not found."); + return it->second; + } + + template + EntityId App::spawn(ComponentTypeList... components) + { + std::unique_lock lock(mutexForEntities_); + + auto newEntity = std::make_shared(); + EntityId entityId = newEntity->id(); + entities_.push_back(std::make_pair(entityId, newEntity)); + + // Add the components to the entity. + (componentsMgr_.addComponent(entityId, components), ...); + return entityId; + } + + template + std::vector App::queryEntities(std::function filter) + { + std::shared_lock lock(mutexForEntities_); + std::vector result; + auto componentSet = componentsMgr_.getComponentSet(); + for (auto &pair : entities_) { - if (filter != nullptr) + EntityId entityId = pair.first; + if (componentSet->contains(entityId)) { - std::shared_ptr component = componentSet->get(entityId); - assert(component != nullptr); - if (filter(*component) == false) - continue; + if (filter != nullptr) + { + std::shared_ptr component = componentSet->get(entityId); + assert(component != nullptr); + if (filter(*component) == false) + continue; + } + result.push_back(entityId); } - result.push_back(entityId); } + return result; } - return result; - } - template - std::vector>> App::queryEntitiesWithComponent( - std::function filter) - { - std::shared_lock lock(mutexForEntities_); - std::vector>> result; - auto queryComponentSet = componentsMgr_.getComponentSet(); - auto includeComponentSet = componentsMgr_.getComponentSet(); - for (auto &pair : entities_) + template + std::vector>> App::queryEntitiesWithComponent( + std::function filter) { - EntityId entityId = pair.first; - if (queryComponentSet->contains(entityId)) + std::shared_lock lock(mutexForEntities_); + std::vector>> result; + auto queryComponentSet = componentsMgr_.getComponentSet(); + auto includeComponentSet = componentsMgr_.getComponentSet(); + for (auto &pair : entities_) { - std::shared_ptr queryComponent = queryComponentSet->get(entityId); - assert(queryComponent != nullptr); - - std::shared_ptr includeComponent; - if constexpr (std::is_same::value) - includeComponent = queryComponent; // If the types are the same, use the query component. - else - includeComponent = includeComponentSet->get(entityId); - - // Check if the entity should be included. - if (filter == nullptr || filter(*queryComponent) == true) - result.push_back(std::make_pair(entityId, includeComponent)); + EntityId entityId = pair.first; + if (queryComponentSet->contains(entityId)) + { + std::shared_ptr queryComponent = queryComponentSet->get(entityId); + assert(queryComponent != nullptr); + + std::shared_ptr includeComponent; + if constexpr (std::is_same::value) + includeComponent = queryComponent; // If the types are the same, use the query component. + else + includeComponent = includeComponentSet->get(entityId); + + // Check if the entity should be included. + if (filter == nullptr || filter(*queryComponent) == true) + result.push_back(std::make_pair(entityId, includeComponent)); + } } + return result; } - return result; - } - template - std::optional App::firstEntity() - { - std::shared_lock lock(mutexForEntities_); - std::optional result = std::nullopt; - auto componentSet = componentsMgr_.getComponentSet(); - for (auto &pair : entities_) + template + std::optional App::firstEntity() { - EntityId entityId = pair.first; - if (componentSet->contains(entityId)) + std::shared_lock lock(mutexForEntities_); + std::optional result = std::nullopt; + auto componentSet = componentsMgr_.getComponentSet(); + for (auto &pair : entities_) { - result = entityId; - break; + EntityId entityId = pair.first; + if (componentSet->contains(entityId)) + { + result = entityId; + break; + } } + return result; } - return result; - } - template - std::optional App::firstEntityWithComponent() - { - std::shared_lock lock(mutexForEntities_); - std::optional result = std::nullopt; - auto queryComponentSet = componentsMgr_.getComponentSet(); - auto includeComponentSet = componentsMgr_.getComponentSet(); - for (auto &pair : entities_) + template + std::optional App::firstEntityWithComponent() { - EntityId entityId = pair.first; - if (queryComponentSet->contains(entityId)) + std::shared_lock lock(mutexForEntities_); + std::optional result = std::nullopt; + auto queryComponentSet = componentsMgr_.getComponentSet(); + auto includeComponentSet = componentsMgr_.getComponentSet(); + for (auto &pair : entities_) { - IncludeComponentType includeComponent = includeComponentSet->get(entityId); - result = includeComponent; - break; + EntityId entityId = pair.first; + if (queryComponentSet->contains(entityId)) + { + IncludeComponentType includeComponent = includeComponentSet->get(entityId); + result = includeComponent; + break; + } } + return result; } - return result; - } - template - std::shared_ptr App::getComponent(EntityId entity) - { - std::shared_lock lock(mutexForEntities_); - return componentsMgr_.getComponent(entity); - } + template + std::shared_ptr App::getComponent(EntityId entity) + { + std::shared_lock lock(mutexForEntities_); + return componentsMgr_.getComponent(entity); + } - template - void App::addComponent(EntityId entity, ComponentTypeList... components) - { - std::unique_lock lock(mutexForEntities_); - (componentsMgr_.addComponent(entity, components), ...); - } + template + void App::addComponent(EntityId entity, ComponentTypeList... components) + { + std::unique_lock lock(mutexForEntities_); + (componentsMgr_.addComponent(entity, components), ...); + } - template - void App::removeComponent(EntityId entity, bool ignoreIfNotExists) - { - std::unique_lock lock(mutexForEntities_); - componentsMgr_.removeComponent(entity, ignoreIfNotExists); - } + template + void App::removeComponent(EntityId entity, bool ignoreIfNotExists) + { + std::unique_lock lock(mutexForEntities_); + componentsMgr_.removeComponent(entity, ignoreIfNotExists); + } - template - std::shared_ptr App::replaceComponent(EntityId entity, ComponentType component) - { - std::unique_lock lock(mutexForEntities_); - return componentsMgr_.replaceComponent(entity, component); + template + std::shared_ptr App::replaceComponent(EntityId entity, ComponentType component) + { + std::unique_lock lock(mutexForEntities_); + return componentsMgr_.replaceComponent(entity, component); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/ecs.cpp b/src/client/builtin_scene/ecs.cpp index 8c5df9ecf..d9ba405f7 100644 --- a/src/client/builtin_scene/ecs.cpp +++ b/src/client/builtin_scene/ecs.cpp @@ -5,131 +5,134 @@ #include #endif -namespace builtin_scene::ecs +namespace endor { - using namespace std; + namespace builtin_scene::ecs + { + using namespace std; #ifdef TR_ECS_ENABLE_TIME_PROFILING - using namespace std::chrono; + using namespace std::chrono; #endif - bool ISystemSet::addSystem(std::shared_ptr system) - { - systems_.push_back(system); - return true; - } + bool ISystemSet::addSystem(std::shared_ptr system) + { + systems_.push_back(system); + return true; + } - bool ISystemSet::removeSystem(SystemId id) - { - for (auto it = systems_.begin(); it != systems_.end(); ++it) + bool ISystemSet::removeSystem(SystemId id) { - if ((*it)->id() == id) + for (auto it = systems_.begin(); it != systems_.end(); ++it) { - systems_.erase(it); - return true; + if ((*it)->id() == id) + { + systems_.erase(it); + return true; + } } + return false; } - return false; - } - void ISystemSet::run() - { - for (auto &system : systems_) - system->runOnce(); - } + void ISystemSet::run() + { + for (auto &system : systems_) + system->runOnce(); + } - bool App::removeEntity(EntityId entity) - { - unique_lock lock(mutexForEntities_); + bool App::removeEntity(EntityId entity) + { + unique_lock lock(mutexForEntities_); - auto it = find_if(entities_.begin(), entities_.end(), [entity](const auto &pair) - { return pair.first == entity; }); - if (it == entities_.end()) - return false; + auto it = find_if(entities_.begin(), entities_.end(), [entity](const auto &pair) + { return pair.first == entity; }); + if (it == entities_.end()) + return false; - componentsMgr_.onEntityDestroyed(entity); - entities_.erase(it); - return true; - } + componentsMgr_.onEntityDestroyed(entity); + entities_.erase(it); + return true; + } - SystemId App::addSystem(SchedulerLabel label, std::shared_ptr system) - { - if (system == nullptr) - throw std::runtime_error("System is null."); - - std::unique_lock lock(mutexForSystems_); - if (systemSets_.find(label) == systemSets_.end()) - systemSets_[label] = std::make_shared>(); - auto systemSet = std::static_pointer_cast>(systemSets_[label]); - systemSet->addSystem(system); - system->connect(shared_from_this()); - return system->id(); - } + SystemId App::addSystem(SchedulerLabel label, std::shared_ptr system) + { + if (system == nullptr) + throw std::runtime_error("System is null."); + + std::unique_lock lock(mutexForSystems_); + if (systemSets_.find(label) == systemSets_.end()) + systemSets_[label] = std::make_shared>(); + auto systemSet = std::static_pointer_cast>(systemSets_[label]); + systemSet->addSystem(system); + system->connect(shared_from_this()); + return system->id(); + } - void App::removeSystem(SystemId id) - { - std::unique_lock lock(mutexForSystems_); - for (auto &pair : systemSets_) + void App::removeSystem(SystemId id) { - auto systemSet = pair.second; - systemSet->removeSystem(id); + std::unique_lock lock(mutexForSystems_); + for (auto &pair : systemSets_) + { + auto systemSet = pair.second; + systemSet->removeSystem(id); + } } - } - void App::startup() - { - // Build the plugins. - pluginsMgr_.build(*this); + void App::startup() + { + // Build the plugins. + pluginsMgr_.build(*this); - // Schedule the "startup" systems. - runSystems(SchedulerLabel::kPreStartup); - runSystems(SchedulerLabel::kStartup); - runSystems(SchedulerLabel::kPostStartup); - } + // Schedule the "startup" systems. + runSystems(SchedulerLabel::kPreStartup); + runSystems(SchedulerLabel::kStartup); + runSystems(SchedulerLabel::kPostStartup); + } - void App::update() - { - runSystems(SchedulerLabel::kFirst); - runSystems(SchedulerLabel::kPreUpdate); - runSystems(SchedulerLabel::kStateTransition); - runSystems(SchedulerLabel::kUpdate); - runSystems(SchedulerLabel::kPostUpdate); - runSystems(SchedulerLabel::kLast); - } + void App::update() + { + runSystems(SchedulerLabel::kFirst); + runSystems(SchedulerLabel::kPreUpdate); + runSystems(SchedulerLabel::kStateTransition); + runSystems(SchedulerLabel::kUpdate); + runSystems(SchedulerLabel::kPostUpdate); + runSystems(SchedulerLabel::kLast); + } - void App::runSystems(SchedulerLabel label) - { - shared_lock lock(mutexForSystems_); - if (systemSets_.find(label) == systemSets_.end()) - return; - auto systemSet = systemSets_[label]; - systemSet->run(); - } + void App::runSystems(SchedulerLabel label) + { + shared_lock lock(mutexForSystems_); + if (systemSets_.find(label) == systemSets_.end()) + return; + auto systemSet = systemSets_[label]; + systemSet->run(); + } - void System::runOnce() - { + void System::runOnce() + { #ifdef TR_ECS_ENABLE_TIME_PROFILING - steady_clock::time_point started = chrono::high_resolution_clock::now(); + steady_clock::time_point started = chrono::high_resolution_clock::now(); #endif - onExecute(); + onExecute(); #ifdef TR_ECS_ENABLE_TIME_PROFILING - steady_clock::time_point ended = chrono::high_resolution_clock::now(); - auto duration = chrono::duration_cast(ended - started); - cout << fixed << setprecision(3); - cout << "System(" << name() << ") took " << (duration.count() / 1000.0f) << "ms." << endl; + steady_clock::time_point ended = chrono::high_resolution_clock::now(); + auto duration = chrono::duration_cast(ended - started); + cout << fixed << setprecision(3); + cout << "System(" << name() << ") took " << (duration.count() / 1000.0f) << "ms." << endl; - // reset the format - cout.unsetf(ios::fixed); + // reset the format + cout.unsetf(ios::fixed); #endif - if (next_ != nullptr) - next_->runOnce(); - } + if (next_ != nullptr) + next_->runOnce(); + } - void System::connect(shared_ptr app) - { - connectedApp_ = app; - if (next_ != nullptr) - next_->connect(app); + void System::connect(shared_ptr app) + { + connectedApp_ = app; + if (next_ != nullptr) + next_->connect(app); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/ecs.hpp b/src/client/builtin_scene/ecs.hpp index cf85e6c85..87c9e1450 100644 --- a/src/client/builtin_scene/ecs.hpp +++ b/src/client/builtin_scene/ecs.hpp @@ -28,9 +28,11 @@ // Uncomment the following line to disable the time profiling for the ECS. #undef TR_ECS_ENABLE_TIME_PROFILING -namespace builtin_scene::ecs +namespace endor { - /** + namespace builtin_scene::ecs + { + /** * The labels used to identify the different scheduling events. * * By default, it will run the following schedules in the given order. @@ -48,121 +50,121 @@ namespace builtin_scene::ecs * 5. `kPostUpdate` * 6. `kLast` */ - enum class SchedulerLabel - { - kFirst = 0x9, - kLast, - kStartup, - kUpdate, - kPreStartup, - kPreUpdate, - kPostStartup, - kPostUpdate, - kStateTransition, - }; - - typedef uint32_t EntityId; - typedef uint32_t ComponentId; - typedef uint32_t SystemId; - typedef std::type_index ComponentName; - typedef std::type_index PluginName; - typedef std::type_index ResourceName; - - constexpr EntityId MAX_ENTITY_ID = 10 * 10000; - constexpr SystemId MAX_SYSTEM_ID = 1000; - - // Forward declarations. - class App; - class Plugin; - class Resource; - class Entity; - class Component; - class System; - - /** + enum class SchedulerLabel + { + kFirst = 0x9, + kLast, + kStartup, + kUpdate, + kPreStartup, + kPreUpdate, + kPostStartup, + kPostUpdate, + kStateTransition, + }; + + typedef uint32_t EntityId; + typedef uint32_t ComponentId; + typedef uint32_t SystemId; + typedef std::type_index ComponentName; + typedef std::type_index PluginName; + typedef std::type_index ResourceName; + + constexpr EntityId MAX_ENTITY_ID = 10 * 10000; + constexpr SystemId MAX_SYSTEM_ID = 1000; + + // Forward declarations. + class App; + class Plugin; + class Resource; + class Entity; + class Component; + class System; + + /** * The plugin is used to register some components and systems to the ECS app. */ - class Plugin - { - friend class PluginsManager; + class Plugin + { + friend class PluginsManager; - public: - Plugin() = default; - virtual ~Plugin() = default; + public: + Plugin() = default; + virtual ~Plugin() = default; - protected: - /** + protected: + /** * Implement this method to register the components and systems to the ECS app. * * @param app The ECS app to register the components and systems to. */ - virtual void build(App &app) = 0; - }; + virtual void build(App &app) = 0; + }; - /** + /** * The class for managing all plugins in the ECS. */ - class PluginsManager - { - public: - PluginsManager() = default; + class PluginsManager + { + public: + PluginsManager() = default; - public: - /** + public: + /** * Register a new plugin. * * @tparam T The type of the plugin. */ - template - void registerPlugin(); + template + void registerPlugin(); - /** + /** * Get the Id of the plugin of the given type. * * @tparam T The type of the plugin. * @returns The Id of the plugin of the given type. */ - template - uint32_t getPluginId(); + template + uint32_t getPluginId(); - /** + /** * Get the plugin of the given type. * * @tparam T The type of the plugin. * @returns The plugin of the given type. */ - template - std::shared_ptr getPlugin(); + template + std::shared_ptr getPlugin(); - /** + /** * Build all registered plugins. * * @param app The ECS app to register the components and systems to. */ - inline void build(App &app) - { - for (auto &pair : plugins_) + inline void build(App &app) { - auto plugin = pair.second; - plugin->build(app); + for (auto &pair : plugins_) + { + auto plugin = pair.second; + plugin->build(app); + } } - } - private: - std::unordered_map pluginIds_{}; - std::unordered_map> plugins_{}; - uint8_t nextPluginId_ = 0; - }; + private: + std::unordered_map pluginIds_{}; + std::unordered_map> plugins_{}; + uint8_t nextPluginId_ = 0; + }; - /** + /** * The base class for the resources which are used to store the global data such as meshes, textures, etc. */ - class Resource - { - friend class ResourcesManager; + class Resource + { + friend class ResourcesManager; - public: - /** + public: + /** * Create a new instance of the specified resource type. * * @tparam ResourceType The type of the resource. @@ -170,195 +172,195 @@ namespace builtin_scene::ecs * @param args The arguments to pass to the constructor of the resource. * @returns The new instance of the specified resource type. */ - template - static inline std::shared_ptr Make(Args... args) - { - return std::make_shared(args...); - } + template + static inline std::shared_ptr Make(Args... args) + { + return std::make_shared(args...); + } - public: - Resource() = default; - virtual ~Resource() = default; - }; + public: + Resource() = default; + virtual ~Resource() = default; + }; - /** + /** * The class for managing all resources in the ECS. */ - class ResourcesManager - { - public: - ResourcesManager() = default; + class ResourcesManager + { + public: + ResourcesManager() = default; - public: - /** + public: + /** * Add a resource to the manager. * * @tparam ResourceType The type of the resource. * @param resource The resource to add. */ - template - void addResource(std::shared_ptr resource); - /** + template + void addResource(std::shared_ptr resource); + /** * Remove a resource from the manager. * * @tparam ResourceType The type of the resource. */ - template - void removeResource(); + template + void removeResource(); - /** + /** * Get the resource of the given type. * * @tparam ResourceType The type of the resource. * @returns The resource of the given type. */ - template - std::shared_ptr getResource(); + template + std::shared_ptr getResource(); - private: - std::unordered_map> resources_{}; - }; + private: + std::unordered_map> resources_{}; + }; - /** + /** * The class for all entities in the ECS. */ - class Entity - { - public: - Entity() - : id_(idGen_.get()) + class Entity { - } - ~Entity() = default; + public: + Entity() + : id_(idGen_.get()) + { + } + ~Entity() = default; - public: - /** + public: + /** * @returns The Id of the entity. */ - inline EntityId id() - { - return id_; - } + inline EntityId id() + { + return id_; + } - private: - EntityId id_; + private: + EntityId id_; - private: - inline static thread_local TrIdGenerator idGen_ = TrIdGenerator(0x1, MAX_ENTITY_ID); - }; + private: + inline static thread_local TrIdGenerator idGen_ = TrIdGenerator(0x1, MAX_ENTITY_ID); + }; - /** + /** * The base class for all components in the ECS. * * Components are the data that is attached to entities. */ - class Component - { - public: - Component() = default; - virtual ~Component() = default; - }; + class Component + { + public: + Component() = default; + virtual ~Component() = default; + }; - /** + /** * The interface for managing a set of components. */ - class IComponentSet - { - public: - virtual ~IComponentSet() = default; + class IComponentSet + { + public: + virtual ~IComponentSet() = default; - public: - virtual void onEntityDestroyed(EntityId entity) = 0; - }; + public: + virtual void onEntityDestroyed(EntityId entity) = 0; + }; - /** + /** * The template class for managing a set of components of the given type. * * @tparam T The type of the component. */ - template - class ComponentSet : public IComponentSet - { - friend class ComponentsManager; + template + class ComponentSet : public IComponentSet + { + friend class ComponentsManager; - public: - /** + public: + /** * Create a new instance of the `ComponentSet`. * * @returns The new instance of the `ComponentSet`. */ - static inline std::shared_ptr> Make() - { - return std::make_shared(); - } - /** + static inline std::shared_ptr> Make() + { + return std::make_shared(); + } + /** * Create a new instance of the `ComponentSet` with the given components. * * @param entity The entity to attach the components to. * @param components The components to attach. * @returns The new instance of the `ComponentSet`. */ - static inline std::shared_ptr> Make(EntityId entity, std::vector &components) - { - return std::make_shared(entity, components); - } + static inline std::shared_ptr> Make(EntityId entity, std::vector &components) + { + return std::make_shared(entity, components); + } - public: - ComponentSet() - { - } - ComponentSet(EntityId entity, std::vector &components) - { - for (auto &component : components) - insert(entity, component); - } + public: + ComponentSet() + { + } + ComponentSet(EntityId entity, std::vector &components) + { + for (auto &component : components) + insert(entity, component); + } - public: - /** + public: + /** * Insert a new component for the given entity. * * @param entity The entity to attach the component to. * @param component The component to attach. */ - std::shared_ptr insert(EntityId entity, std::shared_ptr component); - /** + std::shared_ptr insert(EntityId entity, std::shared_ptr component); + /** * Insert a new component with the given arguments to construct the component, then attach it to the entity. * * @tparam InitialzingArgs The types of the arguments to construct the component. * @param entity The entity to attach the component to. * @param args The arguments to construct the component. */ - template - inline std::shared_ptr insertWithArgs(EntityId entity, InitialzingArgs... args) - { - return insert(entity, std::make_shared(args...)); - } - /** + template + inline std::shared_ptr insertWithArgs(EntityId entity, InitialzingArgs... args) + { + return insert(entity, std::make_shared(args...)); + } + /** * Remove the component of the given entity. * * @param entity The entity to remove the component from. */ - void remove(EntityId entity); - /** + void remove(EntityId entity); + /** * Get the component of the given entity. * * @param entity The entity to get the component for. * @returns The component of the given entity. * @throws std::runtime_error if the entity does not have a component of this type. */ - std::shared_ptr get(EntityId entity); - /** + std::shared_ptr get(EntityId entity); + /** * Check if the component set contains the component of the given entity. * * @param entity The entity to check. * @returns `true` if the component set contains the component of the given entity, `false` otherwise. */ - inline bool contains(EntityId entity) - { - std::shared_lock lock(componentMutex_); - return entityToIndexMap_.find(entity) != entityToIndexMap_.end(); - } - /** + inline bool contains(EntityId entity) + { + std::shared_lock lock(componentMutex_); + return entityToIndexMap_.find(entity) != entityToIndexMap_.end(); + } + /** * Replace the component of the given entity with the new component. * * If the component already exists, it will be removed and replaced with the new component. @@ -367,83 +369,83 @@ namespace builtin_scene::ecs * @param newComponent The new component to replace. * @returns The replaced component. */ - inline std::shared_ptr replace(EntityId entity, std::shared_ptr newComponent) - { - if (newComponent == nullptr) - throw std::runtime_error("The component to add is null."); - - std::unique_lock lock(componentMutex_); - - // Remove existing component if it exists - if (entityToIndexMap_.find(entity) != entityToIndexMap_.end()) + inline std::shared_ptr replace(EntityId entity, std::shared_ptr newComponent) { - size_t indexToRemove = entityToIndexMap_[entity]; - size_t lastIndex = size_ - 1; - components_[indexToRemove] = components_[lastIndex]; - - EntityId lastEntity = indexToEntityMap_[lastIndex]; - entityToIndexMap_[lastEntity] = indexToRemove; - indexToEntityMap_[indexToRemove] = lastEntity; - - entityToIndexMap_.erase(entity); - indexToEntityMap_.erase(lastIndex); - entityToComponentCache_.erase(entity); - size_ -= 1; + if (newComponent == nullptr) + throw std::runtime_error("The component to add is null."); + + std::unique_lock lock(componentMutex_); + + // Remove existing component if it exists + if (entityToIndexMap_.find(entity) != entityToIndexMap_.end()) + { + size_t indexToRemove = entityToIndexMap_[entity]; + size_t lastIndex = size_ - 1; + components_[indexToRemove] = components_[lastIndex]; + + EntityId lastEntity = indexToEntityMap_[lastIndex]; + entityToIndexMap_[lastEntity] = indexToRemove; + indexToEntityMap_[indexToRemove] = lastEntity; + + entityToIndexMap_.erase(entity); + indexToEntityMap_.erase(lastIndex); + entityToComponentCache_.erase(entity); + size_ -= 1; + } + + // Insert new component + auto index = size_; + entityToIndexMap_[entity] = index; + indexToEntityMap_[index] = entity; + components_[index] = newComponent; + size_ += 1; + + return newComponent; } - // Insert new component - auto index = size_; - entityToIndexMap_[entity] = index; - indexToEntityMap_[index] = entity; - components_[index] = newComponent; - size_ += 1; - - return newComponent; - } - - private: - void onEntityDestroyed(EntityId entity) override; - - private: - std::array, MAX_ENTITY_ID> components_; - std::unordered_map entityToIndexMap_; - std::unordered_map indexToEntityMap_; - std::unordered_map> entityToComponentCache_; - size_t size_ = 0; - // Mutex to protect concurrent access to the internal maps during parallel rendering - mutable std::shared_mutex componentMutex_; - }; - - /** + private: + void onEntityDestroyed(EntityId entity) override; + + private: + std::array, MAX_ENTITY_ID> components_; + std::unordered_map entityToIndexMap_; + std::unordered_map indexToEntityMap_; + std::unordered_map> entityToComponentCache_; + size_t size_ = 0; + // Mutex to protect concurrent access to the internal maps during parallel rendering + mutable std::shared_mutex componentMutex_; + }; + + /** * The class for managing all components in the ECS. */ - class ComponentsManager - { - public: - ComponentsManager() + class ComponentsManager { - } + public: + ComponentsManager() + { + } - public: - /** + public: + /** * Register a new component type. * * @tparam ComponentType The type of the component. * @throws std::runtime_error if the component is already registered. */ - template - void registerComponent(); + template + void registerComponent(); - /** + /** * Get the Id of the component of the given type. * * @tparam ComponentType The type of the component. * @returns The Id of the component of the given type. */ - template - ComponentId getComponentId(); + template + ComponentId getComponentId(); - /** + /** * Add the component of the given type to the given entity. * * @tparam ComponentType The type of the component. @@ -451,13 +453,13 @@ namespace builtin_scene::ecs * @param component The component to add. * @returns The added component. */ - template - inline std::shared_ptr addComponent(EntityId entity, ComponentType component) - { - return getComponentSet()->insert(entity, std::make_shared(component)); - } + template + inline std::shared_ptr addComponent(EntityId entity, ComponentType component) + { + return getComponentSet()->insert(entity, std::make_shared(component)); + } - /** + /** * Add the component of the given type to the given entity, with the provided arguments to construct the component. * * @tparam ComponentType The type of the component. @@ -466,44 +468,44 @@ namespace builtin_scene::ecs * @param args The arguments to construct the component. * @returns The added component. */ - template - inline std::shared_ptr addComponentWithArgs(EntityId entity, InitialzingArgs... args) - { - return getComponentSet()->insertWithArgs(entity, args...); - } + template + inline std::shared_ptr addComponentWithArgs(EntityId entity, InitialzingArgs... args) + { + return getComponentSet()->insertWithArgs(entity, args...); + } - /** + /** * Remove the component of the given type from the given entity. * * @tparam ComponentType The type of the component. * @param entity The entity to remove the component from. * @param ignoreIfNotExists If `true`, it will ignore if the component does not exist, otherwise it will throw an error. */ - template - inline void removeComponent(EntityId entity, bool ignoreIfNotExists = false) - { - auto componentSet = getComponentSet(); - assert(componentSet != nullptr); + template + inline void removeComponent(EntityId entity, bool ignoreIfNotExists = false) + { + auto componentSet = getComponentSet(); + assert(componentSet != nullptr); - if (ignoreIfNotExists == true && !componentSet->contains(entity)) - return; - componentSet->remove(entity); - } + if (ignoreIfNotExists == true && !componentSet->contains(entity)) + return; + componentSet->remove(entity); + } - /** + /** * Get the component of the given type for the given entity. * * @tparam ComponentType The type of the component. * @param entity The entity to get the component for. * @returns The component of the given type for the given entity. */ - template - inline std::shared_ptr getComponent(EntityId entity) - { - return getComponentSet()->get(entity); - } + template + inline std::shared_ptr getComponent(EntityId entity) + { + return getComponentSet()->get(entity); + } - /** + /** * Replace the component of the given type for the given entity. * * If the component already exists, it will be removed and replaced with the new component. @@ -513,116 +515,116 @@ namespace builtin_scene::ecs * @param component The component to replace. * @returns The replaced component. */ - template - inline std::shared_ptr replaceComponent(EntityId entity, ComponentType component) - { - return getComponentSet()->replace(entity, std::make_shared(component)); - } + template + inline std::shared_ptr replaceComponent(EntityId entity, ComponentType component) + { + return getComponentSet()->replace(entity, std::make_shared(component)); + } - /** + /** * Notify the manager that the entity has been destroyed. * * @param entity The Id of the entity that has been destroyed. */ - inline void onEntityDestroyed(EntityId entity) - { - for (auto &pair : componentSets_) + inline void onEntityDestroyed(EntityId entity) { - auto componentSet = pair.second; - componentSet->onEntityDestroyed(entity); + for (auto &pair : componentSets_) + { + auto componentSet = pair.second; + componentSet->onEntityDestroyed(entity); + } } - } - /** + /** * Get the `ComponentSet` for the given component type. * * @tparam ComponentType The type of the component. * @returns The `ComponentSet` for the given component type. */ - template - std::shared_ptr> getComponentSet() + template + std::shared_ptr> getComponentSet() + { + static const ComponentName name = typeid(ComponentType); + auto it = componentSets_.find(name); + if (TR_UNLIKELY(it == componentSets_.end())) + throw std::runtime_error("ComponentSet(" + std::string(name.name()) + ") not found."); + return std::static_pointer_cast>(it->second); + } + + private: + std::unordered_map componentIds_{}; + std::unordered_map> componentSets_{}; + ComponentId nextComponentId_ = 0; + }; + + class ISystemSet { - static const ComponentName name = typeid(ComponentType); - auto it = componentSets_.find(name); - if (TR_UNLIKELY(it == componentSets_.end())) - throw std::runtime_error("ComponentSet(" + std::string(name.name()) + ") not found."); - return std::static_pointer_cast>(it->second); - } - - private: - std::unordered_map componentIds_{}; - std::unordered_map> componentSets_{}; - ComponentId nextComponentId_ = 0; - }; - - class ISystemSet - { - public: - virtual ~ISystemSet() = default; + public: + virtual ~ISystemSet() = default; - public: - /** + public: + /** * Add a new system to the set. * * @param system The system to add. * @returns `true` if the system is added, `false` otherwise. */ - bool addSystem(std::shared_ptr system); - /** + bool addSystem(std::shared_ptr system); + /** * Remove the system of the given Id from the set. * * @param id The Id of the system to remove. * @returns `true` if the system is removed, `false` otherwise. */ - bool removeSystem(SystemId id); - /** + bool removeSystem(SystemId id); + /** * Run all systems in the set once. * * This method should be called in the main loop of the app. */ - void run(); + void run(); - protected: - std::vector> systems_; - }; + protected: + std::vector> systems_; + }; - template - class LabeledSystemSet : public ISystemSet - { - public: - LabeledSystemSet() + template + class LabeledSystemSet : public ISystemSet { - } - }; + public: + LabeledSystemSet() + { + } + }; - /** + /** * The main class for the ECS. */ - class App : public std::enable_shared_from_this - { - public: - App() + class App : public std::enable_shared_from_this { - } - virtual ~App() = default; + public: + App() + { + } + virtual ~App() = default; - public: - /** + public: + /** * Spawn a new entity with the given components. * * @param components The components to attach to the entity. * @returns The Id of the new entity. */ - template - EntityId spawn(ComponentTypeList... components); - /** + template + EntityId spawn(ComponentTypeList... components); + /** * Remove the entity from the ECS. * * @param entity The entity to remove. * @returns `true` if the entity is removed, `false` otherwise. */ - bool removeEntity(EntityId entity); - /** + bool removeEntity(EntityId entity); + /** * Query all entities with the given component type. * * @tparam ComponentType The type of the component. @@ -630,10 +632,10 @@ namespace builtin_scene::ecs * @param filter The filter to apply to the component. * @returns The list of entity Ids with the given component type. */ - template - [[nodiscard]] std::vector queryEntities( - std::function filter = nullptr); - /** + template + [[nodiscard]] std::vector queryEntities( + std::function filter = nullptr); + /** * Query all entities with the given query component type and include the include component type. * * @tparam QueryComponentType The component type to query. @@ -642,48 +644,48 @@ namespace builtin_scene::ecs * @param filter The filter to apply to the query component. * @returns The list of components of the given type. */ - template - [[nodiscard]] std::vector>> queryEntitiesWithComponent( - std::function filter = nullptr); - /** + template + [[nodiscard]] std::vector>> queryEntitiesWithComponent( + std::function filter = nullptr); + /** * Get the first entity with the given component type, or an empty optional if not found. * * @tparam ComponentType The type of the component. * @returns The Id of the first entity with the given component type. */ - template - [[nodiscard]] std::optional firstEntity(); - /** + template + [[nodiscard]] std::optional firstEntity(); + /** * Get the first entity with the given component type, or an empty optional if not found. * * @tparam QueryComponentType The type of the query component. * @tparam IncludeComponentType The type of the include component. * @returns The include component of the first entity with the query component type. */ - template - [[nodiscard]] std::optional firstEntityWithComponent(); - /** + template + [[nodiscard]] std::optional firstEntityWithComponent(); + /** * Check if the entity has the component of the given type. * * @tparam ComponentType The type of the component. * @param entity The entity to get the component for. * @returns `true` if the entity has the component of the given type, `false` otherwise. */ - template - [[nodiscard]] inline bool hasComponent(EntityId entity) - { - return getComponent(entity) != nullptr; - } - /** + template + [[nodiscard]] inline bool hasComponent(EntityId entity) + { + return getComponent(entity) != nullptr; + } + /** * Get the component of the given entity. * * @tparam ComponentType The type of the component. * @param entity The entity to get the component for. * @returns The component of the given type for the given entity or an empty optional if not found. */ - template - [[nodiscard]] std::shared_ptr getComponent(EntityId entity); - /** + template + [[nodiscard]] std::shared_ptr getComponent(EntityId entity); + /** * Get the component reference of the given entity, it will expect the entity to have the component. If you are not sure * if the entity has the component, use `getComponent` instead. * @@ -691,23 +693,23 @@ namespace builtin_scene::ecs * @param entity The entity to get the component for. * @returns The component reference of the given entity. */ - template - [[nodiscard]] ComponentType &getComponentChecked(EntityId entity) - { - auto componentRef = getComponent(entity); - assert(componentRef != nullptr && "The entity does not have the component."); - return *componentRef; - } - /** + template + [[nodiscard]] ComponentType &getComponentChecked(EntityId entity) + { + auto componentRef = getComponent(entity); + assert(componentRef != nullptr && "The entity does not have the component."); + return *componentRef; + } + /** * Add component(s) to the entity. * * @tparam ComponentTypeList The types of the components to add. * @param entity The entity to add the components to. * @param components The components to add. */ - template - void addComponent(EntityId entity, ComponentTypeList... components); - /** + template + void addComponent(EntityId entity, ComponentTypeList... components); + /** * Remove the components of the entity. * * @tparam ComponentType The type of the component. @@ -715,9 +717,9 @@ namespace builtin_scene::ecs * @param ignoreIfNotExists If `true`, it will ignore if the component does not exist, otherwise it will throw an error. * @returns The number of components removed. */ - template - void removeComponent(EntityId entity, bool ignoreIfNotExists = false); - /** + template + void removeComponent(EntityId entity, bool ignoreIfNotExists = false); + /** * Replace the current component of the entity with the new one. * * @tparam ComponentType The type of the component. @@ -725,63 +727,63 @@ namespace builtin_scene::ecs * @param component The new component to replace with. * @returns The replaced component. */ - template - std::shared_ptr replaceComponent(EntityId entity, ComponentType component); - /** + template + std::shared_ptr replaceComponent(EntityId entity, ComponentType component); + /** * Register a new component type to use in the app. * * @tparam ComponentType The type of the component. */ - template - inline void registerComponent() - { - componentsMgr_.registerComponent(); - } - /** + template + inline void registerComponent() + { + componentsMgr_.registerComponent(); + } + /** * Add a plugin to the app. * * @tparam PluginType The type of the plugin. */ - template - inline void addPlugin() - { - pluginsMgr_.registerPlugin(); - } - /** + template + inline void addPlugin() + { + pluginsMgr_.registerPlugin(); + } + /** * Add a resource to the app. * * @tparam ResourceType The type of the resource. * @param Resource The resource to add. * @returns The added resource. */ - template - inline std::shared_ptr addResource(std::shared_ptr resource) - { - resourcesMgr_.addResource(resource); - return resource; - } - /** + template + inline std::shared_ptr addResource(std::shared_ptr resource) + { + resourcesMgr_.addResource(resource); + return resource; + } + /** * Remove a resource from the app. * * @tparam ResourceType The type of the resource. */ - template - inline void removeResource() - { - resourcesMgr_.removeResource(); - } - /** + template + inline void removeResource() + { + resourcesMgr_.removeResource(); + } + /** * Get the resource of the given type. * * @tparam ResourceType The type of the resource. * @returns The resource of the given type. */ - template - inline std::shared_ptr getResource() - { - return resourcesMgr_.getResource(); - } - /** + template + inline std::shared_ptr getResource() + { + return resourcesMgr_.getResource(); + } + /** * Add a system to the ECS with the given label. * * @param label The label to use for scheduling the system. @@ -789,52 +791,52 @@ namespace builtin_scene::ecs * @returns The Id of the added system. * @throws std::runtime_error if the system is null. */ - SystemId addSystem(SchedulerLabel label, std::shared_ptr system); - /** + SystemId addSystem(SchedulerLabel label, std::shared_ptr system); + /** * Remove a system from the ECS app. * * @param id The Id of the system to remove. */ - void removeSystem(SystemId id); + void removeSystem(SystemId id); - protected: - /** + protected: + /** * The derived class should call this method when the environment is ready to start the ECS. */ - void startup(); - /** + void startup(); + /** * The derived class should call this method to update the ECS. */ - void update(); + void update(); - private: - /** + private: + /** * Run all systems with the given label. * * @param label The label to use for scheduling the systems. */ - void runSystems(SchedulerLabel label); + void runSystems(SchedulerLabel label); - private: - PluginsManager pluginsMgr_; - ResourcesManager resourcesMgr_; - ComponentsManager componentsMgr_; - std::vector>> entities_; - std::unordered_map> systemSets_{}; - // mutexes to make the ECS thread-safe. - std::shared_mutex mutexForEntities_; - std::shared_mutex mutexForSystems_; - }; + private: + PluginsManager pluginsMgr_; + ResourcesManager resourcesMgr_; + ComponentsManager componentsMgr_; + std::vector>> entities_; + std::unordered_map> systemSets_{}; + // mutexes to make the ECS thread-safe. + std::shared_mutex mutexForEntities_; + std::shared_mutex mutexForSystems_; + }; - /** + /** * The base class for all systems in the ECS. */ - class System - { - friend class App; + class System + { + friend class App; - public: - /** + public: + /** * Create a new instance of the specified system type. * * @tparam SystemType The type of the system. @@ -842,68 +844,68 @@ namespace builtin_scene::ecs * @param args The arguments to pass to the constructor of the system. * @returns The new instance of the specified system type. */ - template - static std::shared_ptr Make(Args... args) - { - return std::make_shared(args...); - } + template + static std::shared_ptr Make(Args... args) + { + return std::make_shared(args...); + } - public: - System() - : next_(nullptr) - { - } - virtual ~System() = default; + public: + System() + : next_(nullptr) + { + } + virtual ~System() = default; - public: - /** + public: + /** * @returns The name of the system. */ - virtual const std::string name() const = 0; - /** + virtual const std::string name() const = 0; + /** * This method should be implemented by the derived class to execute the system. */ - virtual void onExecute() = 0; + virtual void onExecute() = 0; - public: - /** + public: + /** * @returns The Id of the system. */ - inline SystemId id() - { - return id_; - } + inline SystemId id() + { + return id_; + } - public: - /** + public: + /** * Run the system once, it also runs the next system in the chain if it's configured. */ - void runOnce(); - /** + void runOnce(); + /** * Add a system to the chain of this system, the former will be executed after the latter. * * @param next The next system to run. * @returns The next system. */ - inline std::shared_ptr chain(std::shared_ptr next) - { - next_ = next; - return next; - } + inline std::shared_ptr chain(std::shared_ptr next) + { + next_ = next; + return next; + } - protected: // Methods for system implementations. - /** + protected: // Methods for system implementations. + /** * Spawn a new entity with the given components. * * @param components The components to attach to the entity. * @returns The Id of the new entity. */ - template - inline EntityId spawn(ComponentTypeList... components) - { - return connectedApp_->spawn(components...); - } - /** + template + inline EntityId spawn(ComponentTypeList... components) + { + return connectedApp_->spawn(components...); + } + /** * Query all entities with the given component type. * * @tparam ComponentType The type of the component. @@ -911,12 +913,12 @@ namespace builtin_scene::ecs * @param filter The filter to apply to the query. * @returns The list of entity Ids with the given component type. */ - template - inline std::vector queryEntities(std::function filter = nullptr) - { - return connectedApp_->queryEntities(filter); - } - /** + template + inline std::vector queryEntities(std::function filter = nullptr) + { + return connectedApp_->queryEntities(filter); + } + /** * Query all entities with the given query component type and include the include component type. * * @tparam QueryComponentType The component type to query. @@ -925,60 +927,60 @@ namespace builtin_scene::ecs * @param filter The filter to apply to the query component. * @returns The list of components of the given type. */ - template - inline std::vector>> queryEntitiesWithComponent( - std::function filter = nullptr) - { - return connectedApp_->queryEntitiesWithComponent(filter); - } - /** + template + inline std::vector>> queryEntitiesWithComponent( + std::function filter = nullptr) + { + return connectedApp_->queryEntitiesWithComponent(filter); + } + /** * Get the first entity with the given component type, or an empty optional if not found. * * @tparam ComponentType The type of the component. * @returns The Id of the first entity with the given component type. */ - template - inline std::optional firstEntity() - { - return connectedApp_->firstEntity(); - } - /** + template + inline std::optional firstEntity() + { + return connectedApp_->firstEntity(); + } + /** * Get the first entity with the given component type, or an empty optional if not found. * * @tparam QueryComponentType The type of the query component. * @tparam IncludeComponentType The type of the include component. * @returns The include component of the first entity with the query component type. */ - template - inline std::optional firstEntityWithComponent() - { - return connectedApp_->firstEntityWithComponent(); - } - /** + template + inline std::optional firstEntityWithComponent() + { + return connectedApp_->firstEntityWithComponent(); + } + /** * Check if the entity has the component of the given type. * * @tparam ComponentType The type of the component. * @param entity The entity to get the component for. * @returns `true` if the entity has the component of the given type, `false` otherwise. */ - template - [[nodiscard]] inline bool hasComponent(EntityId entity) - { - return connectedApp_->hasComponent(entity); - } - /** + template + [[nodiscard]] inline bool hasComponent(EntityId entity) + { + return connectedApp_->hasComponent(entity); + } + /** * Get the component of the given entity. * * @tparam ComponentType The type of the component. * @param entity The entity to get the component for. * @returns The component of the given type for the given entity or nullptr if not found. */ - template - [[nodiscard]] inline std::shared_ptr getComponent(EntityId entity) - { - return connectedApp_->getComponent(entity); - } - /** + template + [[nodiscard]] inline std::shared_ptr getComponent(EntityId entity) + { + return connectedApp_->getComponent(entity); + } + /** * Get the component reference of the given entity, it will expect the entity to have the component. If you are not sure * if the entity has the component, use `getComponent` instead. * @@ -986,35 +988,35 @@ namespace builtin_scene::ecs * @param entity The entity to get the component for. * @returns The component reference of the given entity. */ - template - [[nodiscard]] ComponentType &getComponentChecked(EntityId entity) - { - return connectedApp_->getComponentChecked(entity); - } - /** + template + [[nodiscard]] ComponentType &getComponentChecked(EntityId entity) + { + return connectedApp_->getComponentChecked(entity); + } + /** * Add component(s) to the entity. * * @tparam ComponentTypeList The types of the components to add. * @param entity The entity to add the components to. * @param components The components to add. */ - template - inline void addComponent(EntityId entity, ComponentTypeList... components) - { - (connectedApp_->addComponent(components), ...); - } - /** + template + inline void addComponent(EntityId entity, ComponentTypeList... components) + { + (connectedApp_->addComponent(components), ...); + } + /** * Remove the components of the entity. * * @tparam ComponentType The type of the component. * @param entity The entity to remove the component from. */ - template - inline void removeComponent(EntityId entity) - { - connectedApp_->removeComponent(entity); - } - /** + template + inline void removeComponent(EntityId entity) + { + connectedApp_->removeComponent(entity); + } + /** * Replace the current component of the entity with the new one. * * If the component already exists, it will be removed and replaced with the new component. @@ -1024,43 +1026,44 @@ namespace builtin_scene::ecs * @param component The new component to replace with. * @returns The replaced component. */ - template - inline std::shared_ptr replaceComponent(EntityId entity, ComponentType component) - { - return connectedApp_->replaceComponent(entity, component); - } - /** + template + inline std::shared_ptr replaceComponent(EntityId entity, ComponentType component) + { + return connectedApp_->replaceComponent(entity, component); + } + /** * Get the resource of the given type. * * @tparam ResourceType The type of the resource. * @returns The resource of the given type. */ - template - inline std::shared_ptr getResource() - { - return connectedApp_->getResource(); - } - /** + template + inline std::shared_ptr getResource() + { + return connectedApp_->getResource(); + } + /** * Get the application that this system is connected to. * * @tparam ApplicationType The type of the application. * @returns The application that this system is connected to. */ - template - std::shared_ptr getApplication() - { - return dynamic_pointer_cast(connectedApp_); - } + template + std::shared_ptr getApplication() + { + return dynamic_pointer_cast(connectedApp_); + } - private: - void connect(std::shared_ptr app); + private: + void connect(std::shared_ptr app); - private: - SystemId id_; - std::shared_ptr connectedApp_ = nullptr; - std::shared_ptr next_ = nullptr; + private: + SystemId id_; + std::shared_ptr connectedApp_ = nullptr; + std::shared_ptr next_ = nullptr; - private: - inline static thread_local TrIdGenerator idGen_ = TrIdGenerator(0, MAX_SYSTEM_ID); - }; -} + private: + inline static thread_local TrIdGenerator idGen_ = TrIdGenerator(0, MAX_SYSTEM_ID); + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/gaussian_splats_mesh.cpp b/src/client/builtin_scene/gaussian_splats_mesh.cpp index 493221df8..43ddf9011 100644 --- a/src/client/builtin_scene/gaussian_splats_mesh.cpp +++ b/src/client/builtin_scene/gaussian_splats_mesh.cpp @@ -10,265 +10,268 @@ #include "./gaussian_splats_mesh.hpp" #include "./compressed_splats.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace client_graphics; - - // Static member for empty indices - const Indices GaussianSplatsMesh::emptyIndices_; - - GaussianSplatsMesh::GaussianSplatsMesh() - : meshes::Splat() - , needsRebuild_(false) - , needsSorting_(false) - , bufferInitialized_(false) - , textureInitialized_(false) - , needsTextureUpdate_(false) + namespace builtin_scene { - } - - void GaussianSplatsMesh::addSplatsEntity(ecs::EntityId entityId) - { - // Check if entity already exists to avoid duplicates - auto it = find(splatEntities_.begin(), splatEntities_.end(), entityId); - if (it == splatEntities_.end()) + using namespace std; + using namespace client_graphics; + + // Static member for empty indices + const Indices GaussianSplatsMesh::emptyIndices_; + + GaussianSplatsMesh::GaussianSplatsMesh() + : meshes::Splat() + , needsRebuild_(false) + , needsSorting_(false) + , bufferInitialized_(false) + , textureInitialized_(false) + , needsTextureUpdate_(false) { - splatEntities_.push_back(entityId); - needsRebuild_ = true; - needsSorting_ = true; } - } - void GaussianSplatsMesh::removeSplatsEntity(ecs::EntityId entityId) - { - auto it = find(splatEntities_.begin(), splatEntities_.end(), entityId); - if (it != splatEntities_.end()) + void GaussianSplatsMesh::addSplatsEntity(ecs::EntityId entityId) { - splatEntities_.erase(it); - needsRebuild_ = true; - needsSorting_ = true; + // Check if entity already exists to avoid duplicates + auto it = find(splatEntities_.begin(), splatEntities_.end(), entityId); + if (it == splatEntities_.end()) + { + splatEntities_.push_back(entityId); + needsRebuild_ = true; + needsSorting_ = true; + } } - } - void GaussianSplatsMesh::updateSplatsEntity(ecs::EntityId entityId) - { - auto it = find(splatEntities_.begin(), splatEntities_.end(), entityId); - if (it != splatEntities_.end()) + void GaussianSplatsMesh::removeSplatsEntity(ecs::EntityId entityId) { - // Entity exists, mark for rebuild - needsRebuild_ = true; - needsSorting_ = true; + auto it = find(splatEntities_.begin(), splatEntities_.end(), entityId); + if (it != splatEntities_.end()) + { + splatEntities_.erase(it); + needsRebuild_ = true; + needsSorting_ = true; + } } - else + + void GaussianSplatsMesh::updateSplatsEntity(ecs::EntityId entityId) { - // Entity doesn't exist, add it - splatEntities_.push_back(entityId); - needsRebuild_ = true; - needsSorting_ = true; + auto it = find(splatEntities_.begin(), splatEntities_.end(), entityId); + if (it != splatEntities_.end()) + { + // Entity exists, mark for rebuild + needsRebuild_ = true; + needsSorting_ = true; + } + else + { + // Entity doesn't exist, add it + splatEntities_.push_back(entityId); + needsRebuild_ = true; + needsSorting_ = true; + } } - } - void GaussianSplatsMesh::setupSplatBuffer(shared_ptr glContext, shared_ptr vao) - { - if (!glContext || bufferInitialized_) - return; + void GaussianSplatsMesh::setupSplatBuffer(shared_ptr glContext, shared_ptr vao) + { + if (!glContext || bufferInitialized_) + return; - // Create the instance buffer for splat data - splatInstanceBuffer_ = glContext->createBuffer(); - if (splatInstanceBuffer_) [[likely]] - bufferInitialized_ = true; - } + // Create the instance buffer for splat data + splatInstanceBuffer_ = glContext->createBuffer(); + if (splatInstanceBuffer_) [[likely]] + bufferInitialized_ = true; + } - void GaussianSplatsMesh::updateSplatBuffer(shared_ptr glContext, shared_ptr vao) - { - if (!glContext || - !bufferInitialized_ || - sortedSplats_.empty() || - !isDirty()) - return; + void GaussianSplatsMesh::updateSplatBuffer(shared_ptr glContext, shared_ptr vao) + { + if (!glContext || + !bufferInitialized_ || + sortedSplats_.empty() || + !isDirty()) + return; - // Prepare a contiguous array of sorted indices (only uint32_t values now) - vector indexData; - indexData.reserve(sortedSplats_.size()); + // Prepare a contiguous array of sorted indices (only uint32_t values now) + vector indexData; + indexData.reserve(sortedSplats_.size()); - for (const auto &splat : sortedSplats_) - indexData.push_back(splat.index); + for (const auto &splat : sortedSplats_) + indexData.push_back(splat.index); - // Upload to GPU - { - WebGLVertexArrayScope vaoScope(glContext, vao); - glContext->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, splatInstanceBuffer_); - glContext->bufferData(WebGLBufferBindingTarget::kArrayBuffer, - indexData.size() * sizeof(uint32_t), - indexData.data(), - WebGLBufferUsage::kDynamicDraw); - } - setDirty(false); + // Upload to GPU + { + WebGLVertexArrayScope vaoScope(glContext, vao); + glContext->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, splatInstanceBuffer_); + glContext->bufferData(WebGLBufferBindingTarget::kArrayBuffer, + indexData.size() * sizeof(uint32_t), + indexData.data(), + WebGLBufferUsage::kDynamicDraw); + } + setDirty(false); - DEBUG("GaussianSplatsMesh", "Updated GPU buffer with %zu sorted indices", sortedSplats_.size()); - } + DEBUG("GaussianSplatsMesh", "Updated GPU buffer with %zu sorted indices", sortedSplats_.size()); + } - void GaussianSplatsMesh::updateSplatTextures(shared_ptr glContext) - { - if (!glContext || compressedSplatData_.empty()) - return; + void GaussianSplatsMesh::updateSplatTextures(shared_ptr glContext) + { + if (!glContext || compressedSplatData_.empty()) + return; - // Calculate texture dimensions using compressed method - auto textureSize = compressed_splat_utils::getTextureSize(static_cast(compressedSplatData_.size())); - uint32_t width = textureSize[0]; // 1024 - uint32_t height = textureSize[1]; // power of 2 height - uint32_t maxSplats = textureSize[2]; + // Calculate texture dimensions using compressed method + auto textureSize = compressed_splat_utils::getTextureSize(static_cast(compressedSplatData_.size())); + uint32_t width = textureSize[0]; // 1024 + uint32_t height = textureSize[1]; // power of 2 height + uint32_t maxSplats = textureSize[2]; - // Create texture2DArray if not initialized - if (!textureInitialized_) - { - compressedSplatsTexture_ = glContext->createTexture(); + // Create texture2DArray if not initialized + if (!textureInitialized_) + { + compressedSplatsTexture_ = glContext->createTexture(); - if (!compressedSplatsTexture_) - return; - textureInitialized_ = true; - } + if (!compressedSplatsTexture_) + return; + textureInitialized_ = true; + } - // Prepare texture data array for single layer - vector splatData(maxSplats * 4, 0.0f); // Single texel + // Prepare texture data array for single layer + vector splatData(maxSplats * 4, 0.0f); // Single texel - // Fill texture data from compressed splats - for (size_t i = 0; i < compressedSplatData_.size(); ++i) - { - const auto &compressed = compressedSplatData_[i]; + // Fill texture data from compressed splats + for (size_t i = 0; i < compressedSplatData_.size(); ++i) + { + const auto &compressed = compressedSplatData_[i]; - // Single texel data - splatData[i * 4 + 0] = compressed.word[0]; // compressed_pos - splatData[i * 4 + 1] = compressed.word[1]; // compressed_scale - splatData[i * 4 + 2] = compressed.word[2]; // compressed_quat - splatData[i * 4 + 3] = compressed.word[3]; // compressed_color - } + // Single texel data + splatData[i * 4 + 0] = compressed.word[0]; // compressed_pos + splatData[i * 4 + 1] = compressed.word[1]; // compressed_scale + splatData[i * 4 + 2] = compressed.word[2]; // compressed_quat + splatData[i * 4 + 3] = compressed.word[3]; // compressed_color + } - // Upload compressed texture (RGBA32F for non-Android, RGBA32UI for Android with usampler2D) - glContext->bindTexture(WebGLTextureTarget::kTexture2D, compressedSplatsTexture_); + // Upload compressed texture (RGBA32F for non-Android, RGBA32UI for Android with usampler2D) + glContext->bindTexture(WebGLTextureTarget::kTexture2D, compressedSplatsTexture_); #ifdef __ANDROID__ - glContext->texStorage2D(WebGLTexture2DTarget::kTexture2D, - 1, - WEBGL2_RGBA32UI, - width, - height); - glContext->texSubImage2D(WebGLTexture2DTarget::kTexture2D, - 0, - 0, - 0, - width, - height, - WebGLTextureFormat::kRGBAInteger, - WebGLPixelType::kUnsignedInt, - (unsigned char *)splatData.data()); + glContext->texStorage2D(WebGLTexture2DTarget::kTexture2D, + 1, + WEBGL2_RGBA32UI, + width, + height); + glContext->texSubImage2D(WebGLTexture2DTarget::kTexture2D, + 0, + 0, + 0, + width, + height, + WebGLTextureFormat::kRGBAInteger, + WebGLPixelType::kUnsignedInt, + (unsigned char *)splatData.data()); #else - glContext->texStorage2D(WebGLTexture2DTarget::kTexture2D, - 1, - WEBGL2_RGBA32F, - width, - height); - glContext->texSubImage2D(WebGLTexture2DTarget::kTexture2D, - 0, - 0, - 0, - width, - height, - WebGLTextureFormat::kRGBA, - WebGLPixelType::kFloat, - (unsigned char *)splatData.data()); + glContext->texStorage2D(WebGLTexture2DTarget::kTexture2D, + 1, + WEBGL2_RGBA32F, + width, + height); + glContext->texSubImage2D(WebGLTexture2DTarget::kTexture2D, + 0, + 0, + 0, + width, + height, + WebGLTextureFormat::kRGBA, + WebGLPixelType::kFloat, + (unsigned char *)splatData.data()); #endif - // Set texture parameters (nearest sampling for discrete data) - glContext->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureMinFilter, - WEBGL_NEAREST); - glContext->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureMagFilter, - WEBGL_NEAREST); - glContext->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureWrapS, - WEBGL_CLAMP_TO_EDGE); - glContext->texParameteri(WebGLTextureTarget::kTexture2D, - WebGLTextureParameterName::kTextureWrapT, - WEBGL_CLAMP_TO_EDGE); - - // Reset the flag since textures have been updated - needsTextureUpdate_ = false; - - DEBUG("GaussianSplatsMesh", "Updated compressed splat texture: %zu splats, %ux%u texture2D", compressedSplatData_.size(), width, height); - } + // Set texture parameters (nearest sampling for discrete data) + glContext->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureMinFilter, + WEBGL_NEAREST); + glContext->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureMagFilter, + WEBGL_NEAREST); + glContext->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureWrapS, + WEBGL_CLAMP_TO_EDGE); + glContext->texParameteri(WebGLTextureTarget::kTexture2D, + WebGLTextureParameterName::kTextureWrapT, + WEBGL_CLAMP_TO_EDGE); + + // Reset the flag since textures have been updated + needsTextureUpdate_ = false; + + DEBUG("GaussianSplatsMesh", "Updated compressed splat texture: %zu splats, %ux%u texture2D", compressedSplatData_.size(), width, height); + } - void GaussianSplatsMesh::updateSplatTexturesIfNeeded() - { - if (needsTextureUpdate_) + void GaussianSplatsMesh::updateSplatTexturesIfNeeded() { - auto glContext = glContext_.lock(); - if (glContext) + if (needsTextureUpdate_) { - updateSplatTextures(glContext); + auto glContext = glContext_.lock(); + if (glContext) + { + updateSplatTextures(glContext); + } } } - } - void GaussianSplatsMesh::onMesh3dInitialized(shared_ptr mesh3d, - shared_ptr glContext) - { - // Call parent implementation first - Mesh::onMesh3dInitialized(mesh3d, glContext); + void GaussianSplatsMesh::onMesh3dInitialized(shared_ptr mesh3d, + shared_ptr glContext) + { + // Call parent implementation first + Mesh::onMesh3dInitialized(mesh3d, glContext); - // Store the glContext for iterateInstanceAttributes - glContext_ = glContext; + // Store the glContext for iterateInstanceAttributes + glContext_ = glContext; - // Initialize the splat buffer - setupSplatBuffer(glContext, mesh3d->vertexArrayObject()); - } + // Initialize the splat buffer + setupSplatBuffer(glContext, mesh3d->vertexArrayObject()); + } - size_t GaussianSplatsMesh::iterateInstanceAttributes(shared_ptr program, - function callback) const - { - // We need a weak reference to glContext for attribute location queries - auto glContext = glContext_.lock(); - if (glContext == nullptr) - return 0; + size_t GaussianSplatsMesh::iterateInstanceAttributes(shared_ptr program, + function callback) const + { + // We need a weak reference to glContext for attribute location queries + auto glContext = glContext_.lock(); + if (glContext == nullptr) + return 0; - size_t attribsCount = 0; - size_t offset = 0; + size_t attribsCount = 0; + size_t offset = 0; - for (size_t i = 0; i < INSTANCE_ATTRIBUTES.size(); i++) - { - auto &name = INSTANCE_ATTRIBUTES[i]; - auto attribLocation = glContext->getAttribLocation(program, name); - if (attribLocation.has_value()) + for (size_t i = 0; i < INSTANCE_ATTRIBUTES.size(); i++) { - auto instanceIndex = attribLocation.value().index.value_or(-1); - unique_ptr attrib = nullptr; - - // Configure based on attribute name and type - if (name == "splatIndex") + auto &name = INSTANCE_ATTRIBUTES[i]; + auto attribLocation = glContext->getAttribLocation(program, name); + if (attribLocation.has_value()) { - // uint32 attribute for texture index - attrib = make_unique>(name, instanceIndex, VertexFormat::kUint32); + auto instanceIndex = attribLocation.value().index.value_or(-1); + unique_ptr attrib = nullptr; + + // Configure based on attribute name and type + if (name == "splatIndex") + { + // uint32 attribute for texture index + attrib = make_unique>(name, instanceIndex, VertexFormat::kUint32); + } + else + { + assert(false && "Unknown splat instance attribute name."); + } + + assert(attrib != nullptr); + callback(*attrib, instanceIndex, STRIDE, offset); + offset += attrib->byteLength(); + attribsCount += 1; } else { - assert(false && "Unknown splat instance attribute name."); + cerr << "The splat instance attribute " << name << " is not found." << endl; } - - assert(attrib != nullptr); - callback(*attrib, instanceIndex, STRIDE, offset); - offset += attrib->byteLength(); - attribsCount += 1; - } - else - { - cerr << "The splat instance attribute " << name << " is not found." << endl; } - } - return attribsCount; + return attribsCount; + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/gaussian_splats_mesh.hpp b/src/client/builtin_scene/gaussian_splats_mesh.hpp index eda73062f..ec5ccc673 100644 --- a/src/client/builtin_scene/gaussian_splats_mesh.hpp +++ b/src/client/builtin_scene/gaussian_splats_mesh.hpp @@ -16,33 +16,35 @@ #include "./meshes/splat.hpp" #include "./compressed_splats.hpp" -namespace builtin_scene +namespace endor { - /** + namespace builtin_scene + { + /** * Data structure for a single splat instance in the global rendering system. * Now only contains sorting-related data and a reference to texture data. */ - struct SplatInstanceData - { - uint32_t index; // Index into the packed splat texture (changed to uint32_t) - float depth; // For sorting (not uploaded to GPU) - ecs::EntityId sourceEntity; // Not uploaded to GPU - - SplatInstanceData() - : index(0) - , depth(0.0f) - , sourceEntity(0) + struct SplatInstanceData { - } + uint32_t index; // Index into the packed splat texture (changed to uint32_t) + float depth; // For sorting (not uploaded to GPU) + ecs::EntityId sourceEntity; // Not uploaded to GPU + + SplatInstanceData() + : index(0) + , depth(0.0f) + , sourceEntity(0) + { + } - // Get the size of GPU-uploadable data (only the sorted index) - static constexpr size_t getGPUDataSize() - { - return sizeof(uint32_t); - } - }; + // Get the size of GPU-uploadable data (only the sorted index) + static constexpr size_t getGPUDataSize() + { + return sizeof(uint32_t); + } + }; - /** + /** * Global mesh for rendering all Gaussian splats in the scene. * This class manages entity references for all model entities with splats, * handles sorting, and performs instanced rendering with the base quad geometry. @@ -50,341 +52,341 @@ namespace builtin_scene * Uses compressed splat storage with texture2D for optimal GPU performance. * Each splat is stored in 1 texel (4 floats) instead of 2 texels (8 floats). */ - class GaussianSplatsMesh : public meshes::Splat - { - public: - // Splat instance attributes layout for GPU buffer (only sorted index now) - static constexpr size_t STRIDE = SplatInstanceData::getGPUDataSize(); - static inline std::vector INSTANCE_ATTRIBUTES = { - "splatIndex" // uint (sorted index into packed splat data texture array) - }; - - public: - GaussianSplatsMesh(); - virtual ~GaussianSplatsMesh() = default; - - public: - /** + class GaussianSplatsMesh : public meshes::Splat + { + public: + // Splat instance attributes layout for GPU buffer (only sorted index now) + static constexpr size_t STRIDE = SplatInstanceData::getGPUDataSize(); + static inline std::vector INSTANCE_ATTRIBUTES = { + "splatIndex" // uint (sorted index into packed splat data texture array) + }; + + public: + GaussianSplatsMesh(); + virtual ~GaussianSplatsMesh() = default; + + public: + /** * Add an entity with splats to the global rendering system. */ - void addSplatsEntity(ecs::EntityId entityId); + void addSplatsEntity(ecs::EntityId entityId); - /** + /** * Remove an entity from the global rendering system. */ - void removeSplatsEntity(ecs::EntityId entityId); + void removeSplatsEntity(ecs::EntityId entityId); - /** + /** * Update entities that have splats for rendering. */ - void updateSplatsEntity(ecs::EntityId entityId); + void updateSplatsEntity(ecs::EntityId entityId); - /** + /** * Sort all splats by depth for proper transparency rendering. * This should be called each frame before rendering. * Takes an ECS query function to access GaussianSplattingModel3d components. */ - template - void sortSplatsByDepth(const glm::mat4 &viewMatrix, QueryFunc getComponent) - { - if (needsRebuild_) + template + void sortSplatsByDepth(const glm::mat4 &viewMatrix, QueryFunc getComponent) { - rebuildSortedSplats(getComponent); - // Update texture after rebuilding splat data - updateSplatTexturesIfNeeded(); - } + if (needsRebuild_) + { + rebuildSortedSplats(getComponent); + // Update texture after rebuilding splat data + updateSplatTexturesIfNeeded(); + } - if (!needsSorting_) - { - return; - } + if (!needsSorting_) + { + return; + } - // Calculate depth for each splat using compressed splat data - for (auto &splat : sortedSplats_) - { - // Get position from compressed splat data to calculate depth - uint32_t compressedIndex = splat.index; - if (compressedIndex < compressedSplatData_.size()) + // Calculate depth for each splat using compressed splat data + for (auto &splat : sortedSplats_) { - const auto &compressed = compressedSplatData_[compressedIndex]; + // Get position from compressed splat data to calculate depth + uint32_t compressedIndex = splat.index; + if (compressedIndex < compressedSplatData_.size()) + { + const auto &compressed = compressedSplatData_[compressedIndex]; - // Use compressed position data for depth calculation (word0 and word1 contain half-float position) - auto pos = compressed_splat_utils::decompressPositionHalf(compressed.word[0], compressed.word[1]); - glm::vec3 position(pos[0], pos[1], pos[2]); + // Use compressed position data for depth calculation (word0 and word1 contain half-float position) + auto pos = compressed_splat_utils::decompressPositionHalf(compressed.word[0], compressed.word[1]); + glm::vec3 position(pos[0], pos[1], pos[2]); - glm::vec4 viewPos = viewMatrix * glm::vec4(position, 1.0f); - splat.depth = -viewPos.z; // Depth in view space - } - else - { - splat.depth = 0.0f; // Invalid splat + glm::vec4 viewPos = viewMatrix * glm::vec4(position, 1.0f); + splat.depth = -viewPos.z; // Depth in view space + } + else + { + splat.depth = 0.0f; // Invalid splat + } } - } - // Sort by depth (back to front for transparency blending) - // This reorders the sorted indices, not the texture data - std::sort(sortedSplats_.begin(), sortedSplats_.end(), [](const SplatInstanceData &a, const SplatInstanceData &b) - { - return a.depth > b.depth; // Back to front - }); + // Sort by depth (back to front for transparency blending) + // This reorders the sorted indices, not the texture data + std::sort(sortedSplats_.begin(), sortedSplats_.end(), [](const SplatInstanceData &a, const SplatInstanceData &b) + { + return a.depth > b.depth; // Back to front }); - needsSorting_ = false; + needsSorting_ = false; - // Mark the mesh as dirty so the GPU buffer gets updated with new sorting - setDirty(true); - } + // Mark the mesh as dirty so the GPU buffer gets updated with new sorting + setDirty(true); + } - /** + /** * Get all splat instances for rendering. */ - inline const std::vector &getSplatInstances() const - { - return sortedSplats_; - } + inline const std::vector &getSplatInstances() const + { + return sortedSplats_; + } - /** + /** * Get the total number of splats across all models. */ - inline size_t getTotalSplatCount() const - { - return sortedSplats_.size(); - } + inline size_t getTotalSplatCount() const + { + return sortedSplats_.size(); + } - /** + /** * Check if any splats need to be re-sorted. */ - inline bool needsSorting() const - { - return needsSorting_; - } + inline bool needsSorting() const + { + return needsSorting_; + } - /** + /** * Mark that splats need to be re-sorted (called when camera moves). */ - inline void markNeedsSorting() - { - needsSorting_ = true; - } + inline void markNeedsSorting() + { + needsSorting_ = true; + } - /** + /** * Check if geometry is initialized. */ - inline bool isGeometryInitialized() const - { - return !isDirty(); - } + inline bool isGeometryInitialized() const + { + return !isDirty(); + } - /** + /** * Setup the splat buffer and vertex attributes for instanced rendering. * Should be called when the mesh is initialized with WebGL context. */ - void setupSplatBuffer(std::shared_ptr glContext, - std::shared_ptr vao); + void setupSplatBuffer(std::shared_ptr glContext, + std::shared_ptr vao); - /** + /** * Update the splat instance buffer with current splat indices. * This uploads only the sorted indices to GPU for rendering. */ - void updateSplatBuffer(std::shared_ptr glContext, - std::shared_ptr vao); + void updateSplatBuffer(std::shared_ptr glContext, + std::shared_ptr vao); - /** + /** * Update the splat data textures with all splat properties. * This uploads compressed splat data to a texture2DArray for shader access. */ - void updateSplatTextures(std::shared_ptr glContext); + void updateSplatTextures(std::shared_ptr glContext); - /** + /** * Update splat textures if needed based on flags. */ - void updateSplatTexturesIfNeeded(); + void updateSplatTexturesIfNeeded(); - /** + /** * Get the compressed splat texture array containing all splat properties. */ - inline std::shared_ptr getCompressedSplatsTexture() const - { - return compressedSplatsTexture_; - } + inline std::shared_ptr getCompressedSplatsTexture() const + { + return compressedSplatsTexture_; + } - /** + /** * Get the total number of compressed splats stored in the texture array. */ - inline size_t getTotalCompressedSplats() const - { - return compressedSplatData_.size(); - } + inline size_t getTotalCompressedSplats() const + { + return compressedSplatData_.size(); + } - /** + /** * Get the normalization parameters for position and scale compression. */ - inline const SplatNormalizationParams &getNormalizationParams() const - { - return normalizationParams_; - } + inline const SplatNormalizationParams &getNormalizationParams() const + { + return normalizationParams_; + } - /** + /** * Get the splat instance buffer for attribute configuration. */ - inline std::shared_ptr getSplatInstanceBuffer() const - { - return splatInstanceBuffer_; - } + inline std::shared_ptr getSplatInstanceBuffer() const + { + return splatInstanceBuffer_; + } - /** + /** * Initialize the splat buffer when the mesh is set up with WebGL context. */ - void onMesh3dInitialized(std::shared_ptr mesh3d, - std::shared_ptr glContext) override; + void onMesh3dInitialized(std::shared_ptr mesh3d, + std::shared_ptr glContext) override; - /** + /** * Iterate over instance attributes for proper configuration. * This follows the same pattern as InstancedMesh for standard attribute setup. */ - size_t iterateInstanceAttributes(std::shared_ptr program, - std::function callback) const; - - private: - /** + size_t iterateInstanceAttributes(std::shared_ptr program, + std::function callback) const; + + private: + /** * Rebuild the sorted splats list from all entity splats. * This rebuilds both the compressed texture data and the sorted indices. */ - template - void rebuildSortedSplats(QueryFunc getComponent) - { - compressedSplatData_.clear(); - sortedSplats_.clear(); + template + void rebuildSortedSplats(QueryFunc getComponent) + { + compressedSplatData_.clear(); + sortedSplats_.clear(); - // Collect all splats from all entities and compute bounds for normalization - std::vector allSplats; + // Collect all splats from all entities and compute bounds for normalization + std::vector allSplats; - for (ecs::EntityId entityId : splatEntities_) - { - auto *model = getComponent(entityId); - if (model && model->isLoaded() && model->visible()) - { - const auto &splats = model->getSplats(); - allSplats.insert(allSplats.end(), splats.begin(), splats.end()); - } - } + for (ecs::EntityId entityId : splatEntities_) + { + auto *model = getComponent(entityId); + if (model && model->isLoaded() && model->visible()) + { + const auto &splats = model->getSplats(); + allSplats.insert(allSplats.end(), splats.begin(), splats.end()); + } + } - if (allSplats.empty()) - { - needsRebuild_ = false; - needsSorting_ = false; - needsTextureUpdate_ = true; - setDirty(true); - return; - } + if (allSplats.empty()) + { + needsRebuild_ = false; + needsSorting_ = false; + needsTextureUpdate_ = true; + setDirty(true); + return; + } - // Compute scale bounds for log compression (no position bounds needed for half-floats) - float scaleMin[3] = {std::log2(std::max(0.001f, allSplats[0].scale[0])), - std::log2(std::max(0.001f, allSplats[0].scale[1])), - std::log2(std::max(0.001f, allSplats[0].scale[2]))}; - float scaleMax[3] = {scaleMin[0], scaleMin[1], scaleMin[2]}; + // Compute scale bounds for log compression (no position bounds needed for half-floats) + float scaleMin[3] = {std::log2(std::max(0.001f, allSplats[0].scale[0])), + std::log2(std::max(0.001f, allSplats[0].scale[1])), + std::log2(std::max(0.001f, allSplats[0].scale[2]))}; + float scaleMax[3] = {scaleMin[0], scaleMin[1], scaleMin[2]}; - for (const auto &splat : allSplats) - { - // Update scale bounds (log2 space) - no position bounds needed for half-floats - for (int i = 0; i < 3; i++) - { - float logScale = std::log2(std::max(0.001f, splat.scale[i])); - scaleMin[i] = std::min(scaleMin[i], logScale); - scaleMax[i] = std::max(scaleMax[i], logScale); - } - } + for (const auto &splat : allSplats) + { + // Update scale bounds (log2 space) - no position bounds needed for half-floats + for (int i = 0; i < 3; i++) + { + float logScale = std::log2(std::max(0.001f, splat.scale[i])); + scaleMin[i] = std::min(scaleMin[i], logScale); + scaleMax[i] = std::max(scaleMax[i], logScale); + } + } - // Store normalization parameters (only scale bounds needed) - for (int i = 0; i < 3; i++) - { - normalizationParams_.scaleMin[i] = scaleMin[i]; - normalizationParams_.scaleMax[i] = scaleMax[i]; - } + // Store normalization parameters (only scale bounds needed) + for (int i = 0; i < 3; i++) + { + normalizationParams_.scaleMin[i] = scaleMin[i]; + normalizationParams_.scaleMax[i] = scaleMax[i]; + } - // Now convert all splats to compressed format - uint32_t compressedIndex = 0; - for (ecs::EntityId entityId : splatEntities_) - { - auto *model = getComponent(entityId); - if (model && model->isLoaded() && model->visible()) - { - const auto &splats = model->getSplats(); - for (const auto &splat : splats) + // Now convert all splats to compressed format + uint32_t compressedIndex = 0; + for (ecs::EntityId entityId : splatEntities_) { - // Convert splat data to compressed format (1 texel per splat) - CompressedSplat compressed = compressed_splat_utils::convertSplat( - splat.position[0], splat.position[1], splat.position[2], // position - splat.scale[0], - splat.scale[1], - splat.scale[2], // scale - splat.rotation[0], - splat.rotation[1], - splat.rotation[2], - splat.rotation[3], // quaternion - splat.color[0], - splat.color[1], - splat.color[2], - splat.opacity, // color + opacity - normalizationParams_ // normalization parameters - ); - - compressedSplatData_.push_back(compressed); - - // Add to sorted instances (only index and sorting data) - SplatInstanceData instance; - instance.index = compressedIndex; - instance.sourceEntity = entityId; - sortedSplats_.push_back(instance); - - compressedIndex++; + auto *model = getComponent(entityId); + if (model && model->isLoaded() && model->visible()) + { + const auto &splats = model->getSplats(); + for (const auto &splat : splats) + { + // Convert splat data to compressed format (1 texel per splat) + CompressedSplat compressed = compressed_splat_utils::convertSplat( + splat.position[0], splat.position[1], splat.position[2], // position + splat.scale[0], + splat.scale[1], + splat.scale[2], // scale + splat.rotation[0], + splat.rotation[1], + splat.rotation[2], + splat.rotation[3], // quaternion + splat.color[0], + splat.color[1], + splat.color[2], + splat.opacity, // color + opacity + normalizationParams_ // normalization parameters + ); + + compressedSplatData_.push_back(compressed); + + // Add to sorted instances (only index and sorting data) + SplatInstanceData instance; + instance.index = compressedIndex; + instance.sourceEntity = entityId; + sortedSplats_.push_back(instance); + + compressedIndex++; + } + } } - } - } - needsRebuild_ = false; - needsSorting_ = true; // After rebuilding, we need to sort the splats - needsTextureUpdate_ = true; // After rebuilding, we need to update the textures + needsRebuild_ = false; + needsSorting_ = true; // After rebuilding, we need to sort the splats + needsTextureUpdate_ = true; // After rebuilding, we need to update the textures - // Mark the mesh as dirty so the GPU buffer gets updated - setDirty(true); + // Mark the mesh as dirty so the GPU buffer gets updated + setDirty(true); - // Debug output - DEBUG("GaussianSplatsMesh", "Rebuilt compressed splats: %zu total splats from %zu entities", sortedSplats_.size(), splatEntities_.size()); - } + // Debug output + DEBUG("GaussianSplatsMesh", "Rebuilt compressed splats: %zu total splats from %zu entities", sortedSplats_.size(), splatEntities_.size()); + } - private: - // Vector of entity IDs that have GaussianSplattingModel3d components - std::vector splatEntities_; + private: + // Vector of entity IDs that have GaussianSplattingModel3d components + std::vector splatEntities_; - // Compressed splat data (1 texel per splat: 4 words, stable during sorting) - std::vector compressedSplatData_; + // Compressed splat data (1 texel per splat: 4 words, stable during sorting) + std::vector compressedSplatData_; - // Sorted splat indices for rendering (rebuilt when entities change or camera moves) - std::vector sortedSplats_; + // Sorted splat indices for rendering (rebuilt when entities change or camera moves) + std::vector sortedSplats_; - // WebGL buffer for instanced splat indices - std::shared_ptr splatInstanceBuffer_; + // WebGL buffer for instanced splat indices + std::shared_ptr splatInstanceBuffer_; - // WebGL texture2D for compressed splat data (1 texel per splat) - std::shared_ptr compressedSplatsTexture_; + // WebGL texture2D for compressed splat data (1 texel per splat) + std::shared_ptr compressedSplatsTexture_; - // Normalization parameters for position and scale compression - SplatNormalizationParams normalizationParams_; + // Normalization parameters for position and scale compression + SplatNormalizationParams normalizationParams_; - // WebGL context reference (needed for iterateInstanceAttributes) - std::weak_ptr glContext_; + // WebGL context reference (needed for iterateInstanceAttributes) + std::weak_ptr glContext_; - // Flags - bool needsRebuild_; - bool needsSorting_; - bool bufferInitialized_; - bool textureInitialized_; - bool needsTextureUpdate_; + // Flags + bool needsRebuild_; + bool needsSorting_; + bool bufferInitialized_; + bool textureInitialized_; + bool needsTextureUpdate_; - // Empty indices to prevent normal draw call - static const Indices emptyIndices_; - }; -} \ No newline at end of file + // Empty indices to prevent normal draw call + static const Indices emptyIndices_; + }; + } + } // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/gaussian_splatting.cpp b/src/client/builtin_scene/gaussian_splatting.cpp index a9998d0f8..53a7eb080 100644 --- a/src/client/builtin_scene/gaussian_splatting.cpp +++ b/src/client/builtin_scene/gaussian_splatting.cpp @@ -8,92 +8,95 @@ #include "./transform.hpp" #include "./xr.hpp" -namespace builtin_scene::gaussian_splatting +namespace endor { - using namespace std; - using namespace ecs; - - void GaussianSplattingInitSystem::onExecute() + namespace builtin_scene::gaussian_splatting { - auto gaussianSplattingCtx = getResource(); - auto meshes = getResource(); - auto materials = getResource(); - assert(gaussianSplattingCtx != nullptr && - meshes != nullptr && - materials != nullptr); + using namespace std; + using namespace ecs; - // Create the global GaussianSplatsMesh entity for rendering all Gaussian splats - gaussianSplattingCtx->globalSplatsMeshEntity_ = spawn( - hierarchy::Root(true), - Mesh3d(meshes->add(MeshBuilder::CreateGaussianSplatsMesh()), false), - MeshMaterial3d(materials->add(materials::GaussianSplattingMaterial::Default())), - Transform::FromXYZ(0.0f, 0.0f, 0.0f) - .FromScale(0.05f)); - } + void GaussianSplattingInitSystem::onExecute() + { + auto gaussianSplattingCtx = getResource(); + auto meshes = getResource(); + auto materials = getResource(); + assert(gaussianSplattingCtx != nullptr && + meshes != nullptr && + materials != nullptr); - void GaussianSplatsUpdateSystem::onExecute() - { - // Initialize XR experience reference if not already done - if (!xrExperience_) - xrExperience_ = getResource(); + // Create the global GaussianSplatsMesh entity for rendering all Gaussian splats + gaussianSplattingCtx->globalSplatsMeshEntity_ = spawn( + hierarchy::Root(true), + Mesh3d(meshes->add(MeshBuilder::CreateGaussianSplatsMesh()), false), + MeshMaterial3d(materials->add(materials::GaussianSplattingMaterial::Default())), + Transform::FromXYZ(0.0f, 0.0f, 0.0f) + .FromScale(0.05f)); + } - updateGlobalSplatsMesh(); - } + void GaussianSplatsUpdateSystem::onExecute() + { + // Initialize XR experience reference if not already done + if (!xrExperience_) + xrExperience_ = getResource(); - void GaussianSplatsUpdateSystem::updateGlobalSplatsMesh() - { - // Find the global GaussianSplatsMesh entity (has Mesh3d with GaussianSplatsMesh handle) - auto meshEntities = queryEntities([](const Mesh3d &mesh) -> bool - { return mesh.is(); }); + updateGlobalSplatsMesh(); + } - if (meshEntities.empty()) - return; // No global splats mesh found + void GaussianSplatsUpdateSystem::updateGlobalSplatsMesh() + { + // Find the global GaussianSplatsMesh entity (has Mesh3d with GaussianSplatsMesh handle) + auto meshEntities = queryEntities([](const Mesh3d &mesh) -> bool + { return mesh.is(); }); - auto globalSplatsMeshEntityId = meshEntities[0]; - auto meshComponent = getComponent(globalSplatsMeshEntityId); - if (!meshComponent) - return; + if (meshEntities.empty()) + return; // No global splats mesh found - auto splatsMesh = meshComponent->getHandleAs(); - if (!splatsMesh) - return; + auto globalSplatsMeshEntityId = meshEntities[0]; + auto meshComponent = getComponent(globalSplatsMeshEntityId); + if (!meshComponent) + return; - // Update splats from all GaussianSplattingModel3d entities - auto modelEntities = queryEntities([](const GaussianSplattingModel3d &model) -> bool - { return model.isLoaded() && model.visible(); }); + auto splatsMesh = meshComponent->getHandleAs(); + if (!splatsMesh) + return; - for (auto entityId : modelEntities) - { - auto modelComponent = getComponent(entityId); - if (modelComponent && modelComponent->isDirty()) + // Update splats from all GaussianSplattingModel3d entities + auto modelEntities = queryEntities([](const GaussianSplattingModel3d &model) -> bool + { return model.isLoaded() && model.visible(); }); + + for (auto entityId : modelEntities) { - // Update/add entity in the global mesh (handles both new and existing entities) - splatsMesh->updateSplatsEntity(entityId); - modelComponent->clearDirty(); + auto modelComponent = getComponent(entityId); + if (modelComponent && modelComponent->isDirty()) + { + // Update/add entity in the global mesh (handles both new and existing entities) + splatsMesh->updateSplatsEntity(entityId); + modelComponent->clearDirty(); + } } - } - // Get view matrix for depth sorting - glm::mat4 viewMatrix = glm::mat4(1.0f); + // Get view matrix for depth sorting + glm::mat4 viewMatrix = glm::mat4(1.0f); - // Try to get view matrix from WebXR experience first - if (xrExperience_ != nullptr) - { - auto xrViewerPose = xrExperience_->viewerPose(); - if (xrViewerPose != nullptr) + // Try to get view matrix from WebXR experience first + if (xrExperience_ != nullptr) { - auto &views = xrViewerPose->views(); - if (!views.empty()) + auto xrViewerPose = xrExperience_->viewerPose(); + if (xrViewerPose != nullptr) { - auto firstView = views[0]; - viewMatrix = firstView->transform().matrix(); + auto &views = xrViewerPose->views(); + if (!views.empty()) + { + auto firstView = views[0]; + viewMatrix = firstView->transform().matrix(); + } } } - } - // Sort splats by depth using the first view (or identity matrix if no XR) - // splatsMesh->markNeedsSorting(); - splatsMesh->sortSplatsByDepth(viewMatrix, [this](ecs::EntityId entityId) -> GaussianSplattingModel3d * - { return getComponent(entityId).get(); }); + // Sort splats by depth using the first view (or identity matrix if no XR) + // splatsMesh->markNeedsSorting(); + splatsMesh->sortSplatsByDepth(viewMatrix, [this](ecs::EntityId entityId) -> GaussianSplattingModel3d * + { return getComponent(entityId).get(); }); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/gaussian_splatting.hpp b/src/client/builtin_scene/gaussian_splatting.hpp index 04080083b..fe52896a3 100644 --- a/src/client/builtin_scene/gaussian_splatting.hpp +++ b/src/client/builtin_scene/gaussian_splatting.hpp @@ -5,165 +5,168 @@ #include #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - // Forward declarations - class WebXRExperience; - - // Forward declarations for 3DGS data structures - struct GaussianSplat - { - float position[3]; - float color[3]; - float opacity; - float scale[3]; - float rotation[4]; - }; - - namespace gaussian_splatting + namespace builtin_scene { - /** + // Forward declarations + class WebXRExperience; + + // Forward declarations for 3DGS data structures + struct GaussianSplat + { + float position[3]; + float color[3]; + float opacity; + float scale[3]; + float rotation[4]; + }; + + namespace gaussian_splatting + { + /** * System for initializing the global Gaussian splats mesh entity. * This system creates the global entity with Root component for rendering all splats. */ - class GaussianSplattingInitSystem final : public ecs::System - { - using ecs::System::System; - - public: - const std::string name() const override + class GaussianSplattingInitSystem final : public ecs::System { - return "GaussianSplattingInitSystem"; - } - void onExecute() override; - }; + using ecs::System::System; - /** + public: + const std::string name() const override + { + return "GaussianSplattingInitSystem"; + } + void onExecute() override; + }; + + /** * System for updating Gaussian splats from individual model entities. * This system runs before RenderSystem to collect splats from all GaussianSplattingModel3d * entities and update the global GaussianSplatsMesh with proper depth sorting. */ - class GaussianSplatsUpdateSystem final : public ecs::System - { - using ecs::System::System; - - public: - const std::string name() const override + class GaussianSplatsUpdateSystem final : public ecs::System { - return "GaussianSplatsUpdateSystem"; - } - void onExecute() override; + using ecs::System::System; - private: - /** + public: + const std::string name() const override + { + return "GaussianSplatsUpdateSystem"; + } + void onExecute() override; + + private: + /** * Collect splats from all GaussianSplattingModel3d entities and update the global GaussianSplatsMesh. */ - void updateGlobalSplatsMesh(); + void updateGlobalSplatsMesh(); - private: - std::shared_ptr xrExperience_ = nullptr; - }; - } + private: + std::shared_ptr xrExperience_ = nullptr; + }; + } - /** + /** * Component for storing Gaussian splats data for individual model entities. * This component preserves the splats for each HTMLModelElement entity. */ - class GaussianSplattingModel3d : public ecs::Component - { - public: - GaussianSplattingModel3d(const std::string &src) - : src_(src) - , loaded_(false) - , visible_(true) - , dirty_(true) // Initially dirty to ensure first render + class GaussianSplattingModel3d : public ecs::Component { - } + public: + GaussianSplattingModel3d(const std::string &src) + : src_(src) + , loaded_(false) + , visible_(true) + , dirty_(true) // Initially dirty to ensure first render + { + } - public: - // Accessors - inline const std::string &src() const - { - return src_; - } - inline bool isLoaded() const - { - return loaded_; - } - inline bool visible() const - { - return visible_; - } - inline void setVisible(bool visible) - { - if (visible_ != visible) + public: + // Accessors + inline const std::string &src() const { - visible_ = visible; - dirty_ = true; // Mark as dirty when visibility changes + return src_; + } + inline bool isLoaded() const + { + return loaded_; + } + inline bool visible() const + { + return visible_; + } + inline void setVisible(bool visible) + { + if (visible_ != visible) + { + visible_ = visible; + dirty_ = true; // Mark as dirty when visibility changes + } } - } - // Dirty flag methods - inline bool isDirty() const - { - return dirty_; - } - inline void clearDirty() - { - dirty_ = false; - } + // Dirty flag methods + inline bool isDirty() const + { + return dirty_; + } + inline void clearDirty() + { + dirty_ = false; + } - // Splat data methods - inline const std::vector &getSplats() const - { - return splats_; - } - inline size_t getSplatCount() const - { - return splats_.size(); - } + // Splat data methods + inline const std::vector &getSplats() const + { + return splats_; + } + inline size_t getSplatCount() const + { + return splats_.size(); + } - // Model loading - void setLoaded(bool loaded) - { - if (loaded_ != loaded) + // Model loading + void setLoaded(bool loaded) { - loaded_ = loaded; - dirty_ = true; // Mark as dirty when load state changes + if (loaded_ != loaded) + { + loaded_ = loaded; + dirty_ = true; // Mark as dirty when load state changes + } + } + void setSplats(std::vector &&splats) + { + splats_ = std::move(splats); + loaded_ = !splats_.empty(); + dirty_ = true; // Mark as dirty when splat data changes } - } - void setSplats(std::vector &&splats) - { - splats_ = std::move(splats); - loaded_ = !splats_.empty(); - dirty_ = true; // Mark as dirty when splat data changes - } - private: - std::string src_; - bool loaded_; - bool visible_; - bool dirty_; // Flag to track when splat data has changed + private: + std::string src_; + bool loaded_; + bool visible_; + bool dirty_; // Flag to track when splat data has changed - // 3DGS data - std::vector splats_; - }; + // 3DGS data + std::vector splats_; + }; - /** + /** * Resource for managing Gaussian splatting context and shared state. * Similar to WebContentContext for managing the global Gaussian splats mesh entity. */ - class GaussianSplattingContext : public ecs::Resource - { - friend class gaussian_splatting::GaussianSplattingInitSystem; - - public: - ecs::EntityId globalSplatsMeshEntity() const + class GaussianSplattingContext : public ecs::Resource { - return globalSplatsMeshEntity_; - } + friend class gaussian_splatting::GaussianSplattingInitSystem; + + public: + ecs::EntityId globalSplatsMeshEntity() const + { + return globalSplatsMeshEntity_; + } - private: - ecs::EntityId globalSplatsMeshEntity_; - }; -} + private: + ecs::EntityId globalSplatsMeshEntity_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/hierarchy/children.hpp b/src/client/builtin_scene/hierarchy/children.hpp index db33b5ed0..7fef89080 100644 --- a/src/client/builtin_scene/hierarchy/children.hpp +++ b/src/client/builtin_scene/hierarchy/children.hpp @@ -3,30 +3,33 @@ #include #include "../ecs-inl.hpp" -namespace builtin_scene::hierarchy +namespace endor { - class Children : public ecs::Component + namespace builtin_scene::hierarchy { - public: - using ecs::Component::Component; - - public: - const std::vector &children() const - { - return children_; - } - std::vector &children() + class Children : public ecs::Component { - return children_; - } + public: + using ecs::Component::Component; - public: - void addChild(ecs::EntityId child) - { - children_.push_back(child); - } + public: + const std::vector &children() const + { + return children_; + } + std::vector &children() + { + return children_; + } + + public: + void addChild(ecs::EntityId child) + { + children_.push_back(child); + } - private: - std::vector children_; - }; -} + private: + std::vector children_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/hierarchy/element.hpp b/src/client/builtin_scene/hierarchy/element.hpp index 8b517dad4..74c2b9414 100644 --- a/src/client/builtin_scene/hierarchy/element.hpp +++ b/src/client/builtin_scene/hierarchy/element.hpp @@ -4,38 +4,42 @@ #include #include "../ecs-inl.hpp" -namespace builtin_scene::hierarchy +namespace endor { - class Element : public ecs::Component + namespace builtin_scene::hierarchy { - using ecs::Component::Component; - - public: - Element(std::string name, std::shared_ptr node) - : name(name) - , node(node) + class Element : public ecs::Component { - } + using ecs::Component::Component; - public: - template - requires std::is_same_v - std::shared_ptr getNode() const - { - return std::dynamic_pointer_cast(node); - } + public: + Element(std::string name, std::shared_ptr node) + : name(name) + , node(node) + { + } - template - requires std::is_same_v - T &getNodeChecked() const - { - auto ref = getNode(); - assert(ref != nullptr && "The node must be valid."); - return *ref; - } + public: + template + requires std::is_same_v + std::shared_ptr getNode() + const + { + return std::dynamic_pointer_cast(node); + } + + template + requires std::is_same_v + T &getNodeChecked() const + { + auto ref = getNode(); + assert(ref != nullptr && "The node must be valid."); + return *ref; + } - public: - std::string name; - std::shared_ptr node; - }; -} + public: + std::string name; + std::shared_ptr node; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/hierarchy/parent.hpp b/src/client/builtin_scene/hierarchy/parent.hpp index 8a7b4a8a9..debc7038d 100644 --- a/src/client/builtin_scene/hierarchy/parent.hpp +++ b/src/client/builtin_scene/hierarchy/parent.hpp @@ -2,38 +2,41 @@ #include "../ecs-inl.hpp" -namespace builtin_scene::hierarchy +namespace endor { - class Parent : public ecs::Component + namespace builtin_scene::hierarchy { - public: - using ecs::Component::Component; - - public: - Parent(ecs::EntityId parent, ecs::EntityId root) - : parent_(parent) - , root_(root) + class Parent : public ecs::Component { - } + public: + using ecs::Component::Component; + + public: + Parent(ecs::EntityId parent, ecs::EntityId root) + : parent_(parent) + , root_(root) + { + } - public: - /** + public: + /** * @returns The parent entity ID. */ - const ecs::EntityId &parent() const - { - return parent_; - } - /** + const ecs::EntityId &parent() const + { + return parent_; + } + /** * @returns The root entity ID. */ - const ecs::EntityId &root() const - { - return root_; - } + const ecs::EntityId &root() const + { + return root_; + } - private: - ecs::EntityId parent_; - ecs::EntityId root_; - }; -} + private: + ecs::EntityId parent_; + ecs::EntityId root_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/hierarchy/root.hpp b/src/client/builtin_scene/hierarchy/root.hpp index 87f8ce828..5d72b94db 100644 --- a/src/client/builtin_scene/hierarchy/root.hpp +++ b/src/client/builtin_scene/hierarchy/root.hpp @@ -2,18 +2,21 @@ #include "../ecs-inl.hpp" -namespace builtin_scene::hierarchy +namespace endor { - class Root : public ecs::Component + namespace builtin_scene::hierarchy { - public: - Root(bool renderable = false) - : renderable(renderable) + class Root : public ecs::Component { - } + public: + Root(bool renderable = false) + : renderable(renderable) + { + } - public: - // If the root entity can be renderer. - bool renderable; - }; -} + public: + // If the root entity can be renderer. + bool renderable; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/image.hpp b/src/client/builtin_scene/image.hpp index 46f7e0055..c16f2cf57 100644 --- a/src/client/builtin_scene/image.hpp +++ b/src/client/builtin_scene/image.hpp @@ -6,41 +6,44 @@ #include #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - class Image2d : public ecs::Component + namespace builtin_scene { - public: - Image2d(std::string src, std::shared_ptr bitmap) - : src(src) - , bitmap(bitmap) + class Image2d : public ecs::Component { - } + public: + Image2d(std::string src, std::shared_ptr bitmap) + : src(src) + , bitmap(bitmap) + { + } - public: - inline sk_sp image() const - { - return bitmap->asImage(); - } - inline bool hasImageData() const - { - return bitmap != nullptr; - } + public: + inline sk_sp image() const + { + return bitmap->asImage(); + } + inline bool hasImageData() const + { + return bitmap != nullptr; + } - inline bool visible() const - { - return visible_; - } - inline void setVisible(bool b) - { - visible_ = b; - } + inline bool visible() const + { + return visible_; + } + inline void setVisible(bool b) + { + visible_ = b; + } - public: - std::string src; - std::shared_ptr bitmap; + public: + std::string src; + std::shared_ptr bitmap; - private: - bool visible_ = true; - }; -} + private: + bool visible_ = true; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/instanced_mesh-inl.hpp b/src/client/builtin_scene/instanced_mesh-inl.hpp index 08c90b2e4..8abaee39c 100644 --- a/src/client/builtin_scene/instanced_mesh-inl.hpp +++ b/src/client/builtin_scene/instanced_mesh-inl.hpp @@ -3,23 +3,26 @@ #include "./instanced_mesh.hpp" #include "./meshes.hpp" -namespace builtin_scene +namespace endor { - template - requires std::is_base_of::value - void InstancedMesh::onMesh3dInitialized(std::shared_ptr mesh3d, - std::shared_ptr glContext) + namespace builtin_scene { - MeshType::onMesh3dInitialized(mesh3d, glContext); + template + requires std::is_base_of::value + void InstancedMesh::onMesh3dInitialized(std::shared_ptr mesh3d, + std::shared_ptr glContext) + { + MeshType::onMesh3dInitialized(mesh3d, glContext); - setup(glContext, mesh3d); - } + setup(glContext, mesh3d); + } - template - requires std::is_base_of::value - void InstancedMesh::onConfigureInstanceAttribs(std::shared_ptr mesh3d, - std::shared_ptr program) - { - configureInstanceAttribs(program, mesh3d); + template + requires std::is_base_of::value + void InstancedMesh::onConfigureInstanceAttribs(std::shared_ptr mesh3d, + std::shared_ptr program) + { + configureInstanceAttribs(program, mesh3d); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/instanced_mesh.cpp b/src/client/builtin_scene/instanced_mesh.cpp index 20f341ef5..8e9eb2b42 100644 --- a/src/client/builtin_scene/instanced_mesh.cpp +++ b/src/client/builtin_scene/instanced_mesh.cpp @@ -1,816 +1,819 @@ #include "./instanced_mesh-inl.hpp" #include "./css_border_data_texture.hpp" -namespace builtin_scene +namespace endor { - class CSSBorderDataTexture; -} - -namespace builtin_scene -{ - using namespace std; - using namespace client_graphics; - - void Instance::randomColor() + namespace builtin_scene { - glm::vec4 color(1.0f); - color.r = glm::linearRand(0.0f, 1.0f); - color.g = glm::linearRand(0.0f, 1.0f); - color.b = glm::linearRand(0.0f, 1.0f); - color.a = 1.0f; - setColor(color); + class CSSBorderDataTexture; } - bool Instance::setColor(const glm::vec4 &color) + namespace builtin_scene { - if (data_.color.r == color.r && - data_.color.g == color.g && - data_.color.b == color.b && - data_.color.a == color.a) - return false; + using namespace std; + using namespace client_graphics; - data_.color = color; - setMaybeInvisible(); - notifyBufferDataChanged(); - return true; - } + void Instance::randomColor() + { + glm::vec4 color(1.0f); + color.r = glm::linearRand(0.0f, 1.0f); + color.g = glm::linearRand(0.0f, 1.0f); + color.b = glm::linearRand(0.0f, 1.0f); + color.a = 1.0f; + setColor(color); + } - void Instance::setTransform(const glm::mat4 &transformationMatrix) - { - auto &transform = data_.transform; - if (transform == transformationMatrix) - return; // Skip if there is no change. + bool Instance::setColor(const glm::vec4 &color) + { + if (data_.color.r == color.r && + data_.color.g == color.g && + data_.color.b == color.b && + data_.color.a == color.a) + return false; - data_.transform = transformationMatrix; - notifyBufferDataChanged(); - } + data_.color = color; + setMaybeInvisible(); + notifyBufferDataChanged(); + return true; + } - void Instance::setTexture(TextureOffset uvOffset, - TextureOffset uvOffsetR, - TextureScale uvScale, - uint32_t layerIndex) - { - if (data_.texUvOffset == uvOffset && - data_.texUvOffsetR == uvOffsetR && - data_.texUvScale == uvScale && - data_.texLayerIndex == layerIndex) + void Instance::setTransform(const glm::mat4 &transformationMatrix) { - return; // Skip if there is no change. + auto &transform = data_.transform; + if (transform == transformationMatrix) + return; // Skip if there is no change. + + data_.transform = transformationMatrix; + notifyBufferDataChanged(); } - data_.texUvOffset = uvOffset; - data_.texUvOffsetR = uvOffsetR; - data_.texUvScale = uvScale; - data_.texLayerIndex = layerIndex; - setMaybeInvisible(); - notifyBufferDataChanged(); - } + void Instance::setTexture(TextureOffset uvOffset, + TextureOffset uvOffsetR, + TextureScale uvScale, + uint32_t layerIndex) + { + if (data_.texUvOffset == uvOffset && + data_.texUvOffsetR == uvOffsetR && + data_.texUvScale == uvScale && + data_.texLayerIndex == layerIndex) + { + return; // Skip if there is no change. + } - void Instance::disableTexture() - { - setTexture(TextureOffset(), TextureOffset(), TextureScale(), 0); - } + data_.texUvOffset = uvOffset; + data_.texUvOffsetR = uvOffsetR; + data_.texUvScale = uvScale; + data_.texLayerIndex = layerIndex; + setMaybeInvisible(); + notifyBufferDataChanged(); + } - void Instance::setDimensions(float width, float height) - { - if (data_.dimensions.x == width && data_.dimensions.y == height) - return; // Skip if there is no change. + void Instance::disableTexture() + { + setTexture(TextureOffset(), TextureOffset(), TextureScale(), 0); + } - data_.dimensions = glm::vec2(width, height); - notifyBufferDataChanged(); - } + void Instance::setDimensions(float width, float height) + { + if (data_.dimensions.x == width && data_.dimensions.y == height) + return; // Skip if there is no change. - void Instance::setBorderRadius(glm::vec4 borderRadius) - { - if (data_.borderRadius == borderRadius) - return; // Skip if there is no change. + data_.dimensions = glm::vec2(width, height); + notifyBufferDataChanged(); + } - data_.borderRadius = borderRadius; - notifyBufferDataChanged(); - } + void Instance::setBorderRadius(glm::vec4 borderRadius) + { + if (data_.borderRadius == borderRadius) + return; // Skip if there is no change. - void Instance::setBorderRadius(float topLeft, float topRight, float bottomRight, float bottomLeft) - { - setBorderRadius(glm::vec4(topLeft, topRight, bottomRight, bottomLeft)); - } + data_.borderRadius = borderRadius; + notifyBufferDataChanged(); + } - void Instance::setBorderWidth(glm::vec4 borderWidth) - { - if (borderWidths_ == borderWidth) - return; // Skip if there is no change. + void Instance::setBorderRadius(float topLeft, float topRight, float bottomRight, float bottomLeft) + { + setBorderRadius(glm::vec4(topLeft, topRight, bottomRight, bottomLeft)); + } - borderWidths_ = borderWidth; - setMaybeInvisible(); - notifyTextureDataChanged(); - } + void Instance::setBorderWidth(glm::vec4 borderWidth) + { + if (borderWidths_ == borderWidth) + return; // Skip if there is no change. - void Instance::setBorderWidth(float top, float right, float bottom, float left) - { - setBorderWidth(glm::vec4(top, right, bottom, left)); - } + borderWidths_ = borderWidth; + setMaybeInvisible(); + notifyTextureDataChanged(); + } - void Instance::setBorderColor(glm::vec4 borderColor) - { - // Set the same color for all four sides for now - bool anyChanged = false; - for (int i = 0; i < 4; ++i) + void Instance::setBorderWidth(float top, float right, float bottom, float left) { - if (borderColors_[i] != borderColor) + setBorderWidth(glm::vec4(top, right, bottom, left)); + } + + void Instance::setBorderColor(glm::vec4 borderColor) + { + // Set the same color for all four sides for now + bool anyChanged = false; + for (int i = 0; i < 4; ++i) { - borderColors_[i] = borderColor; - anyChanged = true; + if (borderColors_[i] != borderColor) + { + borderColors_[i] = borderColor; + anyChanged = true; + } + } + + if (anyChanged) + { + setMaybeInvisible(); + notifyTextureDataChanged(); } } - if (anyChanged) + void Instance::setBorderColor(float r, float g, float b, float a) { - setMaybeInvisible(); - notifyTextureDataChanged(); + setBorderColor(glm::vec4(r, g, b, a)); } - } - void Instance::setBorderColor(float r, float g, float b, float a) - { - setBorderColor(glm::vec4(r, g, b, a)); - } + void Instance::setBorderStyle(float borderStyle) + { + if (data_.borderStyle == borderStyle) + return; // Skip if there is no change. - void Instance::setBorderStyle(float borderStyle) - { - if (data_.borderStyle == borderStyle) - return; // Skip if there is no change. + data_.borderStyle = borderStyle; + setMaybeInvisible(); + notifyBufferDataChanged(); + } - data_.borderStyle = borderStyle; - setMaybeInvisible(); - notifyBufferDataChanged(); - } + void Instance::setSDFTextureEnabled(bool enabled) + { + float value = enabled ? 1.0f : 0.0f; + if (data_.enableSDFTexture == value) + return; // Skip if there is no change. - void Instance::setSDFTextureEnabled(bool enabled) - { - float value = enabled ? 1.0f : 0.0f; - if (data_.enableSDFTexture == value) - return; // Skip if there is no change. + data_.enableSDFTexture = value; + notifyBufferDataChanged(); + } - data_.enableSDFTexture = value; - notifyBufferDataChanged(); - } + bool Instance::hasNoBorders() const + { + // Fast check for none border style. + if (data_.isBorderNone()) + return true; + + // Check if all border widths are zero and all border colors are transparent. + if (borderWidths_ == glm::vec4(0.0f) && + borderColors_[0].a == 0.0f && + borderColors_[1].a == 0.0f && + borderColors_[2].a == 0.0f && + borderColors_[3].a == 0.0f) + { + return true; + } - bool Instance::hasNoBorders() const - { - // Fast check for none border style. - if (data_.isBorderNone()) - return true; + // Otherwise, there are borders. + return false; + } - // Check if all border widths are zero and all border colors are transparent. - if (borderWidths_ == glm::vec4(0.0f) && - borderColors_[0].a == 0.0f && - borderColors_[1].a == 0.0f && - borderColors_[2].a == 0.0f && - borderColors_[3].a == 0.0f) + void Instance::addHolder(shared_ptr holder) { - return true; - } + // Check if the holder is already added. + for (auto &h : holders_) + { + if (h.lock() == holder) + return; + } - // Otherwise, there are borders. - return false; - } + // Add the holder. + holders_.push_back(holder); + } - void Instance::addHolder(shared_ptr holder) - { - // Check if the holder is already added. - for (auto &h : holders_) + void Instance::removeHolder(shared_ptr holder) { - if (h.lock() == holder) - return; + holders_.erase(remove_if(holders_.begin(), holders_.end(), [holder](const weak_ptr &h) + { return h.lock() == holder; }), + holders_.end()); } - // Add the holder. - holders_.push_back(holder); - } - - void Instance::removeHolder(shared_ptr holder) - { - holders_.erase(remove_if(holders_.begin(), holders_.end(), [holder](const weak_ptr &h) - { return h.lock() == holder; }), - holders_.end()); - } - - void Instance::notifyBufferDataChanged() - { - for (auto &holder : holders_) + void Instance::notifyBufferDataChanged() { - if (auto holderPtr = holder.lock()) - holderPtr->markBufferAsDirty(); + for (auto &holder : holders_) + { + if (auto holderPtr = holder.lock()) + holderPtr->markBufferAsDirty(); + } } - } - void Instance::notifyTextureDataChanged() - { - for (auto &holder : holders_) + void Instance::notifyTextureDataChanged() { - if (auto holderPtr = holder.lock()) + for (auto &holder : holders_) { - if (holderPtr->isContentInstancesList()) - dynamic_pointer_cast(holderPtr)->markTextureDataAsDirty(); + if (auto holderPtr = holder.lock()) + { + if (holderPtr->isContentInstancesList()) + dynamic_pointer_cast(holderPtr)->markTextureDataAsDirty(); + } } } - } - bool Instance::skipToDraw() const - { - // Skip if the instance is disabled or maybe invisible. - if (!enabled_ || maybeInvisible_) - return true; + bool Instance::skipToDraw() const + { + // Skip if the instance is disabled or maybe invisible. + if (!enabled_ || maybeInvisible_) + return true; - // Otherwise, the instance is ready to draw. - return false; - } + // Otherwise, the instance is ready to draw. + return false; + } - void Instance::setMaybeInvisible() - { - // The instance should be invisible if it has transparent color, no texture, and no borders to draw. - maybeInvisible_ = data_.isTransparent() && - !data_.ownTexture() && - hasNoBorders(); - } + void Instance::setMaybeInvisible() + { + // The instance should be invisible if it has transparent color, no texture, and no borders to draw. + maybeInvisible_ = data_.isTransparent() && + !data_.ownTexture() && + hasNoBorders(); + } - InstanceListBase::InstanceListBase(shared_ptr vao, - shared_ptr instanceVbo) - : vao(vao) - , instanceVbo(instanceVbo) - , bufferDataDirty_(true) - { - } + InstanceListBase::InstanceListBase(shared_ptr vao, + shared_ptr instanceVbo) + : vao(vao) + , instanceVbo(instanceVbo) + , bufferDataDirty_(true) + { + } - size_t InstanceListBase::configureAttribs(shared_ptr glContext, - shared_ptr program, - shared_ptr mesh3d) - { - WebGLVertexArrayScope vaoScope(glContext, vao); - - glContext->bindBuffer(WebGLBufferBindingTarget::kElementArrayBuffer, mesh3d->elementBufferObject()); - glContext->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, mesh3d->vertexBufferObject()); - auto configureVertexAttrib = [&](const IVertexAttribute &attrib, int index, size_t stride, size_t offset) - { - glContext->vertexAttribPointer(index, - attrib.size(), - attrib.type(), - attrib.normalized(), - stride, - offset); - glContext->enableVertexAttribArray(index); - }; - mesh3d->iterateEnabledAttributes(program, configureVertexAttrib); - - // Make sure the `vbo` is currently bound. - glContext->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, instanceVbo); - auto configureInstanceAttrib = [&glContext](const IVertexAttribute &attrib, - int index, - size_t stride, - size_t offset) - { - glContext->enableVertexAttribArray(index); - glContext->vertexAttribPointer(index, - attrib.size(), - attrib.type(), - attrib.normalized(), - stride, - offset); - glContext->vertexAttribDivisor(index, 1); - }; - - // Iterate through the instance attributes and call `configureInstanceAttrib` to configure them. - size_t attribsCount = 0; - size_t offset = 0; - for (size_t i = 0; i < InstancedMeshBase::INSTANCE_ATTRIBUTES.size(); i++) - { - auto &name = InstancedMeshBase::INSTANCE_ATTRIBUTES[i]; - auto attribLocation = glContext->getAttribLocation(program, name); - if (attribLocation.has_value()) + size_t InstanceListBase::configureAttribs(shared_ptr glContext, + shared_ptr program, + shared_ptr mesh3d) + { + WebGLVertexArrayScope vaoScope(glContext, vao); + + glContext->bindBuffer(WebGLBufferBindingTarget::kElementArrayBuffer, mesh3d->elementBufferObject()); + glContext->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, mesh3d->vertexBufferObject()); + auto configureVertexAttrib = [&](const IVertexAttribute &attrib, int index, size_t stride, size_t offset) + { + glContext->vertexAttribPointer(index, + attrib.size(), + attrib.type(), + attrib.normalized(), + stride, + offset); + glContext->enableVertexAttribArray(index); + }; + mesh3d->iterateEnabledAttributes(program, configureVertexAttrib); + + // Make sure the `vbo` is currently bound. + glContext->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, instanceVbo); + auto configureInstanceAttrib = [&glContext](const IVertexAttribute &attrib, + int index, + size_t stride, + size_t offset) { - auto instanceIndex = attribLocation.value().index.value_or(-1); - if (name == "instanceTransform") + glContext->enableVertexAttribArray(index); + glContext->vertexAttribPointer(index, + attrib.size(), + attrib.type(), + attrib.normalized(), + stride, + offset); + glContext->vertexAttribDivisor(index, 1); + }; + + // Iterate through the instance attributes and call `configureInstanceAttrib` to configure them. + size_t attribsCount = 0; + size_t offset = 0; + for (size_t i = 0; i < InstancedMeshBase::INSTANCE_ATTRIBUTES.size(); i++) + { + auto &name = InstancedMeshBase::INSTANCE_ATTRIBUTES[i]; + auto attribLocation = glContext->getAttribLocation(program, name); + if (attribLocation.has_value()) { - for (int i = 0; i < 4; i++) + auto instanceIndex = attribLocation.value().index.value_or(-1); + if (name == "instanceTransform") + { + for (int i = 0; i < 4; i++) + { + auto matrixRowIndex = instanceIndex + i; + VertexAttribute attrib(name, matrixRowIndex, VertexFormat::kFloat32x4); + configureInstanceAttrib(attrib, matrixRowIndex, InstancedMeshBase::STRIDE, offset); + offset += attrib.byteLength(); + attribsCount += 1; + } + } + else { - auto matrixRowIndex = instanceIndex + i; - VertexAttribute attrib(name, matrixRowIndex, VertexFormat::kFloat32x4); - configureInstanceAttrib(attrib, matrixRowIndex, InstancedMeshBase::STRIDE, offset); - offset += attrib.byteLength(); + unique_ptr attrib = nullptr; + // 1u + if (name == "instanceLayerIndex" || + name == "instanceBorderStyle") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kUint32); + } + // 1f + else if (name == "instanceUseSDFTexture") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32); + } + // 2f + else if (name == "instanceTexUvOffset" || + name == "instanceTexUvScale" || + name == "instanceTexUvOffsetR" || + name == "instanceDimensions") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x2); + } + // 4f + else if (name == "instanceColor" || + name == "instanceBorderRadius") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x4); + } + else + { + assert(false && "Unknown instance attribute name."); + } + + assert(attrib != nullptr); + configureInstanceAttrib(*attrib, instanceIndex, InstancedMeshBase::STRIDE, offset); + offset += attrib->byteLength(); attribsCount += 1; } } else { - unique_ptr attrib = nullptr; - // 1u - if (name == "instanceLayerIndex" || - name == "instanceBorderStyle") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kUint32); - } - // 1f - else if (name == "instanceUseSDFTexture") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32); - } - // 2f - else if (name == "instanceTexUvOffset" || - name == "instanceTexUvScale" || - name == "instanceTexUvOffsetR" || - name == "instanceDimensions") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x2); - } - // 4f - else if (name == "instanceColor" || - name == "instanceBorderRadius") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x4); - } - else - { - assert(false && "Unknown instance attribute name."); - } - - assert(attrib != nullptr); - configureInstanceAttrib(*attrib, instanceIndex, InstancedMeshBase::STRIDE, offset); - offset += attrib->byteLength(); - attribsCount += 1; + cerr << "The instance attribute " << name << " is not found." << endl; } } - else - { - cerr << "The instance attribute " << name << " is not found." << endl; - } + return attribsCount; } - return attribsCount; - } - - void ContentInstancesList::update(const InstanceMap &instances, SortingOrder sortingOrder) - { - clearInstances(); // Clear the instances first. - for (auto &[id, instance] : instances) + void ContentInstancesList::update(const InstanceMap &instances, SortingOrder sortingOrder) { - if (instance == nullptr) [[unlikely]] - continue; - if (instance->skipToDraw()) - continue; + clearInstances(); // Clear the instances first. - if (filter == InstanceFilter::kAll) + for (auto &[id, instance] : instances) { - addInstance(instance); - } - else if (filter == InstanceFilter::kOpaque) - { - if (instance->isOpaque_) - addInstance(instance); - } - else if (filter == InstanceFilter::kTransparent) - { - if (!instance->isOpaque_) + if (instance == nullptr) [[unlikely]] + continue; + if (instance->skipToDraw()) + continue; + + if (filter == InstanceFilter::kAll) + { addInstance(instance); + } + else if (filter == InstanceFilter::kOpaque) + { + if (instance->isOpaque_) + addInstance(instance); + } + else if (filter == InstanceFilter::kTransparent) + { + if (!instance->isOpaque_) + addInstance(instance); + } } + + sortInstances(sortingOrder); + markTextureDataAsDirty(); } - sortInstances(sortingOrder); - markTextureDataAsDirty(); - } + void ContentInstancesList::beforeInstancedDraw(client_graphics::WebGL2Context &glContext, + CSSBorderDataTexture *borderDataTexture) + { + InstanceListBase::beforeInstancedDraw(glContext); - void ContentInstancesList::beforeInstancedDraw(client_graphics::WebGL2Context &glContext, - CSSBorderDataTexture *borderDataTexture) - { - InstanceListBase::beforeInstancedDraw(glContext); + // Update border data texture if border data is dirty + if (textureDataDirty_ && + borderDataTexture != nullptr && + borderDataTexture->isInitialized()) + { + borderDataTexture->updateBorderData(getInstances()); + textureDataDirty_ = false; + } + } - // Update border data texture if border data is dirty - if (textureDataDirty_ && - borderDataTexture != nullptr && - borderDataTexture->isInitialized()) + void ContentInstancesList::clearInstances() { - borderDataTexture->updateBorderData(getInstances()); - textureDataDirty_ = false; + InstanceListBase::clearInstances(); + textureDataDirty_ = true; } - } - - void ContentInstancesList::clearInstances() - { - InstanceListBase::clearInstances(); - textureDataDirty_ = true; - } - void ContentInstancesList::sortInstances(SortingOrder sortingOrder) - { - if (sortingOrder != SortingOrder::kNone && list_.size() > 1) + void ContentInstancesList::sortInstances(SortingOrder sortingOrder) { - // Sorting the instances by z-index and the sorting order. - auto sortInstances = [sortingOrder](const weak_ptr &a, const weak_ptr &b) + if (sortingOrder != SortingOrder::kNone && list_.size() > 1) { - if (auto aPtr = a.lock(); aPtr != nullptr) + // Sorting the instances by z-index and the sorting order. + auto sortInstances = [sortingOrder](const weak_ptr &a, const weak_ptr &b) { - if (auto bPtr = b.lock(); bPtr != nullptr) + if (auto aPtr = a.lock(); aPtr != nullptr) { - if (sortingOrder == SortingOrder::kFrontToBack) - return aPtr->renderQueue_ < bPtr->renderQueue_; - else if (sortingOrder == SortingOrder::kBackToFront) - return aPtr->renderQueue_ > bPtr->renderQueue_; + if (auto bPtr = b.lock(); bPtr != nullptr) + { + if (sortingOrder == SortingOrder::kFrontToBack) + return aPtr->renderQueue_ < bPtr->renderQueue_; + else if (sortingOrder == SortingOrder::kBackToFront) + return aPtr->renderQueue_ > bPtr->renderQueue_; + } } - } - return false; - }; - sort(list_.begin(), list_.end(), sortInstances); + return false; + }; + sort(list_.begin(), list_.end(), sortInstances); + } } - } - void ContainerInstance::setInstance(std::shared_ptr instance) - { - clearInstances(); - if (instance != nullptr) + void ContainerInstance::setInstance(std::shared_ptr instance) { - addInstance(instance); - belongsToContainerId_ = instance->belongsToContainerId_; + clearInstances(); + if (instance != nullptr) + { + addInstance(instance); + belongsToContainerId_ = instance->belongsToContainerId_; + } } - } - - size_t InstanceListBase::copyToArrayData(vector &dst) - { - size_t len = 0; - for (auto &instance : list_) - { - if (TR_UNLIKELY(instance.expired())) - continue; - auto instancePtr = instance.lock(); - if (TR_UNLIKELY(instancePtr == nullptr)) - continue; - dst.push_back(instancePtr->data_); - len += 1; - } - return len * sizeof(InstanceData); - } - void InstanceListBase::beforeInstancedDraw(WebGL2Context &glContext) - { - // Update instance VBO if structure is dirty - if (bufferDataDirty_) + size_t InstanceListBase::copyToArrayData(vector &dst) { size_t len = 0; - vector array; - if ((len = copyToArrayData(array)) > 0) + for (auto &instance : list_) { - glContext.bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, instanceVbo); - glContext.bufferData(WebGLBufferBindingTarget::kArrayBuffer, - len, - array.data(), - WebGLBufferUsage::kDynamicDraw); + if (TR_UNLIKELY(instance.expired())) + continue; + auto instancePtr = instance.lock(); + if (TR_UNLIKELY(instancePtr == nullptr)) + continue; + dst.push_back(instancePtr->data_); + len += 1; } - bufferDataDirty_ = false; + return len * sizeof(InstanceData); } - } - - void InstanceListBase::afterInstancedDraw(WebGL2Context &glContext) - { - } - vector> InstanceListBase::getInstances() const - { - vector> instances; - for (const auto &weakInstance : list_) + void InstanceListBase::beforeInstancedDraw(WebGL2Context &glContext) { - if (!weakInstance.expired()) + // Update instance VBO if structure is dirty + if (bufferDataDirty_) { - auto instance = weakInstance.lock(); - if (instance) + size_t len = 0; + vector array; + if ((len = copyToArrayData(array)) > 0) { - instances.push_back(instance); + glContext.bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, instanceVbo); + glContext.bufferData(WebGLBufferBindingTarget::kArrayBuffer, + len, + array.data(), + WebGLBufferUsage::kDynamicDraw); } + bufferDataDirty_ = false; } } - return instances; - } - void InstanceListBase::clearInstances() - { - for (auto &instance : list_) + void InstanceListBase::afterInstancedDraw(WebGL2Context &glContext) { - if (TR_UNLIKELY(instance.expired())) - continue; - auto instancePtr = instance.lock(); - if (TR_UNLIKELY(instancePtr == nullptr)) - continue; - instancePtr->removeHolder(shared_from_this()); } - list_.clear(); - bufferDataDirty_ = true; - } - - void InstanceListBase::addInstance(shared_ptr instance) - { - if (TR_UNLIKELY(instance == nullptr)) - return; - list_.push_back(instance); - instance->addHolder(shared_from_this()); - bufferDataDirty_ = true; - } - - size_t InstancedMeshBase::iterateInstanceAttributes(shared_ptr program, - function callback) const - { - auto glContext = glContext_.lock(); - if (glContext == nullptr) - return 0; - size_t attribsCount = 0; - size_t offset = 0; - for (size_t i = 0; i < INSTANCE_ATTRIBUTES.size(); i++) + vector> InstanceListBase::getInstances() const { - auto &name = INSTANCE_ATTRIBUTES[i]; - auto attribLocation = glContext->getAttribLocation(program, name); - if (attribLocation.has_value()) + vector> instances; + for (const auto &weakInstance : list_) { - auto instanceIndex = attribLocation.value().index.value_or(-1); - if (name == "instanceTransform") + if (!weakInstance.expired()) { - for (int i = 0; i < 4; i++) + auto instance = weakInstance.lock(); + if (instance) { - auto matrixRowIndex = instanceIndex + i; - VertexAttribute attrib(name, matrixRowIndex, VertexFormat::kFloat32x4); - callback(attrib, matrixRowIndex, STRIDE, offset); - offset += attrib.byteLength(); - attribsCount += 1; + instances.push_back(instance); } } - else + } + return instances; + } + + void InstanceListBase::clearInstances() + { + for (auto &instance : list_) + { + if (TR_UNLIKELY(instance.expired())) + continue; + auto instancePtr = instance.lock(); + if (TR_UNLIKELY(instancePtr == nullptr)) + continue; + instancePtr->removeHolder(shared_from_this()); + } + list_.clear(); + bufferDataDirty_ = true; + } + + void InstanceListBase::addInstance(shared_ptr instance) + { + if (TR_UNLIKELY(instance == nullptr)) + return; + list_.push_back(instance); + instance->addHolder(shared_from_this()); + bufferDataDirty_ = true; + } + + size_t InstancedMeshBase::iterateInstanceAttributes(shared_ptr program, + function callback) const + { + auto glContext = glContext_.lock(); + if (glContext == nullptr) + return 0; + + size_t attribsCount = 0; + size_t offset = 0; + for (size_t i = 0; i < INSTANCE_ATTRIBUTES.size(); i++) + { + auto &name = INSTANCE_ATTRIBUTES[i]; + auto attribLocation = glContext->getAttribLocation(program, name); + if (attribLocation.has_value()) { - unique_ptr attrib = nullptr; - // 1u - if (name == "instanceLayerIndex" || - name == "instanceBorderStyle") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kUint32); - } - // 1f - else if (name == "instanceUseSDFTexture") + auto instanceIndex = attribLocation.value().index.value_or(-1); + if (name == "instanceTransform") { - attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32); - } - // 2f - else if (name == "instanceTexUvOffset" || - name == "instanceTexUvScale" || - name == "instanceTexUvOffsetR" || - name == "instanceDimensions") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x2); - } - // 4f - else if (name == "instanceColor" || - name == "instanceBorderRadius") - { - attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x4); + for (int i = 0; i < 4; i++) + { + auto matrixRowIndex = instanceIndex + i; + VertexAttribute attrib(name, matrixRowIndex, VertexFormat::kFloat32x4); + callback(attrib, matrixRowIndex, STRIDE, offset); + offset += attrib.byteLength(); + attribsCount += 1; + } } else { - assert(false && "Unknown instance attribute name."); + unique_ptr attrib = nullptr; + // 1u + if (name == "instanceLayerIndex" || + name == "instanceBorderStyle") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kUint32); + } + // 1f + else if (name == "instanceUseSDFTexture") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32); + } + // 2f + else if (name == "instanceTexUvOffset" || + name == "instanceTexUvScale" || + name == "instanceTexUvOffsetR" || + name == "instanceDimensions") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x2); + } + // 4f + else if (name == "instanceColor" || + name == "instanceBorderRadius") + { + attrib = make_unique>(name, instanceIndex, VertexFormat::kFloat32x4); + } + else + { + assert(false && "Unknown instance attribute name."); + } + + assert(attrib != nullptr); + callback(*attrib, instanceIndex, STRIDE, offset); + offset += attrib->byteLength(); + attribsCount += 1; } - - assert(attrib != nullptr); - callback(*attrib, instanceIndex, STRIDE, offset); - offset += attrib->byteLength(); - attribsCount += 1; + } + else + { + cerr << "The instance attribute " << name << " is not found." << endl; } } - else - { - cerr << "The instance attribute " << name << " is not found." << endl; - } + return attribsCount; } - return attribsCount; - } - int InstancedMeshBase::instanceCount() const - { - shared_lock lock(mutex_); - return idToInstanceMap_.size(); - } - - void InstancedMeshBase::iterateInstances(function callback) - { - shared_lock lock(mutex_); - for (auto &[id, instance] : idToInstanceMap_) + int InstancedMeshBase::instanceCount() const { - auto needsUpdate = callback(id, *instance); - if (needsUpdate) - markStructureAsDirty(); + shared_lock lock(mutex_); + return idToInstanceMap_.size(); } - } - Instance &InstancedMeshBase::getInstance(ecs::EntityId id) - { - shared_lock lock(mutex_); - auto it = idToInstanceMap_.find(id); - if (it == idToInstanceMap_.end()) - throw invalid_argument("The instance with the given entity id does not exist."); + void InstancedMeshBase::iterateInstances(function callback) + { + shared_lock lock(mutex_); + for (auto &[id, instance] : idToInstanceMap_) + { + auto needsUpdate = callback(id, *instance); + if (needsUpdate) + markStructureAsDirty(); + } + } - assert(it->second != nullptr); - return *it->second; - } + Instance &InstancedMeshBase::getInstance(ecs::EntityId id) + { + shared_lock lock(mutex_); + auto it = idToInstanceMap_.find(id); + if (it == idToInstanceMap_.end()) + throw invalid_argument("The instance with the given entity id does not exist."); - const Instance &InstancedMeshBase::getInstance(ecs::EntityId id) const - { - shared_lock lock(mutex_); - auto it = idToInstanceMap_.find(id); - if (it == idToInstanceMap_.end()) - throw invalid_argument("The instance with the given entity id does not exist."); + assert(it->second != nullptr); + return *it->second; + } - assert(it->second != nullptr); - return *it->second; - } + const Instance &InstancedMeshBase::getInstance(ecs::EntityId id) const + { + shared_lock lock(mutex_); + auto it = idToInstanceMap_.find(id); + if (it == idToInstanceMap_.end()) + throw invalid_argument("The instance with the given entity id does not exist."); - Instance &InstancedMeshBase::addInstance(ecs::EntityId id) - { - unique_lock lock(mutex_); - if (idToInstanceMap_.find(id) != idToInstanceMap_.end()) - throw invalid_argument("The instance with the given entity id already exists."); + assert(it->second != nullptr); + return *it->second; + } - auto &instance = idToInstanceMap_[id] = make_shared(); - markStructureAsDirty(); - return *instance; - } + Instance &InstancedMeshBase::addInstance(ecs::EntityId id) + { + unique_lock lock(mutex_); + if (idToInstanceMap_.find(id) != idToInstanceMap_.end()) + throw invalid_argument("The instance with the given entity id already exists."); - bool InstancedMeshBase::removeInstance(ecs::EntityId id) - { - unique_lock lock(mutex_); - bool removed = idToInstanceMap_.erase(id) > 0; - if (removed) + auto &instance = idToInstanceMap_[id] = make_shared(); markStructureAsDirty(); - return removed; - } - - void InstancedMeshBase::setup(shared_ptr glContext, shared_ptr mesh3d) - { - assert(glContext != nullptr && "WebGL2 context must not be null."); - assert(mesh3d != nullptr && "Mesh3d must not be null."); + return *instance; + } - glContext_ = glContext; - mesh3d_ = mesh3d; - } + bool InstancedMeshBase::removeInstance(ecs::EntityId id) + { + unique_lock lock(mutex_); + bool removed = idToInstanceMap_.erase(id) > 0; + if (removed) + markStructureAsDirty(); + return removed; + } - void InstancedMeshBase::configureInstanceAttribs(shared_ptr program, shared_ptr mesh3d) - { - auto glContext = glContext_.lock(); - assert(glContext != nullptr && "WebGL2 context must not be null."); - } + void InstancedMeshBase::setup(shared_ptr glContext, shared_ptr mesh3d) + { + assert(glContext != nullptr && "WebGL2 context must not be null."); + assert(mesh3d != nullptr && "Mesh3d must not be null."); - void InstancedMeshBase::updateInstancesList(shared_ptr program, bool ignoreDirty) - { - if (!isStructureDirty_ && !ignoreDirty) - return; + glContext_ = glContext; + mesh3d_ = mesh3d; + } - auto glContext = glContext_.lock(); - auto mesh3d = mesh3d_.lock(); - if (glContext == nullptr || mesh3d == nullptr) + void InstancedMeshBase::configureInstanceAttribs(shared_ptr program, shared_ptr mesh3d) { - cerr << "InstancedMeshBase::updateInstancesList(): WebGL2 context or Mesh3d is not set." - << endl; - return; + auto glContext = glContext_.lock(); + assert(glContext != nullptr && "WebGL2 context must not be null."); } - unique_lock lock(mutex_); - - // Update layered instances based on RenderLayer from instances - set allLayers; - map contentInstanceMaps; - map containerInstanceMaps; - for (auto &[id, instance] : idToInstanceMap_) + void InstancedMeshBase::updateInstancesList(shared_ptr program, bool ignoreDirty) { - if (instance != nullptr) + if (!isStructureDirty_ && !ignoreDirty) + return; + + auto glContext = glContext_.lock(); + auto mesh3d = mesh3d_.lock(); + if (glContext == nullptr || mesh3d == nullptr) { - RenderLayer layer = instance->renderLayer_; - bool shouldInsertLayer = false; - if (!instance->skipToDraw()) - { - contentInstanceMaps[layer][id] = instance; - shouldInsertLayer = true; - } - if (instance->isContainer_) + cerr << "InstancedMeshBase::updateInstancesList(): WebGL2 context or Mesh3d is not set." + << endl; + return; + } + + unique_lock lock(mutex_); + + // Update layered instances based on RenderLayer from instances + set allLayers; + map contentInstanceMaps; + map containerInstanceMaps; + for (auto &[id, instance] : idToInstanceMap_) + { + if (instance != nullptr) { - containerInstanceMaps[layer][id] = instance; - shouldInsertLayer = true; + RenderLayer layer = instance->renderLayer_; + bool shouldInsertLayer = false; + if (!instance->skipToDraw()) + { + contentInstanceMaps[layer][id] = instance; + shouldInsertLayer = true; + } + if (instance->isContainer_) + { + containerInstanceMaps[layer][id] = instance; + shouldInsertLayer = true; + } + if (shouldInsertLayer) + allLayers.insert(layer); } - if (shouldInsertLayer) - allLayers.insert(layer); } - } - // Helper lambda to get or create cached LayeredInstancesData - auto getOrCreateLayerData = [&](RenderLayer layer, uint32_t containerIndex) -> LayeredInstancesData & - { - // Key format: "_" - string key = to_string(static_cast(layer)) + "_" + to_string(containerIndex); - auto it = layerDataPool_.find(key); - if (it == layerDataPool_.end()) + // Helper lambda to get or create cached LayeredInstancesData + auto getOrCreateLayerData = [&](RenderLayer layer, uint32_t containerIndex) -> LayeredInstancesData & { - // Create new LayeredInstancesData with VAO/VBO only once - LayeredInstancesData newLayerData(layer); - - // Initialize container instance for the new layer data - newLayerData.containerInstance = make_shared(containerIndex, - glContext->createVertexArray(), - glContext->createBuffer()); - newLayerData.containerInstance->configureAttribs(glContext, program, mesh3d); + // Key format: "_" + string key = to_string(static_cast(layer)) + "_" + to_string(containerIndex); + auto it = layerDataPool_.find(key); + if (it == layerDataPool_.end()) + { + // Create new LayeredInstancesData with VAO/VBO only once + LayeredInstancesData newLayerData(layer); - // Initialize content instances for the new layer data - newLayerData.contentInstances = make_shared(InstanceFilter::kTransparent, + // Initialize container instance for the new layer data + newLayerData.containerInstance = make_shared(containerIndex, glContext->createVertexArray(), glContext->createBuffer()); - newLayerData.contentInstances->configureAttribs(glContext, program, mesh3d); + newLayerData.containerInstance->configureAttribs(glContext, program, mesh3d); - // Move to the pool and get the iterator - it = layerDataPool_.emplace(key, move(newLayerData)).first; - } - return it->second; - }; + // Initialize content instances for the new layer data + newLayerData.contentInstances = make_shared(InstanceFilter::kTransparent, + glContext->createVertexArray(), + glContext->createBuffer()); + newLayerData.contentInstances->configureAttribs(glContext, program, mesh3d); - // 1. Clear existing layered instances (only pointers, not the actual data) - layeredInstances_.clear(); - - // 2. Initialize default layer data if not already done - if (!defaultLayerData_.has_value()) - { - defaultLayerData_ = LayeredInstancesData(RenderLayer()); // Default layer - defaultLayerData_->containerInstance = nullptr; // No container for default layer - defaultLayerData_->contentInstances = make_shared(InstanceFilter::kTransparent, - glContext->createVertexArray(), - glContext->createBuffer()); - defaultLayerData_->contentInstances->configureAttribs(glContext, program, mesh3d); - } - else - { - // Clear previous content from default layer - defaultLayerData_->contentInstances->clearInstances(); - } + // Move to the pool and get the iterator + it = layerDataPool_.emplace(key, move(newLayerData)).first; + } + return it->second; + }; - // 3. Process each layer - for (const auto &layer : allLayers) - { - // Map to track which container instance each content instance belongs to - unordered_map layeredDataMap; + // 1. Clear existing layered instances (only pointers, not the actual data) + layeredInstances_.clear(); - // 3.1 Process container instances for this layer - bool ownsContainer = containerInstanceMaps.find(layer) != containerInstanceMaps.end(); - if (ownsContainer) + // 2. Initialize default layer data if not already done + if (!defaultLayerData_.has_value()) { - uint32_t containerIndex = 0; - for (const auto &[id, instance] : containerInstanceMaps[layer]) - { - LayeredInstancesData &layerData = getOrCreateLayerData(layer, containerIndex++); - layerData.containerInstance->setInstance(instance); - layerData.contentInstances->clearInstances(); // Clear previous content to rebuild - layeredDataMap[id] = &layerData; - } + defaultLayerData_ = LayeredInstancesData(RenderLayer()); // Default layer + defaultLayerData_->containerInstance = nullptr; // No container for default layer + defaultLayerData_->contentInstances = make_shared(InstanceFilter::kTransparent, + glContext->createVertexArray(), + glContext->createBuffer()); + defaultLayerData_->contentInstances->configureAttribs(glContext, program, mesh3d); + } + else + { + // Clear previous content from default layer + defaultLayerData_->contentInstances->clearInstances(); } - // 3.2 Process content instances for this layer - if (contentInstanceMaps.find(layer) != contentInstanceMaps.end()) + // 3. Process each layer + for (const auto &layer : allLayers) { - for (const auto &[id, instance] : contentInstanceMaps[layer]) + // Map to track which container instance each content instance belongs to + unordered_map layeredDataMap; + + // 3.1 Process container instances for this layer + bool ownsContainer = containerInstanceMaps.find(layer) != containerInstanceMaps.end(); + if (ownsContainer) { - auto belongsToContainer = layeredDataMap.find(instance->belongsToContainerId_) != layeredDataMap.end(); - if (!belongsToContainer) + uint32_t containerIndex = 0; + for (const auto &[id, instance] : containerInstanceMaps[layer]) { - defaultLayerData_->contentInstances->addInstance(instance); - } - else - { - LayeredInstancesData *layerData = layeredDataMap[instance->belongsToContainerId_]; - if (layerData != nullptr && layerData->contentInstances != nullptr) - layerData->contentInstances->addInstance(instance); + LayeredInstancesData &layerData = getOrCreateLayerData(layer, containerIndex++); + layerData.containerInstance->setInstance(instance); + layerData.contentInstances->clearInstances(); // Clear previous content to rebuild + layeredDataMap[id] = &layerData; } } - } - // 3.3 Add active layers to the layeredInstances_ list - { - // Add default layer to the active instances if it was used - if (defaultLayerData_->contentInstances->count() > 0) + // 3.2 Process content instances for this layer + if (contentInstanceMaps.find(layer) != contentInstanceMaps.end()) { - defaultLayerData_->sortContentInstances(); - defaultLayerData_->contentInstances->markTextureDataAsDirty(); - layeredInstances_.push_back(&defaultLayerData_.value()); + for (const auto &[id, instance] : contentInstanceMaps[layer]) + { + auto belongsToContainer = layeredDataMap.find(instance->belongsToContainerId_) != layeredDataMap.end(); + if (!belongsToContainer) + { + defaultLayerData_->contentInstances->addInstance(instance); + } + else + { + LayeredInstancesData *layerData = layeredDataMap[instance->belongsToContainerId_]; + if (layerData != nullptr && layerData->contentInstances != nullptr) + layerData->contentInstances->addInstance(instance); + } + } } - // Add container-specific layer data to the active instances - for (auto &[_, layerData] : layeredDataMap) + // 3.3 Add active layers to the layeredInstances_ list { - layerData->sortContentInstances(); - layerData->contentInstances->markTextureDataAsDirty(); - layeredInstances_.push_back(layerData); + // Add default layer to the active instances if it was used + if (defaultLayerData_->contentInstances->count() > 0) + { + defaultLayerData_->sortContentInstances(); + defaultLayerData_->contentInstances->markTextureDataAsDirty(); + layeredInstances_.push_back(&defaultLayerData_.value()); + } + + // Add container-specific layer data to the active instances + for (auto &[_, layerData] : layeredDataMap) + { + layerData->sortContentInstances(); + layerData->contentInstances->markTextureDataAsDirty(); + layeredInstances_.push_back(layerData); + } } } - } - // 4. Mark the structure as clean after updating. - isStructureDirty_ = false; + // 4. Mark the structure as clean after updating. + isStructureDirty_ = false; + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/instanced_mesh.hpp b/src/client/builtin_scene/instanced_mesh.hpp index 0de19a266..dd24a0db5 100644 --- a/src/client/builtin_scene/instanced_mesh.hpp +++ b/src/client/builtin_scene/instanced_mesh.hpp @@ -20,165 +20,167 @@ #include "./render_layer.hpp" #include "./css_border_data_texture.hpp" -namespace builtin_scene +namespace endor { - // Forward declarations - class Mesh3d; - class InstanceListBase; - namespace materials + namespace builtin_scene { - class WebContentInstancedMaterial; - } - - struct InstanceData - { - InstanceData() - : transform(1.0f) - , color(1.0f, 1.0f, 1.0f, 0.0f) - , texUvOffset(0.0f, 0.0f) - , texUvOffsetR(0.0f, 0.0f) - , texUvScale(1.0f, 1.0f) - , texLayerIndex(0) - , dimensions(0.0f, 0.0f) - , borderRadius(0.0f, 0.0f, 0.0f, 0.0f) - , borderStyle(0.0f) - , enableSDFTexture(0.0f) - { - } - glm::mat4 transform; /** element transformation */ - glm::vec4 color; /** background color */ - glm::vec2 texUvOffset; /** Left or default view texture coordinates */ - glm::vec2 texUvOffsetR; /** Right eye texture coordinates */ - glm::vec2 texUvScale; /** Shared texture scale for both eyes */ - uint32_t texLayerIndex; /** Shared texture layer for both eyes */ - glm::vec2 dimensions; /** The dimensions */ - glm::vec4 borderRadius; /** Border radius for each corner (top-left, top-right, bottom-right, bottom-left) */ - uint32_t borderStyle; /** Border style (0=none, 1=solid, 2=dashed) */ - float enableSDFTexture; /** Whether to use SDF texture rendering (0.0=regular, 1.0=SDF) */ - - friend std::ostream &operator<<(std::ostream &os, const InstanceData &data) - { - os << "InstanceData(" << std::endl - << " transform=" << math3d::to_string(data.transform) << std::endl - << " color=" << math3d::to_string(data.color) << std::endl - << " texUvOffset=" << math3d::to_string(data.texUvOffset) << std::endl - << " texUvScale=" << math3d::to_string(data.texUvScale) << std::endl - << " texLayerIndex=" << data.texLayerIndex << std::endl - << " texUvOffsetR=" << math3d::to_string(data.texUvOffsetR) << std::endl - << " dimensions=" << math3d::to_string(data.dimensions) << std::endl - << " borderRadius=" << math3d::to_string(data.borderRadius) << std::endl - << " borderStyle=" << data.borderStyle << std::endl - << " enableSDFTexture=" << data.enableSDFTexture << std::endl - << ")"; - return os; - } - - // If the instance is transparent(alpha = 0.0f). - inline bool isTransparent() const - { - return color.a == 0.0f; - } - - // If the instance has a none border style. - inline bool isBorderNone() const - { - return borderStyle == 0; - } - - // If the instance own texture to draw. - inline bool ownTexture() const - { - return texUvScale.x > 0.0f || texUvScale.y > 0.0f; - } - }; - - class Instance - { - friend class InstancedMeshBase; - friend class InstanceListBase; - friend class ContainerInstance; - friend class ContentInstancesList; - - private: - class TextureCoordBase : public std::array - { - public: - TextureCoordBase() - : std::array({0.0f, 0.0f}) + // Forward declarations + class Mesh3d; + class InstanceListBase; + namespace materials + { + class WebContentInstancedMaterial; + } + + struct InstanceData + { + InstanceData() + : transform(1.0f) + , color(1.0f, 1.0f, 1.0f, 0.0f) + , texUvOffset(0.0f, 0.0f) + , texUvOffsetR(0.0f, 0.0f) + , texUvScale(1.0f, 1.0f) + , texLayerIndex(0) + , dimensions(0.0f, 0.0f) + , borderRadius(0.0f, 0.0f, 0.0f, 0.0f) + , borderStyle(0.0f) + , enableSDFTexture(0.0f) { } - TextureCoordBase(const std::array &array) - : std::array(array) + glm::mat4 transform; /** element transformation */ + glm::vec4 color; /** background color */ + glm::vec2 texUvOffset; /** Left or default view texture coordinates */ + glm::vec2 texUvOffsetR; /** Right eye texture coordinates */ + glm::vec2 texUvScale; /** Shared texture scale for both eyes */ + uint32_t texLayerIndex; /** Shared texture layer for both eyes */ + glm::vec2 dimensions; /** The dimensions */ + glm::vec4 borderRadius; /** Border radius for each corner (top-left, top-right, bottom-right, bottom-left) */ + uint32_t borderStyle; /** Border style (0=none, 1=solid, 2=dashed) */ + float enableSDFTexture; /** Whether to use SDF texture rendering (0.0=regular, 1.0=SDF) */ + + friend std::ostream &operator<<(std::ostream &os, const InstanceData &data) { + os << "InstanceData(" << std::endl + << " transform=" << math3d::to_string(data.transform) << std::endl + << " color=" << math3d::to_string(data.color) << std::endl + << " texUvOffset=" << math3d::to_string(data.texUvOffset) << std::endl + << " texUvScale=" << math3d::to_string(data.texUvScale) << std::endl + << " texLayerIndex=" << data.texLayerIndex << std::endl + << " texUvOffsetR=" << math3d::to_string(data.texUvOffsetR) << std::endl + << " dimensions=" << math3d::to_string(data.dimensions) << std::endl + << " borderRadius=" << math3d::to_string(data.borderRadius) << std::endl + << " borderStyle=" << data.borderStyle << std::endl + << " enableSDFTexture=" << data.enableSDFTexture << std::endl + << ")"; + return os; } - public: - inline float u() const + // If the instance is transparent(alpha = 0.0f). + inline bool isTransparent() const { - return (*this)[0]; - } - inline float v() const - { - return (*this)[1]; + return color.a == 0.0f; } - operator glm::vec2() const - { - return glm::vec2(u(), v()); - } - bool operator==(const TextureCoordBase &other) const + // If the instance has a none border style. + inline bool isBorderNone() const { - return (u() == other.u() && v() == other.v()); + return borderStyle == 0; } - bool operator==(const glm::vec2 &other) const + + // If the instance own texture to draw. + inline bool ownTexture() const { - return (u() == other.x && v() == other.y); + return texUvScale.x > 0.0f || texUvScale.y > 0.0f; } }; - public: - class TextureScale : public TextureCoordBase + class Instance { - using TextureCoordBase::TextureCoordBase; + friend class InstancedMeshBase; + friend class InstanceListBase; + friend class ContainerInstance; + friend class ContentInstancesList; - public: - void setHalfWidth() + private: + class TextureCoordBase : public std::array { - (*this)[0] *= 0.5f; - } - }; - class TextureOffset : public TextureCoordBase - { - using TextureCoordBase::TextureCoordBase; + public: + TextureCoordBase() + : std::array({0.0f, 0.0f}) + { + } + TextureCoordBase(const std::array &array) + : std::array(array) + { + } + + public: + inline float u() const + { + return (*this)[0]; + } + inline float v() const + { + return (*this)[1]; + } + + operator glm::vec2() const + { + return glm::vec2(u(), v()); + } + bool operator==(const TextureCoordBase &other) const + { + return (u() == other.u() && v() == other.v()); + } + bool operator==(const glm::vec2 &other) const + { + return (u() == other.x && v() == other.y); + } + }; public: - void setForRight(const TextureScale &scale) + class TextureScale : public TextureCoordBase { - (*this)[0] += scale.u() * 0.5f; // Offset right eye UVs by half the width - } - }; + using TextureCoordBase::TextureCoordBase; + + public: + void setHalfWidth() + { + (*this)[0] *= 0.5f; + } + }; + class TextureOffset : public TextureCoordBase + { + using TextureCoordBase::TextureCoordBase; + + public: + void setForRight(const TextureScale &scale) + { + (*this)[0] += scale.u() * 0.5f; // Offset right eye UVs by half the width + } + }; - public: - Instance() = default; - - public: - void randomColor(); - bool setColor(const glm::vec4 &color); - void setTransform(const glm::mat4 &transformationMatrix); - void setTexture(TextureOffset uvOffset, - TextureOffset uvOffsetR, - TextureScale uvScale, - uint32_t layerIndex); - void disableTexture(); - void setDimensions(float width, float height); - void setBorderRadius(glm::vec4 borderRadius); - void setBorderRadius(float topLeft, float topRight, float bottomRight, float bottomLeft); - void setBorderWidth(glm::vec4 borderWidth); - void setBorderWidth(float top, float right, float bottom, float left); - void setBorderColor(glm::vec4 borderColor); - void setBorderColor(float r, float g, float b, float a); - void setBorderStyle(float borderStyle); - void setSDFTextureEnabled(bool); + public: + Instance() = default; + + public: + void randomColor(); + bool setColor(const glm::vec4 &color); + void setTransform(const glm::mat4 &transformationMatrix); + void setTexture(TextureOffset uvOffset, + TextureOffset uvOffsetR, + TextureScale uvScale, + uint32_t layerIndex); + void disableTexture(); + void setDimensions(float width, float height); + void setBorderRadius(glm::vec4 borderRadius); + void setBorderRadius(float topLeft, float topRight, float bottomRight, float bottomLeft); + void setBorderWidth(glm::vec4 borderWidth); + void setBorderWidth(float top, float right, float bottom, float left); + void setBorderColor(glm::vec4 borderColor); + void setBorderColor(float r, float g, float b, float a); + void setBorderStyle(float borderStyle); + void setSDFTextureEnabled(bool); #define IMPL_SETTER(NAME, PRIV_FIELD, TYPE) \ inline bool set##NAME(TYPE value) \ @@ -197,271 +199,271 @@ namespace builtin_scene #define IMPL_U32_SETTER(NAME, PRIV_FIELD) \ IMPL_SETTER(NAME, PRIV_FIELD, uint32_t) - IMPL_BOOL_SETTER(Enabled, enabled_) - IMPL_BOOL_SETTER(Opaque, isOpaque_) - IMPL_BOOL_SETTER(IsContainer, isContainer_) - IMPL_U32_SETTER(BelongsToContainerId, belongsToContainerId_) - IMPL_SETTER(RenderQueue, renderQueue_, RenderQueue) - IMPL_SETTER(RenderLayer, renderLayer_, RenderLayer) + IMPL_BOOL_SETTER(Enabled, enabled_) + IMPL_BOOL_SETTER(Opaque, isOpaque_) + IMPL_BOOL_SETTER(IsContainer, isContainer_) + IMPL_U32_SETTER(BelongsToContainerId, belongsToContainerId_) + IMPL_SETTER(RenderQueue, renderQueue_, RenderQueue) + IMPL_SETTER(RenderLayer, renderLayer_, RenderLayer) #undef IMPL_U32_SETTER #undef IMPL_BOOL_SETTER #undef IMPL_SETTER - // Getter for RenderQueue - inline const RenderQueue &getRenderQueue() const - { - return renderQueue_; - } - - // Returns if the instance might be invisible based on its properties. - inline bool maybeInvisible() const - { - return maybeInvisible_; - } + // Getter for RenderQueue + inline const RenderQueue &getRenderQueue() const + { + return renderQueue_; + } - // Getter for instance data - inline const InstanceData &data() const - { - return data_; - } + // Returns if the instance might be invisible based on its properties. + inline bool maybeInvisible() const + { + return maybeInvisible_; + } - // Getters for border fields - inline const glm::vec4 &getBorderWidths() const - { - return borderWidths_; - } + // Getter for instance data + inline const InstanceData &data() const + { + return data_; + } - inline const glm::vec4 &getBorderColor(int side) const - { - return borderColors_[side % 4]; - } + // Getters for border fields + inline const glm::vec4 &getBorderWidths() const + { + return borderWidths_; + } - inline const glm::vec4 *getBorderColors() const - { - return borderColors_; - } + inline const glm::vec4 &getBorderColor(int side) const + { + return borderColors_[side % 4]; + } - // Returns if this instance has no borders to draw. - bool hasNoBorders() const; - - private: - // Add a holder to the instance. - void addHolder(std::shared_ptr holder); - // Remove a holder from the instance. - void removeHolder(std::shared_ptr holder); - // Notify the holders that buffer data has changed. - void notifyBufferDataChanged(); - // Notify the holders that texture data has changed. - void notifyTextureDataChanged(); - // Returns `true` if the instance should be skipped to draw. - bool skipToDraw() const; - // Set the instance as maybe invisible based on the state of the instance, such as if it has a transparent color, - // no texture, and no borders to draw, etc. - // - // This method should be called when related properties are changed, such as color, texture, border, etc. - void setMaybeInvisible(); - - private: - InstanceData data_; - glm::vec4 borderWidths_; - glm::vec4 borderColors_[4]; - RenderQueue renderQueue_; - RenderLayer renderLayer_; - bool enabled_ = false; - bool maybeInvisible_ = true; - bool isOpaque_ = false; - - bool isContainer_ = false; - uint32_t belongsToContainerId_ = 0; - - private: - std::vector> holders_; - }; - - enum class InstanceFilter - { - kAll, - kOpaque, - kTransparent - }; + inline const glm::vec4 *getBorderColors() const + { + return borderColors_; + } - using InstanceMap = std::unordered_map>; + // Returns if this instance has no borders to draw. + bool hasNoBorders() const; + + private: + // Add a holder to the instance. + void addHolder(std::shared_ptr holder); + // Remove a holder from the instance. + void removeHolder(std::shared_ptr holder); + // Notify the holders that buffer data has changed. + void notifyBufferDataChanged(); + // Notify the holders that texture data has changed. + void notifyTextureDataChanged(); + // Returns `true` if the instance should be skipped to draw. + bool skipToDraw() const; + // Set the instance as maybe invisible based on the state of the instance, such as if it has a transparent color, + // no texture, and no borders to draw, etc. + // + // This method should be called when related properties are changed, such as color, texture, border, etc. + void setMaybeInvisible(); + + private: + InstanceData data_; + glm::vec4 borderWidths_; + glm::vec4 borderColors_[4]; + RenderQueue renderQueue_; + RenderLayer renderLayer_; + bool enabled_ = false; + bool maybeInvisible_ = true; + bool isOpaque_ = false; + + bool isContainer_ = false; + uint32_t belongsToContainerId_ = 0; + + private: + std::vector> holders_; + }; - class InstanceListBase : public std::enable_shared_from_this - { - friend class Instance; - friend class InstancedMeshBase; + enum class InstanceFilter + { + kAll, + kOpaque, + kTransparent + }; - public: - InstanceListBase(std::shared_ptr vao, - std::shared_ptr vbo); - virtual ~InstanceListBase() = default; + using InstanceMap = std::unordered_map>; - public: - virtual bool isContainerInstance() const - { - return false; - } - virtual bool isContentInstancesList() const + class InstanceListBase : public std::enable_shared_from_this { - return false; - } + friend class Instance; + friend class InstancedMeshBase; - inline size_t count() const - { - return list_.size(); - } - inline bool isBufferDataDirty() const - { - return bufferDataDirty_; - } + public: + InstanceListBase(std::shared_ptr vao, + std::shared_ptr vbo); + virtual ~InstanceListBase() = default; - /** + public: + virtual bool isContainerInstance() const + { + return false; + } + virtual bool isContentInstancesList() const + { + return false; + } + + inline size_t count() const + { + return list_.size(); + } + inline bool isBufferDataDirty() const + { + return bufferDataDirty_; + } + + /** * Configure the instance attributes for the given WebGL program. */ - size_t configureAttribs(std::shared_ptr glContext, - std::shared_ptr program, - std::shared_ptr mesh3d); + size_t configureAttribs(std::shared_ptr glContext, + std::shared_ptr program, + std::shared_ptr mesh3d); - size_t copyToArrayData(vector &dst); + size_t copyToArrayData(vector &dst); - void beforeInstancedDraw(client_graphics::WebGL2Context &glContext); - void afterInstancedDraw(client_graphics::WebGL2Context &glContext); + void beforeInstancedDraw(client_graphics::WebGL2Context &glContext); + void afterInstancedDraw(client_graphics::WebGL2Context &glContext); - /** + /** * Get the current instances as a vector (for border data updates). */ - std::vector> getInstances() const; + std::vector> getInstances() const; - protected: - // Clear the instances. - virtual void clearInstances(); - // Add an instance to the list. - void addInstance(std::shared_ptr instance); + protected: + // Clear the instances. + virtual void clearInstances(); + // Add an instance to the list. + void addInstance(std::shared_ptr instance); - inline void markBufferAsDirty() - { - bufferDataDirty_ = true; - } + inline void markBufferAsDirty() + { + bufferDataDirty_ = true; + } - public: - std::shared_ptr vao; - std::shared_ptr instanceVbo; + public: + std::shared_ptr vao; + std::shared_ptr instanceVbo; - protected: - std::vector> list_; + protected: + std::vector> list_; - private: - bool bufferDataDirty_ = true; - }; + private: + bool bufferDataDirty_ = true; + }; - // Derived class for container instances (with container ID) - class ContainerInstance : public InstanceListBase - { - public: - ContainerInstance(uint32_t containerIndex, - std::shared_ptr vao, - std::shared_ptr vbo) - : InstanceListBase(vao, vbo) - , containerIndex_(containerIndex) - , belongsToContainerId_(std::nullopt) + // Derived class for container instances (with container ID) + class ContainerInstance : public InstanceListBase { - } + public: + ContainerInstance(uint32_t containerIndex, + std::shared_ptr vao, + std::shared_ptr vbo) + : InstanceListBase(vao, vbo) + , containerIndex_(containerIndex) + , belongsToContainerId_(std::nullopt) + { + } - bool isContainerInstance() const override - { - return true; - } + bool isContainerInstance() const override + { + return true; + } - /** + /** * Set a single instance for this container. */ - void setInstance(std::shared_ptr instance); + void setInstance(std::shared_ptr instance); - uint32_t getContainerIndex() const - { - return containerIndex_; - } - std::optional getBelongsToContainerId() const - { - return belongsToContainerId_; - } - void setBelongsToContainerId(uint32_t id) - { - belongsToContainerId_ = id; - } + uint32_t getContainerIndex() const + { + return containerIndex_; + } + std::optional getBelongsToContainerId() const + { + return belongsToContainerId_; + } + void setBelongsToContainerId(uint32_t id) + { + belongsToContainerId_ = id; + } - private: - uint32_t containerIndex_; - std::optional belongsToContainerId_; - }; + private: + uint32_t containerIndex_; + std::optional belongsToContainerId_; + }; - // Derived class for content instances list - class ContentInstancesList : public InstanceListBase - { - friend struct LayeredInstancesData; + // Derived class for content instances list + class ContentInstancesList : public InstanceListBase + { + friend struct LayeredInstancesData; - public: - /** + public: + /** * The sorting order of the instances. */ - enum SortingOrder - { - kNone, - kFrontToBack, - kBackToFront - }; + enum SortingOrder + { + kNone, + kFrontToBack, + kBackToFront + }; - public: - ContentInstancesList(InstanceFilter filter, - std::shared_ptr vao, - std::shared_ptr vbo) - : InstanceListBase(vao, vbo) - , textureDataDirty_(true) - { - } + public: + ContentInstancesList(InstanceFilter filter, + std::shared_ptr vao, + std::shared_ptr vbo) + : InstanceListBase(vao, vbo) + , textureDataDirty_(true) + { + } - friend std::ostream &operator<<(std::ostream &os, const ContentInstancesList &list) - { - os << "ContentInstancesList(count=" << list.count() << ")" - << std::endl; - return os; - } + friend std::ostream &operator<<(std::ostream &os, const ContentInstancesList &list) + { + os << "ContentInstancesList(count=" << list.count() << ")" + << std::endl; + return os; + } - bool isContentInstancesList() const override - { - return true; - } - inline bool isTextureDataDirty() const - { - return textureDataDirty_; - } - inline void markTextureDataAsDirty() - { - textureDataDirty_ = true; - } + bool isContentInstancesList() const override + { + return true; + } + inline bool isTextureDataDirty() const + { + return textureDataDirty_; + } + inline void markTextureDataAsDirty() + { + textureDataDirty_ = true; + } - /** + /** * Update the renderable instances list with the given instances. * * @param instances The instances to update. * @param sortingOrder The sorting order of the instances. */ - void update(const InstanceMap &instances, SortingOrder sortingOrder = SortingOrder::kNone); - void beforeInstancedDraw(client_graphics::WebGL2Context &glContext, CSSBorderDataTexture *borderDataTexture); + void update(const InstanceMap &instances, SortingOrder sortingOrder = SortingOrder::kNone); + void beforeInstancedDraw(client_graphics::WebGL2Context &glContext, CSSBorderDataTexture *borderDataTexture); - void clearInstances() override; - void sortInstances(SortingOrder sortingOrder); + void clearInstances() override; + void sortInstances(SortingOrder sortingOrder); - public: - InstanceFilter filter; + public: + InstanceFilter filter; - private: - bool textureDataDirty_; - }; + private: + bool textureDataDirty_; + }; - /** + /** * LayeredInstancesData represents a single container and its content for isolated rendering * * This structure supports per-container stencil rendering to prevent content leakage: @@ -469,125 +471,125 @@ namespace builtin_scene * - Contains the layer, container ID, container instance and content instances * - Enables isolated mask/content rendering per container */ - struct LayeredInstancesData - { - LayeredInstancesData() = default; - LayeredInstancesData(RenderLayer layer) - : layer(layer) + struct LayeredInstancesData { - } + LayeredInstancesData() = default; + LayeredInstancesData(RenderLayer layer) + : layer(layer) + { + } - RenderLayer layer; - std::shared_ptr containerInstance; // Single container mask - std::shared_ptr contentInstances; // Content for this container + RenderLayer layer; + std::shared_ptr containerInstance; // Single container mask + std::shared_ptr contentInstances; // Content for this container - void sortContentInstances() + void sortContentInstances() + { + if (contentInstances) + contentInstances->sortInstances(ContentInstancesList::SortingOrder::kFrontToBack); + } + }; + + class InstancedMeshBase { - if (contentInstances) - contentInstances->sortInstances(ContentInstancesList::SortingOrder::kFrontToBack); - } - }; + friend class RenderSystem; + friend class materials::WebContentInstancedMaterial; - class InstancedMeshBase - { - friend class RenderSystem; - friend class materials::WebContentInstancedMaterial; - - public: - static constexpr size_t STRIDE = sizeof(InstanceData); - static inline std::vector INSTANCE_ATTRIBUTES = {"instanceTransform", - "instanceColor", - "instanceTexUvOffset", - "instanceTexUvOffsetR", - "instanceTexUvScale", - "instanceLayerIndex", - "instanceDimensions", - "instanceBorderRadius", - "instanceBorderStyle", - "instanceUseSDFTexture"}; - - public: - InstancedMeshBase() = default; - virtual ~InstancedMeshBase() = default; - - public: - /** + public: + static constexpr size_t STRIDE = sizeof(InstanceData); + static inline std::vector INSTANCE_ATTRIBUTES = {"instanceTransform", + "instanceColor", + "instanceTexUvOffset", + "instanceTexUvOffsetR", + "instanceTexUvScale", + "instanceLayerIndex", + "instanceDimensions", + "instanceBorderRadius", + "instanceBorderStyle", + "instanceUseSDFTexture"}; + + public: + InstancedMeshBase() = default; + virtual ~InstancedMeshBase() = default; + + public: + /** * Iterate the instance attributes with the given WebGL program. * * @param program The WebGL program to iterate the instance attributes with. * @param callback The callback to call for each instance attribute. * @returns The number of instance attributes. */ - size_t iterateInstanceAttributes(std::shared_ptr program, - std::function callback) const; - /** + size_t iterateInstanceAttributes(std::shared_ptr program, + std::function callback) const; + /** * Get the instance count of this mesh. * * @returns The instance count. */ - int instanceCount() const; - /** + int instanceCount() const; + /** * Iterate the instances with the given callback. * * @param callback The callback to call for each instance. The callback should return `true` if the instance needs * to update the structure, otherwise `false`. */ - void iterateInstances(std::function callback); - /** + void iterateInstances(std::function callback); + /** * Get the instance with the given entity id. * * @param id The entity id of the instance. * @returns The instance reference with the given entity id. */ - Instance &getInstance(ecs::EntityId id); - /** + Instance &getInstance(ecs::EntityId id); + /** * Get the instance with the given entity id. * * @param id The entity id of the instance. * @returns The `const` instance reference with the given entity id. */ - const Instance &getInstance(ecs::EntityId id) const; - /** + const Instance &getInstance(ecs::EntityId id) const; + /** * Add a new instance to the mesh. * * @param id The entity id of the instance. * @throws std::invalid_argument If the instance with the given entity id already exists. */ - Instance &addInstance(ecs::EntityId id); - /** + Instance &addInstance(ecs::EntityId id); + /** * Remove the instance with the given entity id. */ - bool removeInstance(ecs::EntityId id); + bool removeInstance(ecs::EntityId id); - inline size_t countLayers() const - { - return layeredInstances_.size(); - } - - using LayerCallback = std::function; - inline void iterateLayers(LayerCallback callback) const - { - shared_lock lock(mutex_); - for (const auto *layerData : layeredInstances_) + inline size_t countLayers() const { - ContainerInstance *containerInstance = - (layerData->containerInstance && layerData->containerInstance->count() > 0) - ? layerData->containerInstance.get() - : nullptr; + return layeredInstances_.size(); + } - if (containerInstance) + using LayerCallback = std::function; + inline void iterateLayers(LayerCallback callback) const + { + shared_lock lock(mutex_); + for (const auto *layerData : layeredInstances_) { - callback(layerData->layer, containerInstance, layerData->contentInstances.get()); + ContainerInstance *containerInstance = + (layerData->containerInstance && layerData->containerInstance->count() > 0) + ? layerData->containerInstance.get() + : nullptr; + + if (containerInstance) + { + callback(layerData->layer, containerInstance, layerData->contentInstances.get()); + } } } - } - /** + /** * Whether to dispatch a depth-only pass for transparent objects which writes the transparent objects' depth * to the depth buffer. * @@ -596,84 +598,85 @@ namespace builtin_scene * we can write the depth buffer of the transparent objects after rendering them, so that the collision detectior * can read the correct depth value for the GUI elements. */ - inline bool isDepthOnlyPassEnabled() const - { - return isDepthOnlyPassEnabled_; - } - inline void enableDepthOnlyPass(bool value = true) - { - isDepthOnlyPassEnabled_ = value; - } + inline bool isDepthOnlyPassEnabled() const + { + return isDepthOnlyPassEnabled_; + } + inline void enableDepthOnlyPass(bool value = true) + { + isDepthOnlyPassEnabled_ = value; + } - protected: - /** + protected: + /** * Setup the instanced mesh with the given instance VBO. * * @param glContext The WebGL2 context to setup. * @param opaqueVao The instance VBO to setup. */ - void setup(std::shared_ptr glContext, - std::shared_ptr mesh3d = nullptr); - void configureInstanceAttribs(std::shared_ptr program, - std::shared_ptr mesh3d); - /** + void setup(std::shared_ptr glContext, + std::shared_ptr mesh3d = nullptr); + void configureInstanceAttribs(std::shared_ptr program, + std::shared_ptr mesh3d); + /** * Update the internal `idToInstanceMap_` into the `layeredInstances_` and `depthOnlyInstances_`. * * @param ignoreDirty Whether to ignore the dirty flag, `true` means force update. */ - void updateInstancesList(std::shared_ptr program, bool ignoreDirty = false); + void updateInstancesList(std::shared_ptr program, bool ignoreDirty = false); - private: - inline void markStructureAsDirty() - { - isStructureDirty_ = true; - } + private: + inline void markStructureAsDirty() + { + isStructureDirty_ = true; + } - protected: - mutable std::shared_mutex mutex_; - InstanceMap idToInstanceMap_; - std::vector layeredInstances_; // Store pointers to active LayeredInstancesData objects + protected: + mutable std::shared_mutex mutex_; + InstanceMap idToInstanceMap_; + std::vector layeredInstances_; // Store pointers to active LayeredInstancesData objects - private: - std::weak_ptr glContext_; - std::weak_ptr mesh3d_; - bool isDepthOnlyPassEnabled_ = false; - bool isStructureDirty_ = true; + private: + std::weak_ptr glContext_; + std::weak_ptr mesh3d_; + bool isDepthOnlyPassEnabled_ = false; + bool isStructureDirty_ = true; - // Caching/pooling system for LayeredInstancesData objects - std::optional defaultLayerData_; // Default layer for non-container instances - std::unordered_map layerDataPool_; // Pool for layer+container combinations - }; + // Caching/pooling system for LayeredInstancesData objects + std::optional defaultLayerData_; // Default layer for non-container instances + std::unordered_map layerDataPool_; // Pool for layer+container combinations + }; - /** + /** * An instanced mesh which is a collection of sub-meshes that can be rendered together. */ - template - requires std::is_base_of::value - class InstancedMesh final : public InstancedMeshBase, - public MeshType + template + requires std::is_base_of::value + class InstancedMesh final : public InstancedMeshBase, + public MeshType - { - public: - template - InstancedMesh(const std::string &name, InitMeshArgs &&...args) - : InstancedMeshBase() - , MeshType(std::forward(args)...) { - } + public: + template + InstancedMesh(const std::string &name, InitMeshArgs &&...args) + : InstancedMeshBase() + , MeshType(std::forward(args)...) + { + } - public: - float area() override - { - return 0.0f; - } - float volume() override - { - return 0.0f; - } + public: + float area() override + { + return 0.0f; + } + float volume() override + { + return 0.0f; + } - public: - void onMesh3dInitialized(std::shared_ptr, std::shared_ptr) override; - void onConfigureInstanceAttribs(std::shared_ptr, std::shared_ptr) override; - }; -} + public: + void onMesh3dInitialized(std::shared_ptr, std::shared_ptr) override; + void onConfigureInstanceAttribs(std::shared_ptr, std::shared_ptr) override; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/material_base.cpp b/src/client/builtin_scene/material_base.cpp index 3bc24f239..877c9317d 100644 --- a/src/client/builtin_scene/material_base.cpp +++ b/src/client/builtin_scene/material_base.cpp @@ -1,66 +1,69 @@ #include "./material_base.hpp" #include "./meshes.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - - void Material::SetGlobalDefines(const string &define) + namespace builtin_scene { - if (find(GlobalDefines.begin(), GlobalDefines.end(), define) == GlobalDefines.end()) - GlobalDefines.push_back(define); - } + using namespace std; - void Material::UnsetGlobalDefines(const string &define) - { - auto it = find(GlobalDefines.begin(), GlobalDefines.end(), define); - if (it != GlobalDefines.end()) - GlobalDefines.erase(it); - } + void Material::SetGlobalDefines(const string &define) + { + if (find(GlobalDefines.begin(), GlobalDefines.end(), define) == GlobalDefines.end()) + GlobalDefines.push_back(define); + } - bool Material::initialize(shared_ptr glContext, - shared_ptr program) - { - if (TR_UNLIKELY(glContext == nullptr || program == nullptr)) - return false; - glContext_ = glContext; - return true; - } + void Material::UnsetGlobalDefines(const string &define) + { + auto it = find(GlobalDefines.begin(), GlobalDefines.end(), define); + if (it != GlobalDefines.end()) + GlobalDefines.erase(it); + } - void Material::drawMeshImpl(shared_ptr program, - const Mesh3d &mesh, - RenderPass renderPass, - optional renderTarget) - { - auto glContext = glContext_.lock(); - if (glContext == nullptr) [[unlikely]] - return; + bool Material::initialize(shared_ptr glContext, + shared_ptr program) + { + if (TR_UNLIKELY(glContext == nullptr || program == nullptr)) + return false; + glContext_ = glContext; + return true; + } - glContext->drawElements(mesh.primitiveTopology(), - mesh.indices().size(), - WEBGL_UNSIGNED_INT, - 0); - } + void Material::drawMeshImpl(shared_ptr program, + const Mesh3d &mesh, + RenderPass renderPass, + optional renderTarget) + { + auto glContext = glContext_.lock(); + if (glContext == nullptr) [[unlikely]] + return; - void Material::onBeforeDrawMesh(shared_ptr program, shared_ptr mesh) - { - } + glContext->drawElements(mesh.primitiveTopology(), + mesh.indices().size(), + WEBGL_UNSIGNED_INT, + 0); + } - void Material::onAfterDrawMesh(shared_ptr program, shared_ptr mesh) - { - } + void Material::onBeforeDrawMesh(shared_ptr program, shared_ptr mesh) + { + } - const vector Material::mixDefines(const vector &baseDefines, - const vector &definesToAdd) const - { - vector result = definesToAdd; + void Material::onAfterDrawMesh(shared_ptr program, shared_ptr mesh) + { + } - // Ignore duplicates. - for (const auto &define : baseDefines) + const vector Material::mixDefines(const vector &baseDefines, + const vector &definesToAdd) const { - if (find(result.begin(), result.end(), define) == result.end()) - result.push_back(define); + vector result = definesToAdd; + + // Ignore duplicates. + for (const auto &define : baseDefines) + { + if (find(result.begin(), result.end(), define) == result.end()) + result.push_back(define); + } + return result; } - return result; } -} +} // namespace endor diff --git a/src/client/builtin_scene/material_base.hpp b/src/client/builtin_scene/material_base.hpp index f2eec5dca..31e6ba120 100644 --- a/src/client/builtin_scene/material_base.hpp +++ b/src/client/builtin_scene/material_base.hpp @@ -12,148 +12,151 @@ #include "./renderer/render_pass.hpp" #include "./renderer/render_target.hpp" -namespace builtin_scene +namespace endor { - class Mesh3d; - class Material + namespace builtin_scene { - public: - static inline std::vector GlobalDefines = {}; - /* The define to require multiview */ - static constexpr const char *kRequireMultiviewDefine = "REQUIRE_MULTIVIEW"; + class Mesh3d; + class Material + { + public: + static inline std::vector GlobalDefines = {}; + /* The define to require multiview */ + static constexpr const char *kRequireMultiviewDefine = "REQUIRE_MULTIVIEW"; - public: - /** + public: + /** * Set the global defines for all materials later created. * * @param define The define to set. */ - static void SetGlobalDefines(const std::string &define); + static void SetGlobalDefines(const std::string &define); - /** + /** * Unset the global defines for all materials later created. * * @param define The define to unset. */ - static void UnsetGlobalDefines(const std::string &define); + static void UnsetGlobalDefines(const std::string &define); - /** + /** * Set the multiview is required for all materials later created. * * @param required Whether the multiview is required. */ - static inline void SetMultiviewRequired(bool required) - { - required - ? SetGlobalDefines(kRequireMultiviewDefine) - : UnsetGlobalDefines(kRequireMultiviewDefine); - } + static inline void SetMultiviewRequired(bool required) + { + required + ? SetGlobalDefines(kRequireMultiviewDefine) + : UnsetGlobalDefines(kRequireMultiviewDefine); + } - /** + /** * Create a new instance of the material. * * @tparam MaterialType The type of the material. * @tparam Args The types of the arguments for the constructor of the material. * @param args The arguments for the constructor of the material. */ - template - static inline std::shared_ptr Make(Args &&...args) - { - return std::make_shared(std::forward(args)...); - } + template + static inline std::shared_ptr Make(Args &&...args) + { + return std::make_shared(std::forward(args)...); + } - public: - Material() - { - } - Material(bool isOpaque) - : isOpaque_(isOpaque) - { - } - virtual ~Material() = default; + public: + Material() + { + } + Material(bool isOpaque) + : isOpaque_(isOpaque) + { + } + virtual ~Material() = default; - public: - /** + public: + /** * @returns The name of the material. */ - virtual const std::string name() const - { - return "Material"; - } - /** + virtual const std::string name() const + { + return "Material"; + } + /** * @returns The list of defines for the material. */ - virtual const std::vector defines() const - { - return {}; - } - /** + virtual const std::vector defines() const + { + return {}; + } + /** * @returns The vertex shader for the material. */ - virtual ShaderRef vertexShader() - { - return ShaderRef(client_graphics::WebGLShaderType::kVertex, "shaders/mesh.vert"); - } - /** + virtual ShaderRef vertexShader() + { + return ShaderRef(client_graphics::WebGLShaderType::kVertex, "shaders/mesh.vert"); + } + /** * @returns The fragment shader for the material. */ - virtual ShaderRef fragmentShader() - { - return ShaderRef(client_graphics::WebGLShaderType::kFragment, "materials/default.frag"); - } - /** + virtual ShaderRef fragmentShader() + { + return ShaderRef(client_graphics::WebGLShaderType::kFragment, "materials/default.frag"); + } + /** * Initialize the material with the given program. * * @param glContext The WebGL context to initialize the material with. * @param program The WebGL program to initialize the material with. * @returns Whether the material is initialized successfully. */ - virtual bool initialize(std::shared_ptr glContext, - std::shared_ptr program); + virtual bool initialize(std::shared_ptr glContext, + std::shared_ptr program); - /** + /** * Custom drawing implementation for materials that need special rendering logic. * Return true if the material handled the drawing, false to use default drawing. */ - virtual void drawMeshImpl(std::shared_ptr program, - const Mesh3d &mesh, - RenderPass renderPass, - std::optional renderTarget); + virtual void drawMeshImpl(std::shared_ptr program, + const Mesh3d &mesh, + RenderPass renderPass, + std::optional renderTarget); - virtual void onBeforeDrawMesh(std::shared_ptr program, - std::shared_ptr mesh); - virtual void onAfterDrawMesh(std::shared_ptr program, - std::shared_ptr mesh); + virtual void onBeforeDrawMesh(std::shared_ptr program, + std::shared_ptr mesh); + virtual void onAfterDrawMesh(std::shared_ptr program, + std::shared_ptr mesh); - public: - /** + public: + /** * @returns The list of defines for the material, which includes the global defines. */ - const std::vector getDefinesWithGlobals() const - { - return mixDefines(defines(), GlobalDefines); - } - /** + const std::vector getDefinesWithGlobals() const + { + return mixDefines(defines(), GlobalDefines); + } + /** * @returns Whether the material is opaque. */ - inline bool isOpaque() const - { - return isOpaque_; - } + inline bool isOpaque() const + { + return isOpaque_; + } - protected: - /** + protected: + /** * Mix the defines with the base defines of the material. * * @param baseDefines The base defines of the material. * @param definesToAdd The list of defines to add to the base defines. * @returns The mixed defines. */ - const std::vector mixDefines(const std::vector &baseDefines, - const std::vector &definesToAdd) const; + const std::vector mixDefines(const std::vector &baseDefines, + const std::vector &definesToAdd) const; - protected: - std::weak_ptr glContext_; - bool isOpaque_ = true; - }; -} + protected: + std::weak_ptr glContext_; + bool isOpaque_ = true; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/materials.hpp b/src/client/builtin_scene/materials.hpp index 80b26b680..ee8af2acf 100644 --- a/src/client/builtin_scene/materials.hpp +++ b/src/client/builtin_scene/materials.hpp @@ -8,11 +8,14 @@ #include "./materials/web_content_instanced.hpp" #include "./materials/gaussian_splatting.hpp" -namespace builtin_scene +namespace endor { - class Materials : public asset::Assets + namespace builtin_scene { - public: - using asset::Assets::Assets; - }; -} + class Materials : public asset::Assets + { + public: + using asset::Assets::Assets; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/materials/color.hpp b/src/client/builtin_scene/materials/color.hpp index 0aec946fa..c7f2cf056 100644 --- a/src/client/builtin_scene/materials/color.hpp +++ b/src/client/builtin_scene/materials/color.hpp @@ -4,62 +4,64 @@ #include #include "../material_base.hpp" -namespace builtin_scene::materials +namespace endor { - class ColorMaterial : public Material + namespace builtin_scene::materials { - using ColorMaterialReference = std::shared_ptr; + class ColorMaterial : public Material + { + using ColorMaterialReference = std::shared_ptr; - public: - /** + public: + /** * @returns The transparent color material. */ - static inline ColorMaterialReference Transparent() - { - return std::make_shared(); - } - /** + static inline ColorMaterialReference Transparent() + { + return std::make_shared(); + } + /** * @returns The white color material. */ - static inline ColorMaterialReference White() - { - return std::make_shared(1.0f, 1.0f, 1.0f); - } - /** + static inline ColorMaterialReference White() + { + return std::make_shared(1.0f, 1.0f, 1.0f); + } + /** * @returns The black color material. */ - static inline ColorMaterialReference Black() - { - return std::make_shared(0.0f, 0.0f, 0.0f); - } - /** + static inline ColorMaterialReference Black() + { + return std::make_shared(0.0f, 0.0f, 0.0f); + } + /** * @returns The red color material. */ - static inline ColorMaterialReference Red() - { - return std::make_shared(1.0f, 0.0f, 0.0f); - } - /** + static inline ColorMaterialReference Red() + { + return std::make_shared(1.0f, 0.0f, 0.0f); + } + /** * @returns The green color material. */ - static inline ColorMaterialReference Green() - { - return std::make_shared(0.0f, 1.0f, 0.0f); - } - /** + static inline ColorMaterialReference Green() + { + return std::make_shared(0.0f, 1.0f, 0.0f); + } + /** * @returns The blue color material. */ - static inline ColorMaterialReference Blue() - { - return std::make_shared(0.0f, 0.0f, 1.0f); - } + static inline ColorMaterialReference Blue() + { + return std::make_shared(0.0f, 0.0f, 1.0f); + } - public: - /** + public: + /** * Create a new instance of the color material with no surface color (alpha is 0.0). */ - ColorMaterial() = default; - /** + ColorMaterial() = default; + /** * Create a new instance of the color material with the given surface color. * * @param red The red component of the surface color. @@ -67,42 +69,43 @@ namespace builtin_scene::materials * @param blue The blue component of the surface color. * @param alpha The alpha component of the surface color. */ - ColorMaterial(float red, float green, float blue, float alpha = 1.0f) - : surfaceColor(red, green, blue, alpha) - { - } - - public: - const std::string name() const override - { - return "ColorMaterial"; - } - ShaderRef fragmentShader() override - { - return ShaderRef(ShaderType::kFragment, "materials/color.frag"); - } - bool initialize(std::shared_ptr glContext, - std::shared_ptr program) override - { - if (TR_UNLIKELY(!Material::initialize(glContext, program))) - return false; + ColorMaterial(float red, float green, float blue, float alpha = 1.0f) + : surfaceColor(red, green, blue, alpha) + { + } - auto surfaceColorLoc = glContext->getUniformLocation(program, "surfaceColor"); - if (TR_UNLIKELY(!surfaceColorLoc.has_value())) + public: + const std::string name() const override + { + return "ColorMaterial"; + } + ShaderRef fragmentShader() override { - std::cerr << name() << ": The surfaceColor uniform location is not found." << std::endl; - return false; + return ShaderRef(ShaderType::kFragment, "materials/color.frag"); } + bool initialize(std::shared_ptr glContext, + std::shared_ptr program) override + { + if (TR_UNLIKELY(!Material::initialize(glContext, program))) + return false; + + auto surfaceColorLoc = glContext->getUniformLocation(program, "surfaceColor"); + if (TR_UNLIKELY(!surfaceColorLoc.has_value())) + { + std::cerr << name() << ": The surfaceColor uniform location is not found." << std::endl; + return false; + } - glContext->uniform4f(surfaceColorLoc.value(), - surfaceColor.r, - surfaceColor.g, - surfaceColor.b, - surfaceColor.a); - return true; - } + glContext->uniform4f(surfaceColorLoc.value(), + surfaceColor.r, + surfaceColor.g, + surfaceColor.b, + surfaceColor.a); + return true; + } - public: - glm::vec4 surfaceColor = {1.0f, 1.0f, 1.0f, 0.0f}; - }; -} + public: + glm::vec4 surfaceColor = {1.0f, 1.0f, 1.0f, 0.0f}; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/materials/gaussian_splatting.cpp b/src/client/builtin_scene/materials/gaussian_splatting.cpp index 0e7fd22bc..2052c01fb 100644 --- a/src/client/builtin_scene/materials/gaussian_splatting.cpp +++ b/src/client/builtin_scene/materials/gaussian_splatting.cpp @@ -8,13 +8,15 @@ #include "./gaussian_splatting.hpp" #include "../meshes.hpp" -namespace builtin_scene::materials +namespace endor { - bool GaussianSplattingMaterial::initialize(std::shared_ptr glContext, - std::shared_ptr program) + namespace builtin_scene::materials { - if (TR_UNLIKELY(!Material::initialize(glContext, program))) - return false; + bool GaussianSplattingMaterial::initialize(std::shared_ptr glContext, + std::shared_ptr program) + { + if (TR_UNLIKELY(!Material::initialize(glContext, program))) + return false; #define LOAD_UNIFORM_LOCATION(name) \ { \ @@ -24,130 +26,131 @@ namespace builtin_scene::materials uniforms_.emplace(name, loc.value()); \ } \ } - // Load uniforms for improved 3DGS rendering - LOAD_UNIFORM_LOCATION("renderSize"); - LOAD_UNIFORM_LOCATION("maxStdDev"); - LOAD_UNIFORM_LOCATION("minAlpha"); - LOAD_UNIFORM_LOCATION("maxPixelRadius"); - LOAD_UNIFORM_LOCATION("clipXY"); - LOAD_UNIFORM_LOCATION("focalAdjustment"); - LOAD_UNIFORM_LOCATION("maxDistance"); - LOAD_UNIFORM_LOCATION("compressedSplats"); - LOAD_UNIFORM_LOCATION("scaleMin"); - LOAD_UNIFORM_LOCATION("scaleMax"); - // Note: viewMatrix and projectionMatrix are handled automatically by WebGL context + // Load uniforms for improved 3DGS rendering + LOAD_UNIFORM_LOCATION("renderSize"); + LOAD_UNIFORM_LOCATION("maxStdDev"); + LOAD_UNIFORM_LOCATION("minAlpha"); + LOAD_UNIFORM_LOCATION("maxPixelRadius"); + LOAD_UNIFORM_LOCATION("clipXY"); + LOAD_UNIFORM_LOCATION("focalAdjustment"); + LOAD_UNIFORM_LOCATION("maxDistance"); + LOAD_UNIFORM_LOCATION("compressedSplats"); + LOAD_UNIFORM_LOCATION("scaleMin"); + LOAD_UNIFORM_LOCATION("scaleMax"); + // Note: viewMatrix and projectionMatrix are handled automatically by WebGL context #undef LOAD_UNIFORM_LOCATION - // Set default values for 3DGS parameters (excluding renderSize which is set per frame) - auto maxStdDevOpt = glContext->getUniformLocation(program, "maxStdDev"); - if (maxStdDevOpt.has_value()) - glContext->uniform1f(maxStdDevOpt.value(), sqrt(8)); // Standard deviations to render - - auto minAlphaOpt = glContext->getUniformLocation(program, "minAlpha"); - if (minAlphaOpt.has_value()) - glContext->uniform1f(minAlphaOpt.value(), 0.5f * (1.0f / 255.0f)); // Minimum alpha threshold + // Set default values for 3DGS parameters (excluding renderSize which is set per frame) + auto maxStdDevOpt = glContext->getUniformLocation(program, "maxStdDev"); + if (maxStdDevOpt.has_value()) + glContext->uniform1f(maxStdDevOpt.value(), sqrt(8)); // Standard deviations to render - auto maxPixelRadiusOpt = glContext->getUniformLocation(program, "maxPixelRadius"); - if (maxPixelRadiusOpt.has_value()) - glContext->uniform1f(maxPixelRadiusOpt.value(), 512.0f); // Maximum pixel radius for splats + auto minAlphaOpt = glContext->getUniformLocation(program, "minAlpha"); + if (minAlphaOpt.has_value()) + glContext->uniform1f(minAlphaOpt.value(), 0.5f * (1.0f / 255.0f)); // Minimum alpha threshold - auto clipXYOpt = glContext->getUniformLocation(program, "clipXY"); - if (clipXYOpt.has_value()) - glContext->uniform1f(clipXYOpt.value(), 1.4f); + auto maxPixelRadiusOpt = glContext->getUniformLocation(program, "maxPixelRadius"); + if (maxPixelRadiusOpt.has_value()) + glContext->uniform1f(maxPixelRadiusOpt.value(), 512.0f); // Maximum pixel radius for splats - auto focalAdjustmentOpt = glContext->getUniformLocation(program, "focalAdjustment"); - if (focalAdjustmentOpt.has_value()) - glContext->uniform1f(focalAdjustmentOpt.value(), 1.0f); + auto clipXYOpt = glContext->getUniformLocation(program, "clipXY"); + if (clipXYOpt.has_value()) + glContext->uniform1f(clipXYOpt.value(), 1.4f); - auto maxDistanceOpt = glContext->getUniformLocation(program, "maxDistance"); - if (maxDistanceOpt.has_value()) - glContext->uniform1f(maxDistanceOpt.value(), 1.5f); // Default 1.5 meters + auto focalAdjustmentOpt = glContext->getUniformLocation(program, "focalAdjustment"); + if (focalAdjustmentOpt.has_value()) + glContext->uniform1f(focalAdjustmentOpt.value(), 1.0f); - return true; - } + auto maxDistanceOpt = glContext->getUniformLocation(program, "maxDistance"); + if (maxDistanceOpt.has_value()) + glContext->uniform1f(maxDistanceOpt.value(), 1.5f); // Default 1.5 meters - void GaussianSplattingMaterial::drawMeshImpl(shared_ptr program, - const Mesh3d &mesh, - RenderPass renderPass, - optional) - { - assert(renderPass == RenderPass::kTransparents && - "GaussianSplattingMaterial should only be used in the transparent render pass."); - - auto glContext = glContext_.lock(); - if (!glContext) - return; - - // Only handle drawing if we have splats to render - if (splatInstanceCount_ == 0) - return; - - // Do the instanced draw call - glContext->drawElementsInstanced( - mesh.primitiveTopology(), - mesh.indices().size(), - WEBGL_UNSIGNED_INT, - 0, - splatInstanceCount_); - } - - void GaussianSplattingMaterial::onBeforeDrawMesh(shared_ptr program, - shared_ptr mesh) - { - Material::onBeforeDrawMesh(program, mesh); - - auto glContext = glContext_.lock(); - if (!glContext) [[unlikely]] - return; + return true; + } - // Update render size from drawing buffer - auto renderSizeOpt = glContext->getUniformLocation(program, "renderSize"); - if (renderSizeOpt.has_value()) + void GaussianSplattingMaterial::drawMeshImpl(shared_ptr program, + const Mesh3d &mesh, + RenderPass renderPass, + optional) { - float width = static_cast(glContext->drawingBufferWidth()); - float height = static_cast(glContext->drawingBufferHeight()); - glContext->uniform2f(renderSizeOpt.value(), width, height); + assert(renderPass == RenderPass::kTransparents && + "GaussianSplattingMaterial should only be used in the transparent render pass."); + + auto glContext = glContext_.lock(); + if (!glContext) + return; + + // Only handle drawing if we have splats to render + if (splatInstanceCount_ == 0) + return; + + // Do the instanced draw call + glContext->drawElementsInstanced( + mesh.primitiveTopology(), + mesh.indices().size(), + WEBGL_UNSIGNED_INT, + 0, + splatInstanceCount_); } - // Get the GaussianSplatsMesh from the Mesh3d component - if (mesh != nullptr) + void GaussianSplattingMaterial::onBeforeDrawMesh(shared_ptr program, + shared_ptr mesh) { - auto splatsMesh = mesh->getHandleAs(); - if (splatsMesh != nullptr) - { - // Update splat count for drawing - splatInstanceCount_ = splatsMesh->getTotalSplatCount(); + Material::onBeforeDrawMesh(program, mesh); + + auto glContext = glContext_.lock(); + if (!glContext) [[unlikely]] + return; - // Update buffer with sorted indices - splatsMesh->updateSplatBuffer(glContext, mesh->vertexArrayObject()); + // Update render size from drawing buffer + auto renderSizeOpt = glContext->getUniformLocation(program, "renderSize"); + if (renderSizeOpt.has_value()) + { + float width = static_cast(glContext->drawingBufferWidth()); + float height = static_cast(glContext->drawingBufferHeight()); + glContext->uniform2f(renderSizeOpt.value(), width, height); + } - // Bind compressed splat texture to texture unit 0 - auto compressedTexture = splatsMesh->getCompressedSplatsTexture(); - if (compressedTexture) + // Get the GaussianSplatsMesh from the Mesh3d component + if (mesh != nullptr) + { + auto splatsMesh = mesh->getHandleAs(); + if (splatsMesh != nullptr) { - // Bind compressed texture to unit 0 (now texture2D instead of texture2DArray) - glContext->activeTexture(client_graphics::WebGLTextureUnit::kTexture0); - glContext->bindTexture(client_graphics::WebGLTextureTarget::kTexture2D, compressedTexture); - auto compressedOpt = glContext->getUniformLocation(program, "compressedSplats"); - if (compressedOpt.has_value()) - glContext->uniform1i(compressedOpt.value(), 0); - - // Set normalization parameters as uniforms (only scale needed now) - auto scaleMinOpt = glContext->getUniformLocation(program, "scaleMin"); - if (scaleMinOpt.has_value()) - { - const auto &normParams = splatsMesh->getNormalizationParams(); - glContext->uniform3f(scaleMinOpt.value(), normParams.scaleMin[0], normParams.scaleMin[1], normParams.scaleMin[2]); - } + // Update splat count for drawing + splatInstanceCount_ = splatsMesh->getTotalSplatCount(); + + // Update buffer with sorted indices + splatsMesh->updateSplatBuffer(glContext, mesh->vertexArrayObject()); - auto scaleMaxOpt = glContext->getUniformLocation(program, "scaleMax"); - if (scaleMaxOpt.has_value()) + // Bind compressed splat texture to texture unit 0 + auto compressedTexture = splatsMesh->getCompressedSplatsTexture(); + if (compressedTexture) { - const auto &normParams = splatsMesh->getNormalizationParams(); - glContext->uniform3f(scaleMaxOpt.value(), normParams.scaleMax[0], normParams.scaleMax[1], normParams.scaleMax[2]); + // Bind compressed texture to unit 0 (now texture2D instead of texture2DArray) + glContext->activeTexture(client_graphics::WebGLTextureUnit::kTexture0); + glContext->bindTexture(client_graphics::WebGLTextureTarget::kTexture2D, compressedTexture); + auto compressedOpt = glContext->getUniformLocation(program, "compressedSplats"); + if (compressedOpt.has_value()) + glContext->uniform1i(compressedOpt.value(), 0); + + // Set normalization parameters as uniforms (only scale needed now) + auto scaleMinOpt = glContext->getUniformLocation(program, "scaleMin"); + if (scaleMinOpt.has_value()) + { + const auto &normParams = splatsMesh->getNormalizationParams(); + glContext->uniform3f(scaleMinOpt.value(), normParams.scaleMin[0], normParams.scaleMin[1], normParams.scaleMin[2]); + } + + auto scaleMaxOpt = glContext->getUniformLocation(program, "scaleMax"); + if (scaleMaxOpt.has_value()) + { + const auto &normParams = splatsMesh->getNormalizationParams(); + glContext->uniform3f(scaleMaxOpt.value(), normParams.scaleMax[0], normParams.scaleMax[1], normParams.scaleMax[2]); + } } } } } } -} +} // namespace endor diff --git a/src/client/builtin_scene/materials/gaussian_splatting.hpp b/src/client/builtin_scene/materials/gaussian_splatting.hpp index c2e7293de..ef9da3922 100644 --- a/src/client/builtin_scene/materials/gaussian_splatting.hpp +++ b/src/client/builtin_scene/materials/gaussian_splatting.hpp @@ -8,71 +8,74 @@ #include "../material_base.hpp" #include "../gaussian_splats_mesh.hpp" -namespace builtin_scene::materials +namespace endor { - /** + namespace builtin_scene::materials + { + /** * Material for rendering 3D Gaussian Splatting models using instanced rendering. * Works with GaussianSplatsMesh to render all splats globally. */ - class GaussianSplattingMaterial : public Material - { - public: - static std::shared_ptr Default() + class GaussianSplattingMaterial : public Material { - return std::make_shared(); - } + public: + static std::shared_ptr Default() + { + return std::make_shared(); + } - GaussianSplattingMaterial() - : Material(false) - { - } // Make transparent + GaussianSplattingMaterial() + : Material(false) + { + } // Make transparent - public: - const std::string name() const override - { - return "GaussianSplattingMaterial"; - } + public: + const std::string name() const override + { + return "GaussianSplattingMaterial"; + } - ShaderRef vertexShader() override - { - return ShaderRef(ShaderType::kVertex, "shaders/gaussian_splatting.vert"); - } + ShaderRef vertexShader() override + { + return ShaderRef(ShaderType::kVertex, "shaders/gaussian_splatting.vert"); + } - ShaderRef fragmentShader() override - { - return ShaderRef(ShaderType::kFragment, "shaders/gaussian_splatting.frag"); - } + ShaderRef fragmentShader() override + { + return ShaderRef(ShaderType::kFragment, "shaders/gaussian_splatting.frag"); + } - bool initialize(std::shared_ptr glContext, - std::shared_ptr program) override; + bool initialize(std::shared_ptr glContext, + std::shared_ptr program) override; - void drawMeshImpl(std::shared_ptr program, - const Mesh3d &mesh, - RenderPass renderPass, - std::optional renderTarget) override; - void onBeforeDrawMesh(std::shared_ptr program, - std::shared_ptr mesh) override; + void drawMeshImpl(std::shared_ptr program, + const Mesh3d &mesh, + RenderPass renderPass, + std::optional renderTarget) override; + void onBeforeDrawMesh(std::shared_ptr program, + std::shared_ptr mesh) override; - public: - /** + public: + /** * Get the current number of splat instances. */ - size_t getSplatInstanceCount() const - { - return splatInstanceCount_; - } + size_t getSplatInstanceCount() const + { + return splatInstanceCount_; + } - private: - inline client_graphics::WebGLUniformLocation uniform(const std::string &name) const - { - auto it = uniforms_.find(name); - if (it == uniforms_.end()) - throw std::runtime_error("The uniform " + name + " is not found."); - return it->second; - } + private: + inline client_graphics::WebGLUniformLocation uniform(const std::string &name) const + { + auto it = uniforms_.find(name); + if (it == uniforms_.end()) + throw std::runtime_error("The uniform " + name + " is not found."); + return it->second; + } - private: - size_t splatInstanceCount_ = 0; - std::unordered_map uniforms_; - }; -} \ No newline at end of file + private: + size_t splatInstanceCount_ = 0; + std::unordered_map uniforms_; + }; + } +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/materials/normal.hpp b/src/client/builtin_scene/materials/normal.hpp index 74888a97b..be1c9f75d 100644 --- a/src/client/builtin_scene/materials/normal.hpp +++ b/src/client/builtin_scene/materials/normal.hpp @@ -3,35 +3,38 @@ #include #include "../material_base.hpp" -namespace builtin_scene::materials +namespace endor { - class NormalMaterial : public Material + namespace builtin_scene::materials { - public: - NormalMaterial() = default; - - public: - const std::string name() const override - { - return "NormalMaterial"; - } - const std::vector defines() const override - { - return {"USE_NORMALS"}; - } - ShaderRef fragmentShader() override + class NormalMaterial : public Material { - return ShaderRef(ShaderType::kFragment, "materials/normal.frag"); - } - bool initialize(std::shared_ptr glContext, - std::shared_ptr program) override - { - auto normalMatrixLoc = glContext->getUniformLocation(program, "normalMatrix"); - if (!normalMatrixLoc.has_value()) - return false; + public: + NormalMaterial() = default; + + public: + const std::string name() const override + { + return "NormalMaterial"; + } + const std::vector defines() const override + { + return {"USE_NORMALS"}; + } + ShaderRef fragmentShader() override + { + return ShaderRef(ShaderType::kFragment, "materials/normal.frag"); + } + bool initialize(std::shared_ptr glContext, + std::shared_ptr program) override + { + auto normalMatrixLoc = glContext->getUniformLocation(program, "normalMatrix"); + if (!normalMatrixLoc.has_value()) + return false; - glContext->uniformMatrix4fv(normalMatrixLoc.value(), false, glm::mat4(1.0f)); - return true; - } - }; -} + glContext->uniformMatrix4fv(normalMatrixLoc.value(), false, glm::mat4(1.0f)); + return true; + } + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/materials/web_content_instanced.cpp b/src/client/builtin_scene/materials/web_content_instanced.cpp index 9d6fcacb7..423aefe1b 100644 --- a/src/client/builtin_scene/materials/web_content_instanced.cpp +++ b/src/client/builtin_scene/materials/web_content_instanced.cpp @@ -11,27 +11,29 @@ #include "./web_content_instanced.hpp" #include "../instanced_mesh.hpp" -namespace builtin_scene::materials +namespace endor { - using namespace std; - using namespace skia::textlayout; - using namespace client_graphics; - - WebContentInstancedMaterial::WebContentInstancedMaterial() - : Material(false) - , width_(0.0f) - , height_(0.0f) - , textureAtlas_(nullptr) - , textureOffset_(0.0f, 0.0f) - , textureScale_(1.0f, 1.0f) + namespace builtin_scene::materials { - } + using namespace std; + using namespace skia::textlayout; + using namespace client_graphics; + + WebContentInstancedMaterial::WebContentInstancedMaterial() + : Material(false) + , width_(0.0f) + , height_(0.0f) + , textureAtlas_(nullptr) + , textureOffset_(0.0f, 0.0f) + , textureScale_(1.0f, 1.0f) + { + } - bool WebContentInstancedMaterial::initialize(shared_ptr glContext, - shared_ptr program) - { - if (TR_UNLIKELY(!Material::initialize(glContext, program))) - return false; + bool WebContentInstancedMaterial::initialize(shared_ptr glContext, + shared_ptr program) + { + if (TR_UNLIKELY(!Material::initialize(glContext, program))) + return false; #define LOAD_UNIFORM_LOCATION(name) \ { \ @@ -42,325 +44,326 @@ namespace builtin_scene::materials } \ } - LOAD_UNIFORM_LOCATION("instanceTexAltas"); - LOAD_UNIFORM_LOCATION("textureTransformation"); - LOAD_UNIFORM_LOCATION("uSdfEnabled"); - LOAD_UNIFORM_LOCATION("borderDataTexture"); - // Fallback uniforms (only present when USE_INSTANCE_SDF is not defined) - LOAD_UNIFORM_LOCATION("uDimensions"); - LOAD_UNIFORM_LOCATION("uBorderRadius"); - LOAD_UNIFORM_LOCATION("uBorderWidth"); - LOAD_UNIFORM_LOCATION("uBorderColor"); - LOAD_UNIFORM_LOCATION("uBorderStyle"); + LOAD_UNIFORM_LOCATION("instanceTexAltas"); + LOAD_UNIFORM_LOCATION("textureTransformation"); + LOAD_UNIFORM_LOCATION("uSdfEnabled"); + LOAD_UNIFORM_LOCATION("borderDataTexture"); + // Fallback uniforms (only present when USE_INSTANCE_SDF is not defined) + LOAD_UNIFORM_LOCATION("uDimensions"); + LOAD_UNIFORM_LOCATION("uBorderRadius"); + LOAD_UNIFORM_LOCATION("uBorderWidth"); + LOAD_UNIFORM_LOCATION("uBorderColor"); + LOAD_UNIFORM_LOCATION("uBorderStyle"); #undef LOAD_UNIFORM_LOCATION - glContext->uniform1i(uniform("instanceTexAltas"), 0); - glContext->uniform1i(uniform("borderDataTexture"), 1); + glContext->uniform1i(uniform("instanceTexAltas"), 0); + glContext->uniform1i(uniform("borderDataTexture"), 1); - // Set the texture to be flipped by the Y-axis. - // - // WebGL uses the bottom-left corner as the origin, while Skia or Web uses the top-left, so flip the texture by - // the Y-axis to make it consistent. - flipTextureByY(true); + // Set the texture to be flipped by the Y-axis. + // + // WebGL uses the bottom-left corner as the origin, while Skia or Web uses the top-left, so flip the texture by + // the Y-axis to make it consistent. + flipTextureByY(true); - // Initialize the texture atlas. - assert(textureAtlas_ == nullptr && "The texture atlas is already initialized."); - textureAtlas_ = make_unique(glContext, client_graphics::WebGLTextureUnit::kTexture0); + // Initialize the texture atlas. + assert(textureAtlas_ == nullptr && "The texture atlas is already initialized."); + textureAtlas_ = make_unique(glContext, client_graphics::WebGLTextureUnit::kTexture0); - // Initialize border data texture manager - borderDataTexture_ = make_unique(); - if (!borderDataTexture_->initialize(glContext)) - { - borderDataTexture_.reset(); - return false; - } - - return textureAtlas_ != nullptr; // Tells the caller whether the initialization is successful. - } + // Initialize border data texture manager + borderDataTexture_ = make_unique(); + if (!borderDataTexture_->initialize(glContext)) + { + borderDataTexture_.reset(); + return false; + } - void WebContentInstancedMaterial::drawMeshImpl(shared_ptr program, - const Mesh3d &mesh, - RenderPass renderPass, - optional renderTarget) - { - assert((renderPass == RenderPass::kTransparents) && - "RenderPass must be either Transparents for instanced meshes."); + return textureAtlas_ != nullptr; // Tells the caller whether the initialization is successful. + } - auto glContext = glContext_.lock(); - if (glContext == nullptr) [[unlikely]] - return; + void WebContentInstancedMaterial::drawMeshImpl(shared_ptr program, + const Mesh3d &mesh, + RenderPass renderPass, + optional renderTarget) + { + assert((renderPass == RenderPass::kTransparents) && + "RenderPass must be either Transparents for instanced meshes."); - auto &instancedMesh = mesh.getHandleCheckedAsRef(); - if (instancedMesh.instanceCount() <= 0) - return; + auto glContext = glContext_.lock(); + if (glContext == nullptr) [[unlikely]] + return; - // a) Check isStructureDirty_ and update layeredInstances_ if needed - instancedMesh.updateInstancesList(program); + auto &instancedMesh = mesh.getHandleCheckedAsRef(); + if (instancedMesh.instanceCount() <= 0) + return; - size_t meshIndicesCount = mesh.indices().size(); - CSSBorderDataTexture *borderDataTexture = getBorderDataTexture(); + // a) Check isStructureDirty_ and update layeredInstances_ if needed + instancedMesh.updateInstancesList(program); - // Set the base matrix once (shared uniform), move the transparent objects +z 0.001 - auto loc = glContext->getUniformLocation(program, "modelMatrix"); - glm::mat4 matToUpdate = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.001f)); - glContext->uniformMatrix4fv(loc.value(), false, matToUpdate); + size_t meshIndicesCount = mesh.indices().size(); + CSSBorderDataTexture *borderDataTexture = getBorderDataTexture(); - bool inDepthWritePass = false; + // Set the base matrix once (shared uniform), move the transparent objects +z 0.001 + auto loc = glContext->getUniformLocation(program, "modelMatrix"); + glm::mat4 matToUpdate = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.001f)); + glContext->uniformMatrix4fv(loc.value(), false, matToUpdate); - // b) Render layeredInstances_ in order (0-1-2-...) - // First render scrollable container masks for each layer, then render regular content with stencil testing - auto renderLayer = [&](RenderLayer layer, ContentInstancesList &layerInstancesList) - { - // c) Switch RenderableInstancesList's vbo to current vao's vbo for this layer - WebGLVertexArrayScope vaoScope(glContext, layerInstancesList.vao); + bool inDepthWritePass = false; - // Draw the layer - layerInstancesList.beforeInstancedDraw(*glContext, borderDataTexture); + // b) Render layeredInstances_ in order (0-1-2-...) + // First render scrollable container masks for each layer, then render regular content with stencil testing + auto renderLayer = [&](RenderLayer layer, ContentInstancesList &layerInstancesList) { - // Draw layer instances to color attachment - if (inDepthWritePass) - { - // Depth write only pass, disable color writes and enable depth writes - glContext->colorMask(false, false, false, false); - glContext->depthMask(true); - glContext->disable(WEBGL_BLEND); - } - else - { - // Normal pass, enable color writes and disable depth writes - glContext->colorMask(true, true, true, true); - glContext->depthMask(false); - glContext->enable(WEBGL_BLEND); - glContext->blendFunc(WEBGL_SRC_ALPHA, WEBGL_ONE_MINUS_SRC_ALPHA); - } + // c) Switch RenderableInstancesList's vbo to current vao's vbo for this layer + WebGLVertexArrayScope vaoScope(glContext, layerInstancesList.vao); - glContext->drawElementsInstanced(mesh.primitiveTopology(), - meshIndicesCount, - WEBGL_UNSIGNED_INT, - 0, - layerInstancesList.count()); - } - layerInstancesList.afterInstancedDraw(*glContext); - }; - - // Render per-container with individual stencil isolation. - // This implements overflow behavior for Web Content by: - // 1. Clearing stencil buffer for each container - // 2. Rendering this container's mask to the stencil buffer - // 3. Rendering content belonging to this container with stencil testing - auto renderLayerWithMask = [&](RenderLayer layer, - ContainerInstance *containerInstance, - ContentInstancesList *contentInstances) - { - bool hasScrollableContainer = containerInstance != nullptr && containerInstance->count() > 0; - bool hasContent = contentInstances != nullptr && contentInstances->count() > 0; + // Draw the layer + layerInstancesList.beforeInstancedDraw(*glContext, borderDataTexture); + { + // Draw layer instances to color attachment + if (inDepthWritePass) + { + // Depth write only pass, disable color writes and enable depth writes + glContext->colorMask(false, false, false, false); + glContext->depthMask(true); + glContext->disable(WEBGL_BLEND); + } + else + { + // Normal pass, enable color writes and disable depth writes + glContext->colorMask(true, true, true, true); + glContext->depthMask(false); + glContext->enable(WEBGL_BLEND); + glContext->blendFunc(WEBGL_SRC_ALPHA, WEBGL_ONE_MINUS_SRC_ALPHA); + } - if (hasScrollableContainer) + glContext->drawElementsInstanced(mesh.primitiveTopology(), + meshIndicesCount, + WEBGL_UNSIGNED_INT, + 0, + layerInstancesList.count()); + } + layerInstancesList.afterInstancedDraw(*glContext); + }; + + // Render per-container with individual stencil isolation. + // This implements overflow behavior for Web Content by: + // 1. Clearing stencil buffer for each container + // 2. Rendering this container's mask to the stencil buffer + // 3. Rendering content belonging to this container with stencil testing + auto renderLayerWithMask = [&](RenderLayer layer, + ContainerInstance *containerInstance, + ContentInstancesList *contentInstances) { - // Step 2: Render scrollable container instance as stencil mask - glContext->enable(WEBGL_STENCIL_TEST); - glContext->colorMask(false, false, false, false); // Don't write to color buffer for mask - glContext->stencilOp(WEBGL_KEEP, WEBGL_KEEP, WEBGL_REPLACE); // Replace stencil value on pass - glContext->stencilMask(0xff); + bool hasScrollableContainer = containerInstance != nullptr && containerInstance->count() > 0; + bool hasContent = contentInstances != nullptr && contentInstances->count() > 0; - /** + if (hasScrollableContainer) + { + // Step 2: Render scrollable container instance as stencil mask + glContext->enable(WEBGL_STENCIL_TEST); + glContext->colorMask(false, false, false, false); // Don't write to color buffer for mask + glContext->stencilOp(WEBGL_KEEP, WEBGL_KEEP, WEBGL_REPLACE); // Replace stencil value on pass + glContext->stencilMask(0xff); + + /** * Stencil masking format: [4 bits for container index | 4 bits for layer index] * * NOTE(yorkie): the container index is reversed as well though it is not used at the moment. * NOTE(yorkie): this stores layer index reversely (0x0f - layer.index()) to work with `LESS` function, so that * the stencil value zero can be filtered out. */ - int maskRef = ((containerInstance->getContainerIndex() & 0x0f) << 4) | ((0x0f - layer.index()) & 0x0f); - if (layer.index() == 0) - { - glContext->stencilFunc(WEBGL_ALWAYS, maskRef, 0xff); + int maskRef = ((containerInstance->getContainerIndex() & 0x0f) << 4) | ((0x0f - layer.index()) & 0x0f); + if (layer.index() == 0) + { + glContext->stencilFunc(WEBGL_ALWAYS, maskRef, 0xff); + } + else + { + // When drawing masks based on the parent layer, we need to ensure that the new mask is drawn inside the + // parent layer's mask, so we use `LESS` function to compare the layer index bits. + glContext->stencilFunc(WEBGL_LESS, maskRef, 0x0f); + } + + // Render the container mask + { + WebGLVertexArrayScope vaoScope(glContext, containerInstance->vao); + containerInstance->beforeInstancedDraw(*glContext); + glContext->drawElementsInstanced(mesh.primitiveTopology(), + meshIndicesCount, + WEBGL_UNSIGNED_INT, + 0, + containerInstance->count()); + containerInstance->afterInstancedDraw(*glContext); + } + + // Step 3: Render content with stencil testing enabled + if (hasContent) + { + glContext->stencilMask(0); // Disable writing to stencil buffer + glContext->stencilFunc(WEBGL_EQUAL, maskRef, 0xff); // Only render when the mask matches exactly + glContext->stencilOp(WEBGL_KEEP, WEBGL_KEEP, WEBGL_KEEP); // Don't modify stencil when rendering content + + // Render the content instances + renderLayer(layer, *contentInstances); + } + + // Step 4: Disable stencil testing after rendering this container + glContext->disable(WEBGL_STENCIL_TEST); } - else + else if (hasContent) { - // When drawing masks based on the parent layer, we need to ensure that the new mask is drawn inside the - // parent layer's mask, so we use `LESS` function to compare the layer index bits. - glContext->stencilFunc(WEBGL_LESS, maskRef, 0x0f); + // No container, render content directly + renderLayer(layer, *contentInstances); } + }; - // Render the container mask - { - WebGLVertexArrayScope vaoScope(glContext, containerInstance->vao); - containerInstance->beforeInstancedDraw(*glContext); - glContext->drawElementsInstanced(mesh.primitiveTopology(), - meshIndicesCount, - WEBGL_UNSIGNED_INT, - 0, - containerInstance->count()); - containerInstance->afterInstancedDraw(*glContext); - } + // Iterate layers and render + inDepthWritePass = false; + instancedMesh.iterateLayers(renderLayerWithMask); - // Step 3: Render content with stencil testing enabled - if (hasContent) - { - glContext->stencilMask(0); // Disable writing to stencil buffer - glContext->stencilFunc(WEBGL_EQUAL, maskRef, 0xff); // Only render when the mask matches exactly - glContext->stencilOp(WEBGL_KEEP, WEBGL_KEEP, WEBGL_KEEP); // Don't modify stencil when rendering content + // d) Execute DepthOnlyPass once after all layers are rendered (if enabled) + if (instancedMesh.isDepthOnlyPassEnabled()) + { + // Clear the stencil buffer for later use + glContext->clearStencil(0); + glContext->clear(WEBGL_STENCIL_BUFFER_BIT); - // Render the content instances - renderLayer(layer, *contentInstances); - } + // Iterate layers and write depth + inDepthWritePass = true; + instancedMesh.iterateLayers(renderLayerWithMask); - // Step 4: Disable stencil testing after rendering this container - glContext->disable(WEBGL_STENCIL_TEST); + // Reset the state + glContext->colorMask(true, true, true, true); } - else if (hasContent) - { - // No container, render content directly - renderLayer(layer, *contentInstances); - } - }; - - // Iterate layers and render - inDepthWritePass = false; - instancedMesh.iterateLayers(renderLayerWithMask); + } - // d) Execute DepthOnlyPass once after all layers are rendered (if enabled) - if (instancedMesh.isDepthOnlyPassEnabled()) + void WebContentInstancedMaterial::onBeforeDrawMesh(shared_ptr program, shared_ptr mesh) { - // Clear the stencil buffer for later use - glContext->clearStencil(0); - glContext->clear(WEBGL_STENCIL_BUFFER_BIT); - - // Iterate layers and write depth - inDepthWritePass = true; - instancedMesh.iterateLayers(renderLayerWithMask); - - // Reset the state - glContext->colorMask(true, true, true, true); + auto glContext = glContext_.lock(); + assert(glContext != nullptr); + + // Update the uniforms + glContext->uniform1f(uniform("uSdfEnabled"), sdfEnabled_ ? 1.0f : 0.0f); + glContext->uniformMatrix3fv(uniform("textureTransformation"), + false, + glm::mat3(textureScale_.x, + 0.0f, + 0.0f, + 0.0f, + textureScale_.y, + 0.0f, + textureOffset_.x, + textureOffset_.y, + 1.0f)); + glContext->uniform1i(uniform("instanceTexAltas"), 0); + glContext->uniform1i(uniform("borderDataTexture"), 1); + + // Bind the texture atlas. + assert(textureAtlas_ != nullptr); + textureAtlas_->onBeforeDraw(); + + // Bind the border data texture + if (borderDataTexture_ && borderDataTexture_->isInitialized()) + borderDataTexture_->bind(client_graphics::WebGLTextureUnit::kTexture1); } - } - - void WebContentInstancedMaterial::onBeforeDrawMesh(shared_ptr program, shared_ptr mesh) - { - auto glContext = glContext_.lock(); - assert(glContext != nullptr); - - // Update the uniforms - glContext->uniform1f(uniform("uSdfEnabled"), sdfEnabled_ ? 1.0f : 0.0f); - glContext->uniformMatrix3fv(uniform("textureTransformation"), - false, - glm::mat3(textureScale_.x, - 0.0f, - 0.0f, - 0.0f, - textureScale_.y, - 0.0f, - textureOffset_.x, - textureOffset_.y, - 1.0f)); - glContext->uniform1i(uniform("instanceTexAltas"), 0); - glContext->uniform1i(uniform("borderDataTexture"), 1); - - // Bind the texture atlas. - assert(textureAtlas_ != nullptr); - textureAtlas_->onBeforeDraw(); - - // Bind the border data texture - if (borderDataTexture_ && borderDataTexture_->isInitialized()) - borderDataTexture_->bind(client_graphics::WebGLTextureUnit::kTexture1); - } - void WebContentInstancedMaterial::onAfterDrawMesh(shared_ptr program, shared_ptr mesh) - { - // Unbind the border data texture - if (borderDataTexture_ && borderDataTexture_->isInitialized()) - borderDataTexture_->unbind(client_graphics::WebGLTextureUnit::kTexture1); + void WebContentInstancedMaterial::onAfterDrawMesh(shared_ptr program, shared_ptr mesh) + { + // Unbind the border data texture + if (borderDataTexture_ && borderDataTexture_->isInitialized()) + borderDataTexture_->unbind(client_graphics::WebGLTextureUnit::kTexture1); - // Unbind the texture atlas. - textureAtlas_->onAfterDraw(); - } + // Unbind the texture atlas. + textureAtlas_->onAfterDraw(); + } - void WebContentInstancedMaterial::flipTextureByY(bool flip) - { - if (flip) + void WebContentInstancedMaterial::flipTextureByY(bool flip) { - textureOffset_ = glm::vec2(0.0f, 1.0f); - textureScale_ = glm::vec2(1.0f, -1.0f); + if (flip) + { + textureOffset_ = glm::vec2(0.0f, 1.0f); + textureScale_ = glm::vec2(1.0f, -1.0f); + } + else + { + textureOffset_ = glm::vec2(0.0f, 0.0f); + textureScale_ = glm::vec2(1.0f, 1.0f); + } } - else + + void WebContentInstancedMaterial::setSdfEnabled(bool enabled) { - textureOffset_ = glm::vec2(0.0f, 0.0f); - textureScale_ = glm::vec2(1.0f, 1.0f); + sdfEnabled_ = enabled; } - } - - void WebContentInstancedMaterial::setSdfEnabled(bool enabled) - { - sdfEnabled_ = enabled; - } - CSSBorderDataTexture *WebContentInstancedMaterial::getBorderDataTexture() const - { - return borderDataTexture_.get(); - } + CSSBorderDataTexture *WebContentInstancedMaterial::getBorderDataTexture() const + { + return borderDataTexture_.get(); + } - WebContentInstancedMaterial::TextureUpdateStatus WebContentInstancedMaterial::updateTexture(WebContent &content) - { - if (textureAtlas_ == nullptr) - return TextureUpdateStatus::kFailed; // Just skip the update when the texture atlas is not ready. + WebContentInstancedMaterial::TextureUpdateStatus WebContentInstancedMaterial::updateTexture(WebContent &content) + { + if (textureAtlas_ == nullptr) + return TextureUpdateStatus::kFailed; // Just skip the update when the texture atlas is not ready. - auto textureRect = content.resizeOrInitTexture(*textureAtlas_); - if (textureRect == nullptr) - return TextureUpdateStatus::kSkipped; // Just skip when the texture creation is failed. + auto textureRect = content.resizeOrInitTexture(*textureAtlas_); + if (textureRect == nullptr) + return TextureUpdateStatus::kSkipped; // Just skip when the texture creation is failed. - unsigned char *pixels = nullptr; - int internalformat = WEBGL2_RGBA8; - WebGLTextureFormat format = WebGLTextureFormat::kRGBA; - WebGLPixelType pixelType = WebGLPixelType::kUnsignedByte; + unsigned char *pixels = nullptr; + int internalformat = WEBGL2_RGBA8; + WebGLTextureFormat format = WebGLTextureFormat::kRGBA; + WebGLPixelType pixelType = WebGLPixelType::kUnsignedByte; - SkCanvas *canvas = content.canvas(); - SkSurface *surface = canvas->getSurface(); - if (surface != nullptr) - { - SkImageInfo info = surface->imageInfo(); - SkPixmap pixmap; - if (surface->peekPixels(&pixmap)) + SkCanvas *canvas = content.canvas(); + SkSurface *surface = canvas->getSurface(); + if (surface != nullptr) { - pixels = (unsigned char *)pixmap.addr(); - - // Update the texture format based on the Skia surface color type. - SkColorType colorType = surface->imageInfo().colorType(); - switch (colorType) + SkImageInfo info = surface->imageInfo(); + SkPixmap pixmap; + if (surface->peekPixels(&pixmap)) { - case kRGBA_8888_SkColorType: - // Keep the default values. - break; - case kRGB_888x_SkColorType: - format = WebGLTextureFormat::kRGB; - internalformat = WEBGL2_RGB8; - break; - case kRGBA_F16_SkColorType: - pixelType = WebGLPixelType::kHalfFloat; - internalformat = WEBGL2_RGBA16F; - break; - case kRGBA_F32_SkColorType: - pixelType = WebGLPixelType::kFloat; - internalformat = WEBGL2_RGBA32F; - break; - case kBGRA_8888_SkColorType: - cerr << name() << ": The BGRA_8888 color type is not supported." << endl; - break; - default: - cerr << name() << ": The color type is not supported." << endl; - break; - }; - } - else - { - cerr << name() << ": The pixels are not readable." << endl; + pixels = (unsigned char *)pixmap.addr(); + + // Update the texture format based on the Skia surface color type. + SkColorType colorType = surface->imageInfo().colorType(); + switch (colorType) + { + case kRGBA_8888_SkColorType: + // Keep the default values. + break; + case kRGB_888x_SkColorType: + format = WebGLTextureFormat::kRGB; + internalformat = WEBGL2_RGB8; + break; + case kRGBA_F16_SkColorType: + pixelType = WebGLPixelType::kHalfFloat; + internalformat = WEBGL2_RGBA16F; + break; + case kRGBA_F32_SkColorType: + pixelType = WebGLPixelType::kFloat; + internalformat = WEBGL2_RGBA32F; + break; + case kBGRA_8888_SkColorType: + cerr << name() << ": The BGRA_8888 color type is not supported." << endl; + break; + default: + cerr << name() << ": The color type is not supported." << endl; + break; + }; + } + else + { + cerr << name() << ": The pixels are not readable." << endl; + } } - } - // Update the texture with the new pixels or the default values. - textureAtlas_->updateTexture(*textureRect, pixels, format, pixelType); + // Update the texture with the new pixels or the default values. + textureAtlas_->updateTexture(*textureRect, pixels, format, pixelType); - // No matter the texture update is successful or not, we will return the status. - return TextureUpdateStatus::kSuccess; - } + // No matter the texture update is successful or not, we will return the status. + return TextureUpdateStatus::kSuccess; + } -} // namespace builtin_scene::materials + } // namespace builtin_scene::materials +} // namespace endor diff --git a/src/client/builtin_scene/materials/web_content_instanced.hpp b/src/client/builtin_scene/materials/web_content_instanced.hpp index 6fe5dea54..a67704591 100644 --- a/src/client/builtin_scene/materials/web_content_instanced.hpp +++ b/src/client/builtin_scene/materials/web_content_instanced.hpp @@ -11,117 +11,120 @@ #include "../css_border_data_texture.hpp" #include "./color.hpp" -namespace builtin_scene::materials +namespace endor { - class WebContentInstancedMaterial final : public Material + namespace builtin_scene::materials { - public: - enum class TextureUpdateStatus : uint8_t + class WebContentInstancedMaterial final : public Material { - kSkipped, - kSuccess, - kFailed - }; + public: + enum class TextureUpdateStatus : uint8_t + { + kSkipped, + kSuccess, + kFailed + }; - public: - WebContentInstancedMaterial(); + public: + WebContentInstancedMaterial(); - public: - const std::string name() const override - { - return "WebContentInstancedMaterial"; - } - const std::vector defines() const override - { - return mixDefines(Material::defines(), - { - "USE_UVS", - "USE_INSTANCE_TRANSFORMS", - "USE_INSTANCE_COLORS", - "USE_INSTANCE_TEXTURE", - "USE_INSTANCE_SDF" - // End - }); - } - ShaderRef fragmentShader() override - { - return ShaderRef(ShaderType::kFragment, "materials/web_content.frag"); - } - bool initialize(std::shared_ptr glContext, - std::shared_ptr program) override; - void drawMeshImpl(std::shared_ptr program, - const Mesh3d &mesh, - RenderPass renderPass, - std::optional renderTarget) override; - void onBeforeDrawMesh(std::shared_ptr program, - std::shared_ptr mesh) override; - void onAfterDrawMesh(std::shared_ptr program, - std::shared_ptr mesh) override; + public: + const std::string name() const override + { + return "WebContentInstancedMaterial"; + } + const std::vector defines() const override + { + return mixDefines(Material::defines(), + { + "USE_UVS", + "USE_INSTANCE_TRANSFORMS", + "USE_INSTANCE_COLORS", + "USE_INSTANCE_TEXTURE", + "USE_INSTANCE_SDF" + // End + }); + } + ShaderRef fragmentShader() override + { + return ShaderRef(ShaderType::kFragment, "materials/web_content.frag"); + } + bool initialize(std::shared_ptr glContext, + std::shared_ptr program) override; + void drawMeshImpl(std::shared_ptr program, + const Mesh3d &mesh, + RenderPass renderPass, + std::optional renderTarget) override; + void onBeforeDrawMesh(std::shared_ptr program, + std::shared_ptr mesh) override; + void onAfterDrawMesh(std::shared_ptr program, + std::shared_ptr mesh) override; - public: - /** + public: + /** * Flip the texture by the Y-axis. */ - void flipTextureByY(bool flip); + void flipTextureByY(bool flip); - /** + /** * Update the texture from the given WebContent. * * @param content The WebContent to update the material with. * @returns The status of the texture update. */ - TextureUpdateStatus updateTexture(WebContent &content); + TextureUpdateStatus updateTexture(WebContent &content); - /** + /** * Enable or disable SDF-based anti-aliasing globally. * * @param enabled Whether to enable SDF rendering. */ - void setSdfEnabled(bool enabled); + void setSdfEnabled(bool enabled); - /** + /** * Get the border data texture for direct updates. * * @return Pointer to the CSSBorderDataTexture instance. */ - CSSBorderDataTexture *getBorderDataTexture() const; + CSSBorderDataTexture *getBorderDataTexture() const; - public: - float width() const - { - return width_; - } - float height() const - { - return height_; - } + public: + float width() const + { + return width_; + } + float height() const + { + return height_; + } - private: - inline client_graphics::WebGLUniformLocation uniform(const std::string &name) const - { - auto it = uniforms_.find(name); - if (it == uniforms_.end()) - throw std::runtime_error("The uniform " + name + " is not found."); - return it->second; - } + private: + inline client_graphics::WebGLUniformLocation uniform(const std::string &name) const + { + auto it = uniforms_.find(name); + if (it == uniforms_.end()) + throw std::runtime_error("The uniform " + name + " is not found."); + return it->second; + } - inline bool hasUniform(const std::string &name) const - { - return uniforms_.find(name) != uniforms_.end(); - } + inline bool hasUniform(const std::string &name) const + { + return uniforms_.find(name) != uniforms_.end(); + } - private: - float width_; - float height_; - glm::vec2 textureOffset_ = glm::vec2(0.0f, 0.0f); - glm::vec2 textureScale_ = glm::vec2(1.0f, 1.0f); - std::unordered_map uniforms_; - std::unique_ptr textureAtlas_; + private: + float width_; + float height_; + glm::vec2 textureOffset_ = glm::vec2(0.0f, 0.0f); + glm::vec2 textureScale_ = glm::vec2(1.0f, 1.0f); + std::unordered_map uniforms_; + std::unique_ptr textureAtlas_; - // SDF rendering parameters - bool sdfEnabled_ = true; + // SDF rendering parameters + bool sdfEnabled_ = true; - // Border data texture manager - std::unique_ptr borderDataTexture_; - }; -} + // Border data texture manager + std::unique_ptr borderDataTexture_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/mesh_base.cpp b/src/client/builtin_scene/mesh_base.cpp index 29ad1269e..6cd6b6fb2 100644 --- a/src/client/builtin_scene/mesh_base.cpp +++ b/src/client/builtin_scene/mesh_base.cpp @@ -1,135 +1,138 @@ #include "./mesh_base.hpp" #include "./meshes.hpp" -namespace builtin_scene +namespace endor { - void Vertex::insertAttributeToCompactData(const IVertexAttribute &attribute) + namespace builtin_scene { - if (attribute.is(ATTRIBUTE_POSITION)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&position), - reinterpret_cast(&position) + sizeof(position)); - else if (attribute.is(ATTRIBUTE_NORMAL)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&normal), - reinterpret_cast(&normal) + sizeof(normal)); - else if (attribute.is(ATTRIBUTE_UV0)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&uv0), - reinterpret_cast(&uv0) + sizeof(uv0)); - else if (attribute.is(ATTRIBUTE_UV1)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&uv1), - reinterpret_cast(&uv1) + sizeof(uv1)); - else if (attribute.is(ATTRIBUTE_TANGENT)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&tangent), - reinterpret_cast(&tangent) + sizeof(tangent)); - else if (attribute.is(ATTRIBUTE_COLOR)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&color), - reinterpret_cast(&color) + sizeof(color)); - else if (attribute.is(ATTRIBUTE_JOINT_WEIGHTS)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&jointWeights), - reinterpret_cast(&jointWeights) + sizeof(jointWeights)); - else if (attribute.is(ATTRIBUTE_JOINT_INDEX)) - compactData_.insert(compactData_.end(), - reinterpret_cast(&jointIndex), - reinterpret_cast(&jointIndex) + sizeof(jointIndex)); - } + void Vertex::insertAttributeToCompactData(const IVertexAttribute &attribute) + { + if (attribute.is(ATTRIBUTE_POSITION)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&position), + reinterpret_cast(&position) + sizeof(position)); + else if (attribute.is(ATTRIBUTE_NORMAL)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&normal), + reinterpret_cast(&normal) + sizeof(normal)); + else if (attribute.is(ATTRIBUTE_UV0)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&uv0), + reinterpret_cast(&uv0) + sizeof(uv0)); + else if (attribute.is(ATTRIBUTE_UV1)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&uv1), + reinterpret_cast(&uv1) + sizeof(uv1)); + else if (attribute.is(ATTRIBUTE_TANGENT)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&tangent), + reinterpret_cast(&tangent) + sizeof(tangent)); + else if (attribute.is(ATTRIBUTE_COLOR)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&color), + reinterpret_cast(&color) + sizeof(color)); + else if (attribute.is(ATTRIBUTE_JOINT_WEIGHTS)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&jointWeights), + reinterpret_cast(&jointWeights) + sizeof(jointWeights)); + else if (attribute.is(ATTRIBUTE_JOINT_INDEX)) + compactData_.insert(compactData_.end(), + reinterpret_cast(&jointIndex), + reinterpret_cast(&jointIndex) + sizeof(jointIndex)); + } - void Vertex::update(const std::vector &enabledAttributes) - { - compactData_.clear(); - for (auto &item : enabledAttributes) + void Vertex::update(const std::vector &enabledAttributes) { - std::visit([this](auto &&attrib) - { this->insertAttributeToCompactData(attrib); }, - item); + compactData_.clear(); + for (auto &item : enabledAttributes) + { + std::visit([this](auto &&attrib) + { this->insertAttributeToCompactData(attrib); }, + item); + } } - } - void MeshVertexBuffer::insertVertices(const MeshVertexBuffer &buffer, bool updateAttributes) - { - for (auto &vertex : buffer.vertices_) - vertices_.push_back(vertex); + void MeshVertexBuffer::insertVertices(const MeshVertexBuffer &buffer, bool updateAttributes) + { + for (auto &vertex : buffer.vertices_) + vertices_.push_back(vertex); + + if (updateAttributes) + { + enabledAttributes_ = buffer.enabledAttributes_; + stride_ = buffer.stride_; + isDirty_ = true; + } + } - if (updateAttributes) + void MeshVertexBuffer::enableAttribute(const MeshVertexAttribute &attribute) { - enabledAttributes_ = buffer.enabledAttributes_; - stride_ = buffer.stride_; + enabledAttributes_.push_back(attribute); + // Set the stride when enabling the attribute. + stride_ += std::visit([](auto &&item) + { return item.byteLength(); }, + attribute); isDirty_ = true; } - } - void MeshVertexBuffer::enableAttribute(const MeshVertexAttribute &attribute) - { - enabledAttributes_.push_back(attribute); - // Set the stride when enabling the attribute. - stride_ += std::visit([](auto &&item) - { return item.byteLength(); }, - attribute); - isDirty_ = true; - } + void MeshVertexBuffer::disableAttribute(const MeshVertexAttribute &attribute) + { + auto it = std::remove_if(enabledAttributes_.begin(), enabledAttributes_.end(), [&attribute](const MeshVertexAttribute &item) + { return item == attribute; }); + enabledAttributes_.erase(it, enabledAttributes_.end()); - void MeshVertexBuffer::disableAttribute(const MeshVertexAttribute &attribute) - { - auto it = std::remove_if(enabledAttributes_.begin(), enabledAttributes_.end(), [&attribute](const MeshVertexAttribute &item) - { return item == attribute; }); - enabledAttributes_.erase(it, enabledAttributes_.end()); + // Set the stride when disabling the attribute. + stride_ -= std::visit([](auto &&item) + { return item.byteLength(); }, + attribute); + isDirty_ = true; + } - // Set the stride when disabling the attribute. - stride_ -= std::visit([](auto &&item) - { return item.byteLength(); }, - attribute); - isDirty_ = true; - } + void MeshVertexBuffer::clear() + { + vertices_.clear(); + enabledAttributes_.clear(); + cachedData_.clear(); + isDirty_ = true; + stride_ = 0; + } - void MeshVertexBuffer::clear() - { - vertices_.clear(); - enabledAttributes_.clear(); - cachedData_.clear(); - isDirty_ = true; - stride_ = 0; - } + const std::vector &MeshVertexBuffer::data() + { + if (!isDirty_ && cachedData_.size() > 0) + return cachedData_; - const std::vector &MeshVertexBuffer::data() - { - if (!isDirty_ && cachedData_.size() > 0) + auto &enabledAttribs = attributes(); + std::vector data; + for (auto &vertex : vertices_) + { + vertex.update(enabledAttribs); // Update the vertex based on the enabled attributes. + + const uint8_t *vertexData = reinterpret_cast(vertex.data()); + data.insert(data.end(), vertexData, vertexData + vertex.size()); + } + cachedData_ = data; + isDirty_ = false; return cachedData_; + } - auto &enabledAttribs = attributes(); - std::vector data; - for (auto &vertex : vertices_) + size_t Mesh::iterateEnabledAttributes(std::function callback) { - vertex.update(enabledAttribs); // Update the vertex based on the enabled attributes. - - const uint8_t *vertexData = reinterpret_cast(vertex.data()); - data.insert(data.end(), vertexData, vertexData + vertex.size()); + size_t attribsCount = 0; + for (auto &item : vertexBuffer_.attributes()) + { + std::visit([&callback](auto &&attrib) + { callback(attrib); }, + item); + attribsCount += 1; + } + return attribsCount; } - cachedData_ = data; - isDirty_ = false; - return cachedData_; - } - size_t Mesh::iterateEnabledAttributes(std::function callback) - { - size_t attribsCount = 0; - for (auto &item : vertexBuffer_.attributes()) + void Mesh::onMesh3dInitialized(shared_ptr mesh3d, std::shared_ptr) { - std::visit([&callback](auto &&attrib) - { callback(attrib); }, - item); - attribsCount += 1; + assert(mesh3d != nullptr && mesh3d->initialized() && + "Mesh3d is not initialized."); } - return attribsCount; - } - - void Mesh::onMesh3dInitialized(shared_ptr mesh3d, std::shared_ptr) - { - assert(mesh3d != nullptr && mesh3d->initialized() && - "Mesh3d is not initialized."); } -} +} // namespace endor diff --git a/src/client/builtin_scene/mesh_base.hpp b/src/client/builtin_scene/mesh_base.hpp index 13800bbad..bdce73f21 100644 --- a/src/client/builtin_scene/mesh_base.hpp +++ b/src/client/builtin_scene/mesh_base.hpp @@ -8,557 +8,560 @@ #include #include "./asset.hpp" -namespace builtin_scene +namespace endor { - // Forward declarations - class Mesh3d; - - class Primitive2d + namespace builtin_scene { - public: - virtual ~Primitive2d() = default; - }; + // Forward declarations + class Mesh3d; - class Primitive3d - { - public: - virtual ~Primitive3d() = default; - }; + class Primitive2d + { + public: + virtual ~Primitive2d() = default; + }; - class Measured3d - { - public: - virtual ~Measured3d() = default; + class Primitive3d + { + public: + virtual ~Primitive3d() = default; + }; - public: - virtual float area() = 0; - virtual float volume() = 0; - }; + class Measured3d + { + public: + virtual ~Measured3d() = default; - using PrimitiveTopology = client_graphics::WebGLDrawMode; + public: + virtual float area() = 0; + virtual float volume() = 0; + }; - template - class Indices : public std::vector - { - public: - using std::vector::vector; + using PrimitiveTopology = client_graphics::WebGLDrawMode; - public: - inline void insertIndices(const Indices &indices) + template + class Indices : public std::vector { - this->insert(this->end(), indices.begin(), indices.end()); - } - inline size_t dataSize() - { - return this->size() * sizeof(T); - } - inline void *dataBuffer() - { - return this->data(); - } - }; + public: + using std::vector::vector; + + public: + inline void insertIndices(const Indices &indices) + { + this->insert(this->end(), indices.begin(), indices.end()); + } + inline size_t dataSize() + { + return this->size() * sizeof(T); + } + inline void *dataBuffer() + { + return this->data(); + } + }; - typedef uint64_t MeshVertexAttributeId; + typedef uint64_t MeshVertexAttributeId; - /** + /** * Supported vertex formats. */ - enum class VertexFormat - { - kUnknown, - kFloat32, - kFloat32x2, - kFloat32x3, - kFloat32x4, - kUint16x2, - kUint16x4, - kUint32, - kInt32, - // More formats? - }; - - /** + enum class VertexFormat + { + kUnknown, + kFloat32, + kFloat32x2, + kFloat32x3, + kFloat32x4, + kUint16x2, + kUint16x4, + kUint32, + kInt32, + // More formats? + }; + + /** * The interface for a vertex attribute. */ - class IVertexAttribute - { - public: - virtual ~IVertexAttribute() = default; + class IVertexAttribute + { + public: + virtual ~IVertexAttribute() = default; - public: - /** + public: + /** * Get the id of the vertex attribute. */ - virtual MeshVertexAttributeId id() const = 0; - /** + virtual MeshVertexAttributeId id() const = 0; + /** * Get the name of the vertex attribute. */ - virtual const std::string name() const = 0; - /** + virtual const std::string name() const = 0; + /** * Get the vertex format: float32x2, float32x3, float32x4, uint16x2, uint16x4. */ - virtual VertexFormat format() const = 0; - /** + virtual VertexFormat format() const = 0; + /** * Get the number of components per generic vertex attribute. Must be 1, 2, 3, 4. The initial value is 4. */ - virtual size_t size() const = 0; - /** + virtual size_t size() const = 0; + /** * Get the data type of each component in the array. */ - virtual int type() const = 0; - /** + virtual int type() const = 0; + /** * Get whether integer data values should be normalized. * * For `glVertexAttribPointer`, specifies whether fixed-point data values should be normalized (`GL_TRUE`) or converted directly as * fixed-point values (`GL_FALSE`) when they are accessed. This parameter is ignored if type is `GL_FIXED`. */ - virtual bool normalized() const = 0; - /** + virtual bool normalized() const = 0; + /** * Get the byte length of this vertex attribute, it's useful to calculate the stride of the vertex buffer. */ - virtual size_t byteLength() const = 0; + virtual size_t byteLength() const = 0; - public: - bool is(const IVertexAttribute &attribute) const - { - return id() == attribute.id(); - } - }; + public: + bool is(const IVertexAttribute &attribute) const + { + return id() == attribute.id(); + } + }; - /** + /** * Traits for vertex attributes, which expose: * * - `format`: The vertex format. * - `type`: The WebGL type of the vertex attribute. */ - template - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kUnknown; - static const int type = 0; - }; + template + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kUnknown; + static const int type = 0; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kFloat32; - static const int type = WEBGL_FLOAT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kFloat32; + static const int type = WEBGL_FLOAT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kFloat32x2; - static const int type = WEBGL_FLOAT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kFloat32x2; + static const int type = WEBGL_FLOAT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kFloat32x3; - static const int type = WEBGL_FLOAT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kFloat32x3; + static const int type = WEBGL_FLOAT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kFloat32x4; - static const int type = WEBGL_FLOAT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kFloat32x4; + static const int type = WEBGL_FLOAT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kUint16x2; - static const int type = WEBGL_UNSIGNED_INT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kUint16x2; + static const int type = WEBGL_UNSIGNED_INT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kUint16x4; - static const int type = WEBGL_UNSIGNED_INT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kUint16x4; + static const int type = WEBGL_UNSIGNED_INT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kUint32; - static const int type = WEBGL_UNSIGNED_INT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kUint32; + static const int type = WEBGL_UNSIGNED_INT; + }; - template <> - class VertexAttributeTraits - { - public: - static const VertexFormat format = VertexFormat::kInt32; - static const int type = WEBGL_INT; - }; + template <> + class VertexAttributeTraits + { + public: + static const VertexFormat format = VertexFormat::kInt32; + static const int type = WEBGL_INT; + }; - /** + /** * The vertex attribute, it contains name, id and format. */ - template - class VertexAttribute : public IVertexAttribute - { - using Traits = VertexAttributeTraits; + template + class VertexAttribute : public IVertexAttribute + { + using Traits = VertexAttributeTraits; - public: - /** + public: + /** * Create a new vertex attribute. * * @param name The name of the vertex attribute. * @param id The id of the vertex attribute. * @param format The vertex format. */ - VertexAttribute(std::string name, uint64_t id, VertexFormat format) - : name_(name) - , id_(id) - , format_(format) - { - } - - public: - MeshVertexAttributeId id() const override - { - return id_; - } - const std::string name() const override - { - return name_; - } - VertexFormat format() const override - { - return format_; - } - size_t size() const override - { - return N; - } - int type() const override - { - return Traits::type; - } - bool normalized() const override - { - return false; - } - size_t byteLength() const override - { - return N * sizeof(Format); - } - - private: - /** + VertexAttribute(std::string name, uint64_t id, VertexFormat format) + : name_(name) + , id_(id) + , format_(format) + { + } + + public: + MeshVertexAttributeId id() const override + { + return id_; + } + const std::string name() const override + { + return name_; + } + VertexFormat format() const override + { + return format_; + } + size_t size() const override + { + return N; + } + int type() const override + { + return Traits::type; + } + bool normalized() const override + { + return false; + } + size_t byteLength() const override + { + return N * sizeof(Format); + } + + private: + /** * The name of the vertex attribute. */ - std::string name_; - /** + std::string name_; + /** * The id of the vertex attribute. */ - MeshVertexAttributeId id_; - /** + MeshVertexAttributeId id_; + /** * The vertex format. */ - VertexFormat format_ = Traits::format; - }; - - using MeshVertexAttribute = std::variant, - VertexAttribute, - VertexAttribute, - VertexAttribute>; - - // Custom comparison operator for `MeshVertexAttribute`. - inline bool operator==(const MeshVertexAttribute &lhs, const MeshVertexAttribute &rhs) - { - return std::visit([&](auto &&attrib) - { return std::visit([&](auto &&attrib2) - { return attrib.id() == attrib2.id(); }, - rhs); }, - lhs); - } - - class Vertex - { - friend class MeshVertexBuffer; - - public: - inline static auto ATTRIBUTE_POSITION = VertexAttribute("position", 0, VertexFormat::kFloat32x3); - inline static auto ATTRIBUTE_NORMAL = VertexAttribute("normal", 1, VertexFormat::kFloat32x3); - inline static auto ATTRIBUTE_UV0 = VertexAttribute("texCoord", 2, VertexFormat::kFloat32x2); - inline static auto ATTRIBUTE_UV1 = VertexAttribute("texCoord1", 3, VertexFormat::kFloat32x2); - inline static auto ATTRIBUTE_TANGENT = VertexAttribute("tangent", 4, VertexFormat::kFloat32x4); - inline static auto ATTRIBUTE_COLOR = VertexAttribute("color", 5, VertexFormat::kFloat32x4); - inline static auto ATTRIBUTE_JOINT_WEIGHTS = VertexAttribute("jointWeights", 6, VertexFormat::kFloat32x4); - inline static auto ATTRIBUTE_JOINT_INDEX = VertexAttribute("jointIndex", 7, VertexFormat::kUint16x4); - - public: - Vertex() = default; - Vertex(glm::vec3 position, glm::vec3 normal, glm::vec2 uv) - : position(position) - , normal(normal) - , uv0(uv) - { - } - - public: - size_t size() const - { - return compactData_.size(); - } - const void *data() const - { - return compactData_.data(); - } - - private: - void insertAttributeToCompactData(const IVertexAttribute &attribute); - void update(const std::vector &enabledAttributes); - - public: - glm::vec3 position; - glm::vec3 normal; - glm::vec2 uv0; - glm::vec2 uv1; - glm::vec4 tangent; - glm::vec4 color; - glm::vec4 jointWeights; - glm::u16vec4 jointIndex; - // TODO: support custom attributes? - - private: - std::vector compactData_; - }; - - class MeshVertexBuffer - { - public: - MeshVertexBuffer() = default; - MeshVertexBuffer(const MeshVertexBuffer &) = delete; - - public: - /** + VertexFormat format_ = Traits::format; + }; + + using MeshVertexAttribute = std::variant, + VertexAttribute, + VertexAttribute, + VertexAttribute>; + + // Custom comparison operator for `MeshVertexAttribute`. + inline bool operator==(const MeshVertexAttribute &lhs, const MeshVertexAttribute &rhs) + { + return std::visit([&](auto &&attrib) + { return std::visit([&](auto &&attrib2) + { return attrib.id() == attrib2.id(); }, + rhs); }, + lhs); + } + + class Vertex + { + friend class MeshVertexBuffer; + + public: + inline static auto ATTRIBUTE_POSITION = VertexAttribute("position", 0, VertexFormat::kFloat32x3); + inline static auto ATTRIBUTE_NORMAL = VertexAttribute("normal", 1, VertexFormat::kFloat32x3); + inline static auto ATTRIBUTE_UV0 = VertexAttribute("texCoord", 2, VertexFormat::kFloat32x2); + inline static auto ATTRIBUTE_UV1 = VertexAttribute("texCoord1", 3, VertexFormat::kFloat32x2); + inline static auto ATTRIBUTE_TANGENT = VertexAttribute("tangent", 4, VertexFormat::kFloat32x4); + inline static auto ATTRIBUTE_COLOR = VertexAttribute("color", 5, VertexFormat::kFloat32x4); + inline static auto ATTRIBUTE_JOINT_WEIGHTS = VertexAttribute("jointWeights", 6, VertexFormat::kFloat32x4); + inline static auto ATTRIBUTE_JOINT_INDEX = VertexAttribute("jointIndex", 7, VertexFormat::kUint16x4); + + public: + Vertex() = default; + Vertex(glm::vec3 position, glm::vec3 normal, glm::vec2 uv) + : position(position) + , normal(normal) + , uv0(uv) + { + } + + public: + size_t size() const + { + return compactData_.size(); + } + const void *data() const + { + return compactData_.data(); + } + + private: + void insertAttributeToCompactData(const IVertexAttribute &attribute); + void update(const std::vector &enabledAttributes); + + public: + glm::vec3 position; + glm::vec3 normal; + glm::vec2 uv0; + glm::vec2 uv1; + glm::vec4 tangent; + glm::vec4 color; + glm::vec4 jointWeights; + glm::u16vec4 jointIndex; + // TODO: support custom attributes? + + private: + std::vector compactData_; + }; + + class MeshVertexBuffer + { + public: + MeshVertexBuffer() = default; + MeshVertexBuffer(const MeshVertexBuffer &) = delete; + + public: + /** * Inserts a vertex to the buffer. * * @param vertex The vertex data to add. */ - void insertVertex(Vertex &vertex) - { - vertices_.push_back(vertex); - isDirty_ = true; - } + void insertVertex(Vertex &vertex) + { + vertices_.push_back(vertex); + isDirty_ = true; + } - /** + /** * Inserts vertices from another buffer. * * @param buffer The buffer to insert vertices from. * @param updateAttributes Whether to update the attributes configuration. */ - void insertVertices(const MeshVertexBuffer &buffer, bool updateAttributes); + void insertVertices(const MeshVertexBuffer &buffer, bool updateAttributes); - /** + /** * Enable a vertex attribute. */ - void enableAttribute(const MeshVertexAttribute &attribute); + void enableAttribute(const MeshVertexAttribute &attribute); - /** + /** * Disable a vertex attribute. */ - void disableAttribute(const MeshVertexAttribute &attribute); + void disableAttribute(const MeshVertexAttribute &attribute); - /** + /** * Get the enabled vertex attributes. */ - inline const std::vector &attributes() const - { - return enabledAttributes_; - } + inline const std::vector &attributes() const + { + return enabledAttributes_; + } - /** + /** * Get the number of vertices in the buffer. */ - inline size_t vertexCount() const - { - return vertices_.size(); - } + inline size_t vertexCount() const + { + return vertices_.size(); + } - /** + /** * Get the stride of the vertex buffer. */ - inline size_t stride() const - { - return stride_; - } + inline size_t stride() const + { + return stride_; + } - /** + /** * Clear the vertex buffer. */ - void clear(); + void clear(); - /** + /** * Get a pointer to the interleaved vertex data. * * @return A pointer to the vertex data. */ - const std::vector &data(); + const std::vector &data(); - private: - std::vector vertices_; // Container to store vertices - std::vector enabledAttributes_; // Enabled vertex attributes - std::vector cachedData_; // Cached interleaved vertex data - bool isDirty_ = true; // Whether the buffer is dirty - size_t stride_ = 0; // The stride of the vertex buffer - }; + private: + std::vector vertices_; // Container to store vertices + std::vector enabledAttributes_; // Enabled vertex attributes + std::vector cachedData_; // Cached interleaved vertex data + bool isDirty_ = true; // Whether the buffer is dirty + size_t stride_ = 0; // The stride of the vertex buffer + }; - class Mesh : public Measured3d - { - public: - /** + class Mesh : public Measured3d + { + public: + /** * Create a new mesh. * * @param name The name of the mesh. */ - Mesh(std::string name, PrimitiveTopology primitiveTopology) - : name(name) - , primitiveTopology(primitiveTopology) - { - } - virtual ~Mesh() = default; + Mesh(std::string name, PrimitiveTopology primitiveTopology) + : name(name) + , primitiveTopology(primitiveTopology) + { + } + virtual ~Mesh() = default; - public: - /** + public: + /** * Update the indices of the mesh. * * @param indices The indices of the mesh. */ - inline void updateIndices(Indices indices) - { - indices_ = indices; - } - /** + inline void updateIndices(Indices indices) + { + indices_ = indices; + } + /** * Insert the vertex to the mesh. * * @param vertex The vertex to insert. */ - inline void insertVertex(Vertex &vertex) - { - vertexBuffer_.insertVertex(vertex); - } - /** + inline void insertVertex(Vertex &vertex) + { + vertexBuffer_.insertVertex(vertex); + } + /** * Insert a new vertex from the given position, normal, and uv. * * @param position The position of the vertex. * @param normal The normal of the vertex. * @param uv The uv of the vertex. */ - inline void insertVertex(glm::vec3 position, glm::vec3 normal, glm::vec2 uv) - { - Vertex vertex(position, normal, uv); - insertVertex(vertex); - } - /** + inline void insertVertex(glm::vec3 position, glm::vec3 normal, glm::vec2 uv) + { + Vertex vertex(position, normal, uv); + insertVertex(vertex); + } + /** * Enable a vertex attribute. * * @param attribute The vertex attribute to enable. */ - inline void enableAttribute(const MeshVertexAttribute &attribute) - { - vertexBuffer_.enableAttribute(attribute); - } - /** + inline void enableAttribute(const MeshVertexAttribute &attribute) + { + vertexBuffer_.enableAttribute(attribute); + } + /** * Disable a vertex attribute. * * @param attribute The vertex attribute to disable. */ - inline void disableAttribute(const MeshVertexAttribute &attribute) - { - vertexBuffer_.disableAttribute(attribute); - } - /** + inline void disableAttribute(const MeshVertexAttribute &attribute) + { + vertexBuffer_.disableAttribute(attribute); + } + /** * The attributes stride is the sum of all enabled attributes byte length, that's used by the * graphics API to get the correct attribute data. */ - inline size_t attributesStride() const - { - return vertexBuffer_.stride(); - } + inline size_t attributesStride() const + { + return vertexBuffer_.stride(); + } - public: - /** + public: + /** * @returns The mesh indices. */ - inline const Indices &indices() const - { - return indices_; - } - /** + inline const Indices &indices() const + { + return indices_; + } + /** * @returns The mesh vertex buffer with all attributes. */ - inline MeshVertexBuffer &vertexBuffer() - { - return vertexBuffer_; - } - /** + inline MeshVertexBuffer &vertexBuffer() + { + return vertexBuffer_; + } + /** * Iterate the enabled attributes of the mesh. * * @param callback The callback to call for each attribute. * @returns The number of enabled attributes. */ - size_t iterateEnabledAttributes(std::function callback); + size_t iterateEnabledAttributes(std::function callback); - /** + /** * @returns Whether the mesh is dirty to update the vertex buffer data. */ - inline bool isDirty() const - { - return isDirty_; - } - /** + inline bool isDirty() const + { + return isDirty_; + } + /** * Set the mesh dirty flag: `true` to update the vertex buffer data, `false` otherwise. */ - inline void setDirty(bool dirty) - { - isDirty_ = dirty; - } + inline void setDirty(bool dirty) + { + isDirty_ = dirty; + } - public: - /** + public: + /** * When the `Mesh` has been initialized for `Mesh3d`, it's used to perform additional setup for specific mesh types. */ - virtual void onMesh3dInitialized(std::shared_ptr, std::shared_ptr); - /** + virtual void onMesh3dInitialized(std::shared_ptr, std::shared_ptr); + /** * Configuring the vertex attribs before initializing material. */ - virtual void onConfigureVertexAttribs(std::shared_ptr, std::shared_ptr) - { - // Default implementation does nothing. - } - virtual void onConfigureInstanceAttribs(std::shared_ptr, std::shared_ptr) - { - // Default implementation does nothing. - } - - public: - /** + virtual void onConfigureVertexAttribs(std::shared_ptr, std::shared_ptr) + { + // Default implementation does nothing. + } + virtual void onConfigureInstanceAttribs(std::shared_ptr, std::shared_ptr) + { + // Default implementation does nothing. + } + + public: + /** * The mesh name. */ - std::string name; - /** + std::string name; + /** * The primitive topology of the mesh. */ - PrimitiveTopology primitiveTopology; + PrimitiveTopology primitiveTopology; - protected: - Indices indices_{}; - MeshVertexBuffer vertexBuffer_; - bool isDirty_ = true; - }; -} + protected: + Indices indices_{}; + MeshVertexBuffer vertexBuffer_; + bool isDirty_ = true; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/mesh_material.cpp b/src/client/builtin_scene/mesh_material.cpp index c6471d730..904f2d5a9 100644 --- a/src/client/builtin_scene/mesh_material.cpp +++ b/src/client/builtin_scene/mesh_material.cpp @@ -1,43 +1,46 @@ #include "./mesh_material.hpp" #include "./meshes.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - - bool MeshMaterial3d::matchesPass(const RenderPass &pass) const - { - assert(pass == RenderPass::kOpaques || pass == RenderPass::kTransparents); - if (pass == RenderPass::kOpaques) - return isOpaque(); - else if (pass == RenderPass::kTransparents) - return isTransparent(); - else - return false; - } - - void MeshMaterial3d::initialize(shared_ptr glContext, - shared_ptr program, - shared_ptr mesh) + namespace builtin_scene { - if (program == nullptr) - throw runtime_error("The program is not initialized."); + using namespace std; - // Configure the mesh attributes - mesh->configureVertexAttribs(program); - mesh->configureInstanceAttribs(program); - - // Initialize the material with the WebGL context and program. - if (material_->initialize(glContext, program)) + bool MeshMaterial3d::matchesPass(const RenderPass &pass) const { - program_ = program; - glContext_ = glContext; - mesh_ = mesh; - initialized_ = true; + assert(pass == RenderPass::kOpaques || pass == RenderPass::kTransparents); + if (pass == RenderPass::kOpaques) + return isOpaque(); + else if (pass == RenderPass::kTransparents) + return isTransparent(); + else + return false; } - else + + void MeshMaterial3d::initialize(shared_ptr glContext, + shared_ptr program, + shared_ptr mesh) { - throw runtime_error("Failed to initialize the material: " + material_->name()); + if (program == nullptr) + throw runtime_error("The program is not initialized."); + + // Configure the mesh attributes + mesh->configureVertexAttribs(program); + mesh->configureInstanceAttribs(program); + + // Initialize the material with the WebGL context and program. + if (material_->initialize(glContext, program)) + { + program_ = program; + glContext_ = glContext; + mesh_ = mesh; + initialized_ = true; + } + else + { + throw runtime_error("Failed to initialize the material: " + material_->name()); + } } } -} +} // namespace endor diff --git a/src/client/builtin_scene/mesh_material.hpp b/src/client/builtin_scene/mesh_material.hpp index c0f8ccdb1..6d19630cf 100644 --- a/src/client/builtin_scene/mesh_material.hpp +++ b/src/client/builtin_scene/mesh_material.hpp @@ -10,104 +10,106 @@ #include "./renderer/render_pass.hpp" #include "./renderer/render_target.hpp" -namespace builtin_scene +namespace endor { - class MeshMaterial3d : public ecs::Component + namespace builtin_scene { - public: - using ecs::Component::Component; - - public: - MeshMaterial3d(std::shared_ptr material) - : ecs::Component() - , material_(material) + class MeshMaterial3d : public ecs::Component { - } + public: + using ecs::Component::Component; + + public: + MeshMaterial3d(std::shared_ptr material) + : ecs::Component() + , material_(material) + { + } - public: - /** + public: + /** * @returns The material handle. */ - template - requires std::is_base_of::value - inline std::shared_ptr material() const - { - if (material_ == nullptr) - return nullptr; + template + requires std::is_base_of::value + inline std::shared_ptr material() const + { + if (material_ == nullptr) + return nullptr; - // If the material type is the same as the base type, return the handle as is. - if constexpr (std::is_same::value) - return material_; + // If the material type is the same as the base type, return the handle as is. + if constexpr (std::is_same::value) + return material_; - // Downcast the material to the specified type. - return std::dynamic_pointer_cast(material_); - } - /** + // Downcast the material to the specified type. + return std::dynamic_pointer_cast(material_); + } + /** * @returns The mesh handle. */ - inline std::shared_ptr mesh() const - { - return mesh_; - } - /** + inline std::shared_ptr mesh() const + { + return mesh_; + } + /** * @returns The program of the material. */ - inline std::shared_ptr program() const - { - return program_; - } + inline std::shared_ptr program() const + { + return program_; + } - inline bool isOpaque() const - { - return material_->isOpaque(); - } - inline bool isTransparent() const - { - return !isOpaque(); - } + inline bool isOpaque() const + { + return material_->isOpaque(); + } + inline bool isTransparent() const + { + return !isOpaque(); + } - /** + /** * Check if the material matches the given render pass. * * @param pass The render pass to check against. * @returns Whether the material matches the render pass. */ - bool matchesPass(const RenderPass &pass) const; + bool matchesPass(const RenderPass &pass) const; - /** + /** * Initialize the `MeshMaterial3d` instance with the given WebGL context and program. * * @param glContext The WebGL context to use. * @param program The WebGL program to use. */ - void initialize(std::shared_ptr glContext, - std::shared_ptr program, - std::shared_ptr mesh); - /** + void initialize(std::shared_ptr glContext, + std::shared_ptr program, + std::shared_ptr mesh); + /** * @returns Whether the material is initialized. */ - inline bool initialized() const - { - return initialized_; - } - /** + inline bool initialized() const + { + return initialized_; + } + /** * Get the shader source of this material. * * @param type The type of the shader, either vertex or fragment. * @returns The shader source. * @throws std::runtime_error If the shader type is not vertex or fragment. */ - inline std::string getShaderSource(client_graphics::WebGLShaderType type) const - { - const auto &defines = material_->getDefinesWithGlobals(); - if (type == client_graphics::WebGLShaderType::kVertex) - return material_->vertexShader().shader(defines).source; - else if (type == client_graphics::WebGLShaderType::kFragment) - return material_->fragmentShader().shader(defines).source; - else - throw std::runtime_error("The shader type is not supported."); - } - /** + inline std::string getShaderSource(client_graphics::WebGLShaderType type) const + { + const auto &defines = material_->getDefinesWithGlobals(); + if (type == client_graphics::WebGLShaderType::kVertex) + return material_->vertexShader().shader(defines).source; + else if (type == client_graphics::WebGLShaderType::kFragment) + return material_->fragmentShader().shader(defines).source; + else + throw std::runtime_error("The shader type is not supported."); + } + /** * Custom drawing implementation for materials that need special rendering logic. * Return true if the material handled the drawing, false to use default drawing. * @@ -115,39 +117,40 @@ namespace builtin_scene * @param renderPass The render pass to use, which can be used to filter the objects or instances to render. * @param renderTarget The render target to draw the mesh with. */ - inline void drawMeshImpl(std::shared_ptr mesh, - RenderPass renderPass, - std::optional renderTarget) - { - assert(material_ != nullptr); - material_->drawMeshImpl(program_, *mesh, renderPass, renderTarget); - } - /** + inline void drawMeshImpl(std::shared_ptr mesh, + RenderPass renderPass, + std::optional renderTarget) + { + assert(material_ != nullptr); + material_->drawMeshImpl(program_, *mesh, renderPass, renderTarget); + } + /** * Called before drawing the mesh with the material. * * @param mesh The mesh to draw. */ - inline void onBeforeDrawMesh(std::shared_ptr mesh) - { - assert(material_ != nullptr); - material_->onBeforeDrawMesh(program_, mesh); - } - /** + inline void onBeforeDrawMesh(std::shared_ptr mesh) + { + assert(material_ != nullptr); + material_->onBeforeDrawMesh(program_, mesh); + } + /** * Called after drawing the mesh with the material. * * @param mesh The mesh to draw. */ - inline void onAfterDrawMesh(std::shared_ptr mesh) - { - assert(material_ != nullptr); - material_->onAfterDrawMesh(program_, mesh); - } + inline void onAfterDrawMesh(std::shared_ptr mesh) + { + assert(material_ != nullptr); + material_->onAfterDrawMesh(program_, mesh); + } - private: - std::shared_ptr material_ = nullptr; - std::shared_ptr mesh_ = nullptr; - std::shared_ptr program_; - std::weak_ptr glContext_; - bool initialized_ = false; - }; -} + private: + std::shared_ptr material_ = nullptr; + std::shared_ptr mesh_ = nullptr; + std::shared_ptr program_; + std::weak_ptr glContext_; + bool initialized_ = false; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes.cpp b/src/client/builtin_scene/meshes.cpp index d59e04456..f43a2c8bf 100644 --- a/src/client/builtin_scene/meshes.cpp +++ b/src/client/builtin_scene/meshes.cpp @@ -1,12 +1,15 @@ #include "meshes.hpp" #include "gaussian_splats_mesh.hpp" -namespace builtin_scene +namespace endor { - std::shared_ptr MeshBuilder::CreateGaussianSplatsMesh() + namespace builtin_scene { - auto splatsMesh = std::make_shared(); - splatsMesh->build(); - return splatsMesh; + std::shared_ptr MeshBuilder::CreateGaussianSplatsMesh() + { + auto splatsMesh = std::make_shared(); + splatsMesh->build(); + return splatsMesh; + } } -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/meshes.hpp b/src/client/builtin_scene/meshes.hpp index b6a6c74d6..d26c8c506 100644 --- a/src/client/builtin_scene/meshes.hpp +++ b/src/client/builtin_scene/meshes.hpp @@ -16,254 +16,259 @@ #include "./instanced_mesh.hpp" #include "./gaussian_splats_mesh.hpp" -namespace builtin_scene +namespace endor { - // Forward declaration - class GaussianSplatsMesh; - - class Meshes : public asset::Assets + namespace builtin_scene { - public: - using asset::Assets::Assets; - }; + // Forward declaration + class GaussianSplatsMesh; - class Mesh3d : public ecs::Component, - public std::enable_shared_from_this - { - using ecs::Component::Component; + class Meshes : public asset::Assets + { + public: + using asset::Assets::Assets; + }; + + class Mesh3d : public ecs::Component, + public std::enable_shared_from_this + { + using ecs::Component::Component; - public: - /** + public: + /** * Construct a mesh3d with the given mesh handle. * * @param handle The mesh handle. * @param disableRendering Whether to disable rendering of the mesh, by default it's disabled. */ - Mesh3d(std::shared_ptr handle, bool disableRendering = true) - : ecs::Component() - , handle_(handle) - , disableRendering_(disableRendering) - { - } + Mesh3d(std::shared_ptr handle, bool disableRendering = true) + : ecs::Component() + , handle_(handle) + , disableRendering_(disableRendering) + { + } - public: - /** + public: + /** * Get if the mesh's handle is the given type. * * @tparam MeshType The type of the mesh to check. * @returns If the mesh's handle is the given type. */ - template - requires std::is_same::value || - std::is_same::value || - std::is_base_of::value - bool is() const - { - return std::dynamic_pointer_cast(handle_) != nullptr; - } - /** + template + requires std::is_same::value || + std::is_same::value || + std::is_base_of::value + bool + is() const + { + return std::dynamic_pointer_cast(handle_) != nullptr; + } + /** * Get if the mesh's handle is an instanced mesh. */ - inline bool isInstancedMesh() const - { - return is() || is(); - } - /** + inline bool isInstancedMesh() const + { + return is() || is(); + } + /** * Get the handle of the mesh as the given type. * * @tparam MeshType The type of the mesh to get the handle as. * @returns The handle of the mesh as the given type. */ - template - requires std::is_same::value || - std::is_same::value || - std::is_base_of::value - inline std::shared_ptr getHandleAs() const - { - if constexpr (std::is_same::value) - return handle_; - else - return std::dynamic_pointer_cast(handle_); - } - /** + template + requires std::is_same::value || + std::is_same::value || + std::is_base_of::value + inline std::shared_ptr + getHandleAs() const + { + if constexpr (std::is_same::value) + return handle_; + else + return std::dynamic_pointer_cast(handle_); + } + /** * Get the handle reference of the mesh as the given type, it will returns the object reference to the mesh, and * throws an exception if the mesh is not valid. * * @tparam MeshType The type of the mesh to get the handle as. * @returns The handle of the mesh as the given type. */ - template - requires std::is_same::value || - std::is_same::value || - std::is_base_of::value - MeshType &getHandleCheckedAsRef() const - { - auto mesh = getHandleAs(); - assert(mesh != nullptr && "The mesh handle is not valid."); - return *mesh; - } - /** + template + requires std::is_same::value || + std::is_same::value || + std::is_base_of::value + MeshType & + getHandleCheckedAsRef() const + { + auto mesh = getHandleAs(); + assert(mesh != nullptr && "The mesh handle is not valid."); + return *mesh; + } + /** * @returns The vertex array object. */ - inline std::shared_ptr vertexArrayObject() const - { - return vao_; - } - /** + inline std::shared_ptr vertexArrayObject() const + { + return vao_; + } + /** * @returns The vertex buffer object. */ - inline std::shared_ptr vertexBufferObject() const - { - return vbo_; - } - /** + inline std::shared_ptr vertexBufferObject() const + { + return vbo_; + } + /** * @returns The element buffer object. */ - inline std::shared_ptr elementBufferObject() const - { - return ebo_; - } + inline std::shared_ptr elementBufferObject() const + { + return ebo_; + } - /** + /** * Set if the mesh3d is initialized. * * @param glContext The WebGL context. * @param vao The vertex array object. * @param vbo The vertex buffer object. */ - inline void initialize(std::shared_ptr glContext, - std::shared_ptr vao, - std::shared_ptr vbo, - std::shared_ptr ebo) - { - if (vao == nullptr) - throw std::runtime_error("The vertex array object is not initialized."); + inline void initialize(std::shared_ptr glContext, + std::shared_ptr vao, + std::shared_ptr vbo, + std::shared_ptr ebo) + { + if (vao == nullptr) + throw std::runtime_error("The vertex array object is not initialized."); - vao_ = vao; - vbo_ = vbo; - ebo_ = ebo; - glContext_ = glContext; - initialized_ = true; + vao_ = vao; + vbo_ = vbo; + ebo_ = ebo; + glContext_ = glContext; + initialized_ = true; - handle_->onMesh3dInitialized(shared_from_this(), glContext); - } - inline bool initialized() const - { - return initialized_; - } + handle_->onMesh3dInitialized(shared_from_this(), glContext); + } + inline bool initialized() const + { + return initialized_; + } - /** + /** * Configure the vertex attributes of the mesh3d. */ - void configureVertexAttribs(std::shared_ptr program, - std::shared_ptr vao = nullptr) - { - auto glContext = glContext_.lock(); - client_graphics::WebGLVertexArrayScope vaoScope(glContext, vao == nullptr ? vao_ : vao); - handle_->onConfigureVertexAttribs(shared_from_this(), program); - } + void configureVertexAttribs(std::shared_ptr program, + std::shared_ptr vao = nullptr) + { + auto glContext = glContext_.lock(); + client_graphics::WebGLVertexArrayScope vaoScope(glContext, vao == nullptr ? vao_ : vao); + handle_->onConfigureVertexAttribs(shared_from_this(), program); + } - /** + /** * Configure the instance attributes of the mesh3d. */ - void configureInstanceAttribs(std::shared_ptr program, - std::shared_ptr vao = nullptr) - { - auto glContext = glContext_.lock(); - client_graphics::WebGLVertexArrayScope vaoScope(glContext, vao == nullptr ? vao_ : vao); - handle_->onConfigureInstanceAttribs(shared_from_this(), program); - } + void configureInstanceAttribs(std::shared_ptr program, + std::shared_ptr vao = nullptr) + { + auto glContext = glContext_.lock(); + client_graphics::WebGLVertexArrayScope vaoScope(glContext, vao == nullptr ? vao_ : vao); + handle_->onConfigureInstanceAttribs(shared_from_this(), program); + } - /** + /** * @returns If the mesh3d needs to update the underlying vertex buffer data. */ - inline bool needsUpdate() const - { - return handle_->isDirty(); - } - /** + inline bool needsUpdate() const + { + return handle_->isDirty(); + } + /** * @returns If the mesh3d is disabled for rendering. */ - inline bool isRenderingDisabled() - { - return disableRendering_; - } - /** + inline bool isRenderingDisabled() + { + return disableRendering_; + } + /** * Disable rendering of the mesh, it causes the mesh not to be rendered. */ - inline void disableRendering() - { - disableRendering_ = true; - } - /** + inline void disableRendering() + { + disableRendering_ = true; + } + /** * Resume rendering of the mesh. */ - inline void resumeRendering() - { - disableRendering_ = false; - } - /** + inline void resumeRendering() + { + disableRendering_ = false; + } + /** * @returns The primitive topology of the mesh. */ - inline PrimitiveTopology primitiveTopology() const - { - return handle_->primitiveTopology; - } - /** + inline PrimitiveTopology primitiveTopology() const + { + return handle_->primitiveTopology; + } + /** * @returns The indices of the mesh. */ - inline const Indices &indices() const - { - return handle_->indices(); - } - /** + inline const Indices &indices() const + { + return handle_->indices(); + } + /** * @returns The vertex buffer of the mesh. */ - inline MeshVertexBuffer &vertexBuffer() - { - return handle_->vertexBuffer(); - } - /** + inline MeshVertexBuffer &vertexBuffer() + { + return handle_->vertexBuffer(); + } + /** * Iterate the enabled attributes of the mesh. * * @param callback The callback to call for each attribute. * @returns The number of enabled attributes. */ - inline size_t iterateEnabledAttributes(std::shared_ptr program, - std::function callback) - { - auto glContext = glContext_.lock(); - assert(glContext != nullptr); - size_t stride = handle_->attributesStride(); - size_t offset = 0; - - auto configureAttrib = [callback, glContext, program, stride, &offset](const IVertexAttribute &attrib) + inline size_t iterateEnabledAttributes(std::shared_ptr program, + std::function callback) { - auto loc = glContext->getAttribLocation(program, attrib.name()); - if (loc.has_value()) - callback(attrib, loc.value().index.value_or(-1), stride, offset); - offset += attrib.byteLength(); - }; - return handle_->iterateEnabledAttributes(configureAttrib); - } + auto glContext = glContext_.lock(); + assert(glContext != nullptr); + size_t stride = handle_->attributesStride(); + size_t offset = 0; - private: - std::shared_ptr handle_ = nullptr; - std::shared_ptr vao_; - std::shared_ptr vbo_; - std::shared_ptr ebo_; - std::weak_ptr glContext_; - bool initialized_ = false; - bool disableRendering_ = true; - }; + auto configureAttrib = [callback, glContext, program, stride, &offset](const IVertexAttribute &attrib) + { + auto loc = glContext->getAttribLocation(program, attrib.name()); + if (loc.has_value()) + callback(attrib, loc.value().index.value_or(-1), stride, offset); + offset += attrib.byteLength(); + }; + return handle_->iterateEnabledAttributes(configureAttrib); + } - class MeshBuilder - { - public: - /** + private: + std::shared_ptr handle_ = nullptr; + std::shared_ptr vao_; + std::shared_ptr vbo_; + std::shared_ptr ebo_; + std::weak_ptr glContext_; + bool initialized_ = false; + bool disableRendering_ = true; + }; + + class MeshBuilder + { + public: + /** * Create an instanced mesh with the given name. * * @tparam MeshType The type of the mesh to create. @@ -273,13 +278,13 @@ namespace builtin_scene * @param args The arguments to pass to the mesh * @returns The created instanced mesh. */ - template - requires std::is_base_of::value - static inline std::shared_ptr> CreateInstancedMesh(const std::string &name, Args &&...args) - { - return CreateAndBuild>(name, std::forward(args)...); - } - /** + template + requires std::is_base_of::value + static inline std::shared_ptr> CreateInstancedMesh(const std::string &name, Args &&...args) + { + return CreateAndBuild>(name, std::forward(args)...); + } + /** * Create a box mesh with the given width, height, and depth. * * @param width The width of the box. @@ -287,61 +292,61 @@ namespace builtin_scene * @param depth The depth of the box. * @return The created box mesh. */ - static inline std::shared_ptr CreateBox(float width, float height, float depth) - { - return CreateAndBuild(width, height, depth); - } - /** + static inline std::shared_ptr CreateBox(float width, float height, float depth) + { + return CreateAndBuild(width, height, depth); + } + /** * Create a box mesh with the given size. * * @param size The size of the box. * @return The created box mesh. */ - static inline std::shared_ptr CreateBox(float size) - { - return CreateAndBuild(size); - } - /** + static inline std::shared_ptr CreateBox(float size) + { + return CreateAndBuild(size); + } + /** * Create a cube mesh with the given size. * * @param size The size of the cube. * @return The created cube mesh. */ - static inline std::shared_ptr CreateCube(float size) - { - return CreateAndBuild(size); - } - /** + static inline std::shared_ptr CreateCube(float size) + { + return CreateAndBuild(size); + } + /** * Create a plane mesh with the given normal and half size. * * @param normal The normal of the plane. * @param halfSize The half size of the plane. * @return The created plane mesh. */ - static inline std::shared_ptr CreatePlane(math::Dir3 normal, glm::vec2 halfSize) - { - return CreateAndBuild(normal, halfSize); - } - /** + static inline std::shared_ptr CreatePlane(math::Dir3 normal, glm::vec2 halfSize) + { + return CreateAndBuild(normal, halfSize); + } + /** * Create a (UV) sphere mesh with the given parameters. * * @param radius The radius of the sphere. * @param sectors The number of longitudinal sectors, aka horizontal resolution. The default is 32. * @param stacks The number of latitudinal stacks, aka vertical resolution. The default is 18. */ - static inline std::shared_ptr CreateSphere(float radius, uint32_t sectors = 32, uint32_t stacks = 18) - { - return CreateAndBuild(radius, sectors, stacks); - } + static inline std::shared_ptr CreateSphere(float radius, uint32_t sectors = 32, uint32_t stacks = 18) + { + return CreateAndBuild(radius, sectors, stacks); + } - /** + /** * Create a GaussianSplatsMesh for rendering Gaussian splats. * This is a global mesh that manages all splats across the scene. */ - static std::shared_ptr CreateGaussianSplatsMesh(); + static std::shared_ptr CreateGaussianSplatsMesh(); - private: - /** + private: + /** * Create a new mesh and build it. * * @tparam MeshType The type of the mesh to create. @@ -349,12 +354,13 @@ namespace builtin_scene * @param args The arguments to pass to the mesh * @return The created mesh. */ - template - static std::shared_ptr CreateAndBuild(Args &&...args) - { - auto newMesh = std::make_shared(std::forward(args)...); - newMesh->build(); - return newMesh; - } - }; -} + template + static std::shared_ptr CreateAndBuild(Args &&...args) + { + auto newMesh = std::make_shared(std::forward(args)...); + newMesh->build(); + return newMesh; + } + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes/box.hpp b/src/client/builtin_scene/meshes/box.hpp index e89f85e17..938aca505 100644 --- a/src/client/builtin_scene/meshes/box.hpp +++ b/src/client/builtin_scene/meshes/box.hpp @@ -3,105 +3,107 @@ #include #include "../mesh_base.hpp" -namespace builtin_scene::meshes +namespace endor { - class Box : public Mesh, - public MeshBuilder, - public Primitive3d + namespace builtin_scene::meshes { - public: - Box(float width, float height, float depth) - : Mesh("Box", PrimitiveTopology::kTriangles) - , width_(width) - , height_(height) - , depth_(depth) + class Box : public Mesh, + public MeshBuilder, + public Primitive3d { - } - Box(float size) - : Box(size, size, size) - { - } - - public: - inline float width() - { - return width_; - } - inline float height() - { - return height_; - } - inline float depth() - { - return depth_; - } - - public: - float area() override - { - return 2.0f * (width_ * height_ + width_ * depth_ + height_ * depth_); - } - float volume() override - { - return width_ * height_ * depth_; - } - void build() override - { - glm::vec3 max = glm::vec3(width_ / 2.0f, height_ / 2.0f, depth_ / 2.0f); - glm::vec3 min = -max; - size_t faces = 6; - auto vertexCount = 4 * faces; - - // Front + public: + Box(float width, float height, float depth) + : Mesh("Box", PrimitiveTopology::kTriangles) + , width_(width) + , height_(height) + , depth_(depth) { - insertVertex(glm::vec3(min.x, min.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(max.x, min.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(max.x, max.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(min.x, max.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 1.0f)); } - - // Back + Box(float size) + : Box(size, size, size) { - insertVertex(glm::vec3(min.x, max.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(max.x, max.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(max.x, min.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(min.x, min.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(0.0f, 1.0f)); } - // Right + public: + inline float width() { - insertVertex(glm::vec3(max.x, min.y, min.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(max.x, max.y, min.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(max.x, max.y, max.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(max.x, min.y, max.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + return width_; } - - // Left + inline float height() { - insertVertex(glm::vec3(min.x, min.y, max.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(min.x, max.y, max.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(min.x, max.y, min.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(min.x, min.y, min.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + return height_; } - - // Top + inline float depth() { - insertVertex(glm::vec3(max.x, max.y, min.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(min.x, max.y, min.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(min.x, max.y, max.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(max.x, max.y, max.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + return depth_; } - // Bottom + public: + float area() override { - insertVertex(glm::vec3(max.x, min.y, max.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(min.x, min.y, max.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(min.x, min.y, min.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(max.x, min.y, min.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + return 2.0f * (width_ * height_ + width_ * depth_ + height_ * depth_); } - - // Indices - // clang-format off + float volume() override + { + return width_ * height_ * depth_; + } + void build() override + { + glm::vec3 max = glm::vec3(width_ / 2.0f, height_ / 2.0f, depth_ / 2.0f); + glm::vec3 min = -max; + size_t faces = 6; + auto vertexCount = 4 * faces; + + // Front + { + insertVertex(glm::vec3(min.x, min.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(max.x, min.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(max.x, max.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(min.x, max.y, max.z), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 1.0f)); + } + + // Back + { + insertVertex(glm::vec3(min.x, max.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(max.x, max.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(max.x, min.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(min.x, min.y, min.z), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec2(0.0f, 1.0f)); + } + + // Right + { + insertVertex(glm::vec3(max.x, min.y, min.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(max.x, max.y, min.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(max.x, max.y, max.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(max.x, min.y, max.z), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + } + + // Left + { + insertVertex(glm::vec3(min.x, min.y, max.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(min.x, max.y, max.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(min.x, max.y, min.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(min.x, min.y, min.z), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + } + + // Top + { + insertVertex(glm::vec3(max.x, max.y, min.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(min.x, max.y, min.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(min.x, max.y, max.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(max.x, max.y, max.z), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + } + + // Bottom + { + insertVertex(glm::vec3(max.x, min.y, max.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(min.x, min.y, max.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(min.x, min.y, min.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(max.x, min.y, min.z), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec2(0.0f, 1.0f)); + } + + // Indices + // clang-format off Indices indices = { 0, 1, 2, 2, 3, 0, // front 4, 5, 6, 6, 7, 4, // back @@ -110,46 +112,47 @@ namespace builtin_scene::meshes 16, 17, 18, 18, 19, 16, // top 20, 21, 22, 22, 23, 20, // bottom }; - // clang-format on + // clang-format on - updateIndices(indices); - enableAttribute(Vertex::ATTRIBUTE_POSITION); - // enableAttribute(Vertex::ATTRIBUTE_NORMAL); - enableAttribute(Vertex::ATTRIBUTE_UV0); - } + updateIndices(indices); + enableAttribute(Vertex::ATTRIBUTE_POSITION); + // enableAttribute(Vertex::ATTRIBUTE_NORMAL); + enableAttribute(Vertex::ATTRIBUTE_UV0); + } - private: - float width_; - float height_; - float depth_; - }; + private: + float width_; + float height_; + float depth_; + }; - class Cube : public Box - { - public: - Cube(float size) - : Box(size) - , halfSize(size / 2.0f) + class Cube : public Box { - } + public: + Cube(float size) + : Box(size) + , halfSize(size / 2.0f) + { + } - public: - inline float size() - { - return halfSize * 2.0f; - } + public: + inline float size() + { + return halfSize * 2.0f; + } - public: - float area() override - { - return 6.0f * halfSize * halfSize; - } - float volume() override - { - return std::pow(halfSize, 3); - } + public: + float area() override + { + return 6.0f * halfSize * halfSize; + } + float volume() override + { + return std::pow(halfSize, 3); + } - private: - float halfSize; - }; -} + private: + float halfSize; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes/builder.hpp b/src/client/builtin_scene/meshes/builder.hpp index e0dd9a5cd..401991da1 100644 --- a/src/client/builtin_scene/meshes/builder.hpp +++ b/src/client/builtin_scene/meshes/builder.hpp @@ -2,18 +2,21 @@ #include -namespace builtin_scene::meshes +namespace endor { - class MeshBuilder + namespace builtin_scene::meshes { - public: - MeshBuilder() = default; - virtual ~MeshBuilder() = default; + class MeshBuilder + { + public: + MeshBuilder() = default; + virtual ~MeshBuilder() = default; - public: - /** + public: + /** * The implemetor should build the mesh's vertices and indices in this method. */ - virtual void build() = 0; - }; -} + virtual void build() = 0; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes/loaders/ksplat_loader.cpp b/src/client/builtin_scene/meshes/loaders/ksplat_loader.cpp index 06f3060a1..cd63d0fdf 100644 --- a/src/client/builtin_scene/meshes/loaders/ksplat_loader.cpp +++ b/src/client/builtin_scene/meshes/loaders/ksplat_loader.cpp @@ -4,488 +4,491 @@ #include "./ksplat_loader.hpp" -namespace builtin_scene::model_loaders +namespace endor { - using namespace std; - - static const char *LOG_TAG = "KsplatLoader"; - - // Compression level definitions - const KsplatLoader::KsplatCompression KsplatLoader::KSPLAT_COMPRESSION[3] = { - // Level 0: Uncompressed - { - .bytesPerCenter = 12, - .bytesPerScale = 12, - .bytesPerRotation = 16, - .bytesPerColor = 4, - .bytesPerSphericalHarmonicsComponent = 4, - .scaleOffsetBytes = 12, - .rotationOffsetBytes = 24, - .colorOffsetBytes = 40, - .sphericalHarmonicsOffsetBytes = 44, - .scaleRange = 1, - }, - // Level 1: Half-float compression - { - .bytesPerCenter = 6, - .bytesPerScale = 6, - .bytesPerRotation = 8, - .bytesPerColor = 4, - .bytesPerSphericalHarmonicsComponent = 2, - .scaleOffsetBytes = 6, - .rotationOffsetBytes = 12, - .colorOffsetBytes = 20, - .sphericalHarmonicsOffsetBytes = 24, - .scaleRange = 32767, - }, - // Level 2: 8-bit compression - { - .bytesPerCenter = 6, - .bytesPerScale = 6, - .bytesPerRotation = 8, - .bytesPerColor = 4, - .bytesPerSphericalHarmonicsComponent = 1, - .scaleOffsetBytes = 6, - .rotationOffsetBytes = 12, - .colorOffsetBytes = 20, - .sphericalHarmonicsOffsetBytes = 24, - .scaleRange = 32767, - }}; - - // SH degree to components mapping - const unordered_map KsplatLoader::KSPLAT_SH_DEGREE_TO_COMPONENTS = { - {0, 0}, - {1, 9}, - {2, 24}, - {3, 45}}; - - // Little-endian data reading functions (matching JavaScript DataView with littleEndian=true) - float KsplatLoader::readFloat32LE(const uint8_t *data, size_t offset) + namespace builtin_scene::model_loaders { - uint32_t value = readUint32LE(data, offset); - return *reinterpret_cast(&value); - } + using namespace std; - uint32_t KsplatLoader::readUint32LE(const uint8_t *data, size_t offset) - { - return static_cast(data[offset]) | - (static_cast(data[offset + 1]) << 8) | - (static_cast(data[offset + 2]) << 16) | - (static_cast(data[offset + 3]) << 24); - } + static const char *LOG_TAG = "KsplatLoader"; - uint16_t KsplatLoader::readUint16LE(const uint8_t *data, size_t offset) - { - return static_cast(data[offset]) | - (static_cast(data[offset + 1]) << 8); - } - - uint8_t KsplatLoader::readUint8(const uint8_t *data, size_t offset) - { - return data[offset]; - } - - bool KsplatLoader::decodeKsplat( - const vector &fileBytes, - function initNumSplats, - SplatCallback splatCallback) - { - try - { - const size_t HEADER_BYTES = 4096; - const size_t SECTION_BYTES = 1024; - - if (fileBytes.size() < HEADER_BYTES) + // Compression level definitions + const KsplatLoader::KsplatCompression KsplatLoader::KSPLAT_COMPRESSION[3] = { + // Level 0: Uncompressed { - cerr << "File too small for KSplat header. Expected " << HEADER_BYTES << " bytes, got " << fileBytes.size() - << endl; - return false; - } - - // Parse header - const uint8_t *data = reinterpret_cast(fileBytes.data()); - size_t headerOffset = 0; - - const uint8_t versionMajor = readUint8(data, 0); - const uint8_t versionMinor = readUint8(data, 1); - if (versionMajor != 0 || versionMinor < 1) - { - cerr << "Unsupported .ksplat version: " - << static_cast(versionMajor) << "." << static_cast(versionMinor) - << endl; - return false; - } - - const uint32_t maxSectionCount = readUint32LE(data, 4); - // const uint32_t sectionCount = readUint32LE(data, 8); - // const uint32_t maxSplatCount = readUint32LE(data, 12); - const uint32_t splatCount = readUint32LE(data, 16); - const uint16_t compressionLevel = readUint16LE(data, 20); - // const float sceneCenterX = readFloat32LE(data, 24); - // const float sceneCenterY = readFloat32LE(data, 28); - // const float sceneCenterZ = readFloat32LE(data, 32); - float minSphericalHarmonicsCoeff = readFloat32LE(data, 36); - float maxSphericalHarmonicsCoeff = readFloat32LE(data, 40); - - // Set default SH coefficient range if not specified - if (minSphericalHarmonicsCoeff == 0.0f) - minSphericalHarmonicsCoeff = -1.5f; - if (maxSphericalHarmonicsCoeff == 0.0f) - maxSphericalHarmonicsCoeff = 1.5f; - - if (compressionLevel < 0 || compressionLevel > 2) + .bytesPerCenter = 12, + .bytesPerScale = 12, + .bytesPerRotation = 16, + .bytesPerColor = 4, + .bytesPerSphericalHarmonicsComponent = 4, + .scaleOffsetBytes = 12, + .rotationOffsetBytes = 24, + .colorOffsetBytes = 40, + .sphericalHarmonicsOffsetBytes = 44, + .scaleRange = 1, + }, + // Level 1: Half-float compression { - cerr << "Invalid .ksplat compression level: " << compressionLevel << endl; - return false; - } - - if (splatCount == 0) + .bytesPerCenter = 6, + .bytesPerScale = 6, + .bytesPerRotation = 8, + .bytesPerColor = 4, + .bytesPerSphericalHarmonicsComponent = 2, + .scaleOffsetBytes = 6, + .rotationOffsetBytes = 12, + .colorOffsetBytes = 20, + .sphericalHarmonicsOffsetBytes = 24, + .scaleRange = 32767, + }, + // Level 2: 8-bit compression { - cerr << "No splats found in KSplat file" << endl; - return false; - } + .bytesPerCenter = 6, + .bytesPerScale = 6, + .bytesPerRotation = 8, + .bytesPerColor = 4, + .bytesPerSphericalHarmonicsComponent = 1, + .scaleOffsetBytes = 6, + .rotationOffsetBytes = 12, + .colorOffsetBytes = 20, + .sphericalHarmonicsOffsetBytes = 24, + .scaleRange = 32767, + }}; + + // SH degree to components mapping + const unordered_map KsplatLoader::KSPLAT_SH_DEGREE_TO_COMPONENTS = { + {0, 0}, + {1, 9}, + {2, 24}, + {3, 45}}; + + // Little-endian data reading functions (matching JavaScript DataView with littleEndian=true) + float KsplatLoader::readFloat32LE(const uint8_t *data, size_t offset) + { + uint32_t value = readUint32LE(data, offset); + return *reinterpret_cast(&value); + } - // Initialize with total splat count - initNumSplats(splatCount); + uint32_t KsplatLoader::readUint32LE(const uint8_t *data, size_t offset) + { + return static_cast(data[offset]) | + (static_cast(data[offset + 1]) << 8) | + (static_cast(data[offset + 2]) << 16) | + (static_cast(data[offset + 3]) << 24); + } - headerOffset = HEADER_BYTES; - size_t sectionBase = HEADER_BYTES + maxSectionCount * SECTION_BYTES; + uint16_t KsplatLoader::readUint16LE(const uint8_t *data, size_t offset) + { + return static_cast(data[offset]) | + (static_cast(data[offset + 1]) << 8); + } - cout << "KSplat file: version=" << static_cast(versionMajor) << "." << static_cast(versionMinor) - << ", sections=" << maxSectionCount - << ", splats=" << splatCount - << ", compression=" << compressionLevel - << endl; + uint8_t KsplatLoader::readUint8(const uint8_t *data, size_t offset) + { + return data[offset]; + } - // Process each section - for (uint32_t sectionIndex = 0; sectionIndex < maxSectionCount; ++sectionIndex) + bool KsplatLoader::decodeKsplat( + const vector &fileBytes, + function initNumSplats, + SplatCallback splatCallback) + { + try { - if (headerOffset + SECTION_BYTES > fileBytes.size()) + const size_t HEADER_BYTES = 4096; + const size_t SECTION_BYTES = 1024; + + if (fileBytes.size() < HEADER_BYTES) { - cerr << "File too small for section header" << endl; + cerr << "File too small for KSplat header. Expected " << HEADER_BYTES << " bytes, got " << fileBytes.size() + << endl; return false; } - const uint8_t *sectionData = &data[headerOffset]; - headerOffset += SECTION_BYTES; - - // Read section header using little-endian functions - const uint32_t sectionSplatCount = readUint32LE(sectionData, 0); - const uint32_t sectionMaxSplatCount = readUint32LE(sectionData, 4); - const uint32_t bucketSize = readUint32LE(sectionData, 8); - const uint32_t bucketCount = readUint32LE(sectionData, 12); - const float bucketBlockSize = readFloat32LE(sectionData, 16); - const uint16_t bucketStorageSizeBytes = readUint16LE(sectionData, 20); - uint32_t compressionScaleRange = readUint32LE(sectionData, 24); - const uint32_t fullBucketCount = readUint32LE(sectionData, 32); - const uint32_t partiallyFilledBucketCount = readUint32LE(sectionData, 36); - const uint16_t sphericalHarmonicsDegree = readUint16LE(sectionData, 40); - - // Use default scale range if not specified - if (compressionScaleRange == 0) - { - compressionScaleRange = KSPLAT_COMPRESSION[compressionLevel].scaleRange; - } + // Parse header + const uint8_t *data = reinterpret_cast(fileBytes.data()); + size_t headerOffset = 0; - // Get SH components count - uint32_t shComponents = 0; - auto shIt = KSPLAT_SH_DEGREE_TO_COMPONENTS.find(sphericalHarmonicsDegree); - if (shIt != KSPLAT_SH_DEGREE_TO_COMPONENTS.end()) + const uint8_t versionMajor = readUint8(data, 0); + const uint8_t versionMinor = readUint8(data, 1); + if (versionMajor != 0 || versionMinor < 1) { - shComponents = shIt->second; + cerr << "Unsupported .ksplat version: " + << static_cast(versionMajor) << "." << static_cast(versionMinor) + << endl; + return false; } - const auto &compression = KSPLAT_COMPRESSION[compressionLevel]; - - // Calculate bytes per splat INCLUDING SH data (critical for correct offset calculation) - const size_t bytesPerSplat = compression.bytesPerCenter + - compression.bytesPerScale + - compression.bytesPerRotation + - compression.bytesPerColor + - shComponents * compression.bytesPerSphericalHarmonicsComponent; - - const size_t bucketsMetaDataSizeBytes = partiallyFilledBucketCount * 4; - const size_t bucketsStorageSizeBytes = bucketStorageSizeBytes * bucketCount + bucketsMetaDataSizeBytes; - const size_t splatDataStorageSizeBytes = bytesPerSplat * sectionMaxSplatCount; - const size_t storageSizeBytes = splatDataStorageSizeBytes + bucketsStorageSizeBytes; - - if (sectionSplatCount == 0) + const uint32_t maxSectionCount = readUint32LE(data, 4); + // const uint32_t sectionCount = readUint32LE(data, 8); + // const uint32_t maxSplatCount = readUint32LE(data, 12); + const uint32_t splatCount = readUint32LE(data, 16); + const uint16_t compressionLevel = readUint16LE(data, 20); + // const float sceneCenterX = readFloat32LE(data, 24); + // const float sceneCenterY = readFloat32LE(data, 28); + // const float sceneCenterZ = readFloat32LE(data, 32); + float minSphericalHarmonicsCoeff = readFloat32LE(data, 36); + float maxSphericalHarmonicsCoeff = readFloat32LE(data, 40); + + // Set default SH coefficient range if not specified + if (minSphericalHarmonicsCoeff == 0.0f) + minSphericalHarmonicsCoeff = -1.5f; + if (maxSphericalHarmonicsCoeff == 0.0f) + maxSphericalHarmonicsCoeff = 1.5f; + + if (compressionLevel < 0 || compressionLevel > 2) { - sectionBase += storageSizeBytes; - continue; + cerr << "Invalid .ksplat compression level: " << compressionLevel << endl; + return false; } - const float compressionScaleFactor = bucketBlockSize / 2.0f / static_cast(compressionScaleRange); - const size_t bucketsBase = sectionBase + bucketsMetaDataSizeBytes; - const size_t dataBase = sectionBase + bucketsStorageSizeBytes; - - if (dataBase + splatDataStorageSizeBytes > fileBytes.size()) + if (splatCount == 0) { - cerr << "File too small for section data. Expected " << dataBase + splatDataStorageSizeBytes - << ", available " << fileBytes.size() - << endl; + cerr << "No splats found in KSplat file" << endl; return false; } - const uint8_t *splatData = &data[dataBase]; - const uint8_t *bucketData = &data[bucketsBase]; - const uint8_t *partiallyFilledBucketLengthsData = &data[sectionBase]; + // Initialize with total splat count + initNumSplats(splatCount); - const uint32_t fullBucketSplats = fullBucketCount * bucketSize; - uint32_t partialBucketIndex = fullBucketCount; - uint32_t partialBucketBase = fullBucketSplats; + headerOffset = HEADER_BYTES; + size_t sectionBase = HEADER_BYTES + maxSectionCount * SECTION_BYTES; - // Debug first bucket data - if (bucketCount > 0 && sectionIndex == 0) - { - float bucket0X = readFloat32LE(bucketData, 0); - float bucket0Y = readFloat32LE(bucketData, 4); - float bucket0Z = readFloat32LE(bucketData, 8); - } + cout << "KSplat file: version=" << static_cast(versionMajor) << "." << static_cast(versionMinor) + << ", sections=" << maxSectionCount + << ", splats=" << splatCount + << ", compression=" << compressionLevel + << endl; - // Process each splat in this section - for (uint32_t i = 0; i < sectionSplatCount; ++i) + // Process each section + for (uint32_t sectionIndex = 0; sectionIndex < maxSectionCount; ++sectionIndex) { - const size_t splatOffset = i * bytesPerSplat; - - // Determine bucket index - uint32_t bucketIndex; - if (i < fullBucketSplats) + if (headerOffset + SECTION_BYTES > fileBytes.size()) { - bucketIndex = i / bucketSize; + cerr << "File too small for section header" << endl; + return false; } - else + + const uint8_t *sectionData = &data[headerOffset]; + headerOffset += SECTION_BYTES; + + // Read section header using little-endian functions + const uint32_t sectionSplatCount = readUint32LE(sectionData, 0); + const uint32_t sectionMaxSplatCount = readUint32LE(sectionData, 4); + const uint32_t bucketSize = readUint32LE(sectionData, 8); + const uint32_t bucketCount = readUint32LE(sectionData, 12); + const float bucketBlockSize = readFloat32LE(sectionData, 16); + const uint16_t bucketStorageSizeBytes = readUint16LE(sectionData, 20); + uint32_t compressionScaleRange = readUint32LE(sectionData, 24); + const uint32_t fullBucketCount = readUint32LE(sectionData, 32); + const uint32_t partiallyFilledBucketCount = readUint32LE(sectionData, 36); + const uint16_t sphericalHarmonicsDegree = readUint16LE(sectionData, 40); + + // Use default scale range if not specified + if (compressionScaleRange == 0) { - const uint32_t bucketLength = readUint32LE(partiallyFilledBucketLengthsData, (partialBucketIndex - fullBucketCount) * 4); - if (i >= partialBucketBase + bucketLength) - { - partialBucketIndex += 1; - partialBucketBase += bucketLength; - } - bucketIndex = partialBucketIndex; + compressionScaleRange = KSPLAT_COMPRESSION[compressionLevel].scaleRange; } - // Read position - float x, y, z; - if (compressionLevel == 0) + // Get SH components count + uint32_t shComponents = 0; + auto shIt = KSPLAT_SH_DEGREE_TO_COMPONENTS.find(sphericalHarmonicsDegree); + if (shIt != KSPLAT_SH_DEGREE_TO_COMPONENTS.end()) { - x = readFloat32LE(splatData, splatOffset + 0); - y = readFloat32LE(splatData, splatOffset + 4); - z = readFloat32LE(splatData, splatOffset + 8); + shComponents = shIt->second; } - else - { - const uint16_t compressedX = readUint16LE(splatData, splatOffset + 0); - const uint16_t compressedY = readUint16LE(splatData, splatOffset + 2); - const uint16_t compressedZ = readUint16LE(splatData, splatOffset + 4); - const float bucketCenterX = readFloat32LE(bucketData, bucketIndex * 3 * 4 + 0); - const float bucketCenterY = readFloat32LE(bucketData, bucketIndex * 3 * 4 + 4); - const float bucketCenterZ = readFloat32LE(bucketData, bucketIndex * 3 * 4 + 8); + const auto &compression = KSPLAT_COMPRESSION[compressionLevel]; - x = (static_cast(compressedX) - static_cast(compressionScaleRange)) * compressionScaleFactor + bucketCenterX; - y = (static_cast(compressedY) - static_cast(compressionScaleRange)) * compressionScaleFactor + bucketCenterY; - z = (static_cast(compressedZ) - static_cast(compressionScaleRange)) * compressionScaleFactor + bucketCenterZ; - } + // Calculate bytes per splat INCLUDING SH data (critical for correct offset calculation) + const size_t bytesPerSplat = compression.bytesPerCenter + + compression.bytesPerScale + + compression.bytesPerRotation + + compression.bytesPerColor + + shComponents * compression.bytesPerSphericalHarmonicsComponent; + + const size_t bucketsMetaDataSizeBytes = partiallyFilledBucketCount * 4; + const size_t bucketsStorageSizeBytes = bucketStorageSizeBytes * bucketCount + bucketsMetaDataSizeBytes; + const size_t splatDataStorageSizeBytes = bytesPerSplat * sectionMaxSplatCount; + const size_t storageSizeBytes = splatDataStorageSizeBytes + bucketsStorageSizeBytes; - // Read scale - float scaleX, scaleY, scaleZ; - if (compressionLevel == 0) + if (sectionSplatCount == 0) { - scaleX = readFloat32LE(splatData, splatOffset + compression.scaleOffsetBytes + 0); - scaleY = readFloat32LE(splatData, splatOffset + compression.scaleOffsetBytes + 4); - scaleZ = readFloat32LE(splatData, splatOffset + compression.scaleOffsetBytes + 8); + sectionBase += storageSizeBytes; + continue; } - else - { - const uint16_t compressedScaleX = readUint16LE(splatData, splatOffset + compression.scaleOffsetBytes + 0); - const uint16_t compressedScaleY = readUint16LE(splatData, splatOffset + compression.scaleOffsetBytes + 2); - const uint16_t compressedScaleZ = readUint16LE(splatData, splatOffset + compression.scaleOffsetBytes + 4); - scaleX = fromHalf(compressedScaleX); - scaleY = fromHalf(compressedScaleY); - scaleZ = fromHalf(compressedScaleZ); - } + const float compressionScaleFactor = bucketBlockSize / 2.0f / static_cast(compressionScaleRange); + const size_t bucketsBase = sectionBase + bucketsMetaDataSizeBytes; + const size_t dataBase = sectionBase + bucketsStorageSizeBytes; - // Read rotation (quaternion) - float quatW, quatX, quatY, quatZ; - if (compressionLevel == 0) + if (dataBase + splatDataStorageSizeBytes > fileBytes.size()) { - quatW = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 0); - quatX = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 4); - quatY = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 8); - quatZ = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 12); - } - else - { - const uint16_t compressedQuatW = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 0); - const uint16_t compressedQuatX = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 2); - const uint16_t compressedQuatY = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 4); - const uint16_t compressedQuatZ = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 6); - - quatW = fromHalf(compressedQuatW); - quatX = fromHalf(compressedQuatX); - quatY = fromHalf(compressedQuatY); - quatZ = fromHalf(compressedQuatZ); + cerr << "File too small for section data. Expected " << dataBase + splatDataStorageSizeBytes + << ", available " << fileBytes.size() + << endl; + return false; } - // Normalize quaternion (critical fix!) - float quatLength = sqrt(quatW * quatW + quatX * quatX + quatY * quatY + quatZ * quatZ); - if (quatLength > 0.0f) + const uint8_t *splatData = &data[dataBase]; + const uint8_t *bucketData = &data[bucketsBase]; + const uint8_t *partiallyFilledBucketLengthsData = &data[sectionBase]; + + const uint32_t fullBucketSplats = fullBucketCount * bucketSize; + uint32_t partialBucketIndex = fullBucketCount; + uint32_t partialBucketBase = fullBucketSplats; + + // Debug first bucket data + if (bucketCount > 0 && sectionIndex == 0) { - quatW /= quatLength; - quatX /= quatLength; - quatY /= quatLength; - quatZ /= quatLength; + float bucket0X = readFloat32LE(bucketData, 0); + float bucket0Y = readFloat32LE(bucketData, 4); + float bucket0Z = readFloat32LE(bucketData, 8); } - else + + // Process each splat in this section + for (uint32_t i = 0; i < sectionSplatCount; ++i) { - // Default unit quaternion - quatW = 1.0f; - quatX = quatY = quatZ = 0.0f; + const size_t splatOffset = i * bytesPerSplat; + + // Determine bucket index + uint32_t bucketIndex; + if (i < fullBucketSplats) + { + bucketIndex = i / bucketSize; + } + else + { + const uint32_t bucketLength = readUint32LE(partiallyFilledBucketLengthsData, (partialBucketIndex - fullBucketCount) * 4); + if (i >= partialBucketBase + bucketLength) + { + partialBucketIndex += 1; + partialBucketBase += bucketLength; + } + bucketIndex = partialBucketIndex; + } + + // Read position + float x, y, z; + if (compressionLevel == 0) + { + x = readFloat32LE(splatData, splatOffset + 0); + y = readFloat32LE(splatData, splatOffset + 4); + z = readFloat32LE(splatData, splatOffset + 8); + } + else + { + const uint16_t compressedX = readUint16LE(splatData, splatOffset + 0); + const uint16_t compressedY = readUint16LE(splatData, splatOffset + 2); + const uint16_t compressedZ = readUint16LE(splatData, splatOffset + 4); + + const float bucketCenterX = readFloat32LE(bucketData, bucketIndex * 3 * 4 + 0); + const float bucketCenterY = readFloat32LE(bucketData, bucketIndex * 3 * 4 + 4); + const float bucketCenterZ = readFloat32LE(bucketData, bucketIndex * 3 * 4 + 8); + + x = (static_cast(compressedX) - static_cast(compressionScaleRange)) * compressionScaleFactor + bucketCenterX; + y = (static_cast(compressedY) - static_cast(compressionScaleRange)) * compressionScaleFactor + bucketCenterY; + z = (static_cast(compressedZ) - static_cast(compressionScaleRange)) * compressionScaleFactor + bucketCenterZ; + } + + // Read scale + float scaleX, scaleY, scaleZ; + if (compressionLevel == 0) + { + scaleX = readFloat32LE(splatData, splatOffset + compression.scaleOffsetBytes + 0); + scaleY = readFloat32LE(splatData, splatOffset + compression.scaleOffsetBytes + 4); + scaleZ = readFloat32LE(splatData, splatOffset + compression.scaleOffsetBytes + 8); + } + else + { + const uint16_t compressedScaleX = readUint16LE(splatData, splatOffset + compression.scaleOffsetBytes + 0); + const uint16_t compressedScaleY = readUint16LE(splatData, splatOffset + compression.scaleOffsetBytes + 2); + const uint16_t compressedScaleZ = readUint16LE(splatData, splatOffset + compression.scaleOffsetBytes + 4); + + scaleX = fromHalf(compressedScaleX); + scaleY = fromHalf(compressedScaleY); + scaleZ = fromHalf(compressedScaleZ); + } + + // Read rotation (quaternion) + float quatW, quatX, quatY, quatZ; + if (compressionLevel == 0) + { + quatW = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 0); + quatX = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 4); + quatY = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 8); + quatZ = readFloat32LE(splatData, splatOffset + compression.rotationOffsetBytes + 12); + } + else + { + const uint16_t compressedQuatW = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 0); + const uint16_t compressedQuatX = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 2); + const uint16_t compressedQuatY = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 4); + const uint16_t compressedQuatZ = readUint16LE(splatData, splatOffset + compression.rotationOffsetBytes + 6); + + quatW = fromHalf(compressedQuatW); + quatX = fromHalf(compressedQuatX); + quatY = fromHalf(compressedQuatY); + quatZ = fromHalf(compressedQuatZ); + } + + // Normalize quaternion (critical fix!) + float quatLength = sqrt(quatW * quatW + quatX * quatX + quatY * quatY + quatZ * quatZ); + if (quatLength > 0.0f) + { + quatW /= quatLength; + quatX /= quatLength; + quatY /= quatLength; + quatZ /= quatLength; + } + else + { + // Default unit quaternion + quatW = 1.0f; + quatX = quatY = quatZ = 0.0f; + } + + // Read color (always 4 uint8 values: R, G, B, A) + const uint8_t r = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 0); + const uint8_t g = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 1); + const uint8_t b = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 2); + const uint8_t opacity = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 3); + + // Call the splat callback with normalized quaternion + // Convert the space from COLMAP to OPENGL + splatCallback( + i, + x, + -y, + -z, + scaleX, + scaleY, + scaleZ, + quatX, + -quatY, + -quatZ, + quatW, + opacity / 255.0f, // opacity + r / 255.0f, // red + g / 255.0f, // green + b / 255.0f // blue + ); } - // Read color (always 4 uint8 values: R, G, B, A) - const uint8_t r = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 0); - const uint8_t g = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 1); - const uint8_t b = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 2); - const uint8_t opacity = readUint8(splatData, splatOffset + compression.colorOffsetBytes + 3); - - // Call the splat callback with normalized quaternion - // Convert the space from COLMAP to OPENGL - splatCallback( - i, - x, - -y, - -z, - scaleX, - scaleY, - scaleZ, - quatX, - -quatY, - -quatZ, - quatW, - opacity / 255.0f, // opacity - r / 255.0f, // red - g / 255.0f, // green - b / 255.0f // blue - ); + sectionBase += storageSizeBytes; } - sectionBase += storageSizeBytes; + return true; + } + catch (const exception &e) + { + cerr << "Error decoding KSplat file: " << e.what() << endl; + return false; } - - return true; } - catch (const exception &e) + + bool KsplatLoader::load(const vector &data, vector &splats) { - cerr << "Error decoding KSplat file: " << e.what() << endl; - return false; + splats.clear(); + + return decodeKsplat( + data, + [&splats](int numSplats) + { + splats.reserve(numSplats); + }, + [&splats](int index, + float x, + float y, + float z, + float scaleX, + float scaleY, + float scaleZ, + float quatX, + float quatY, + float quatZ, + float quatW, + float opacity, + float r, + float g, + float b) + { + builtin_scene::GaussianSplat splat; + splat.position[0] = x; + splat.position[1] = y; + splat.position[2] = z; + splat.scale[0] = scaleX; + splat.scale[1] = scaleY; + splat.scale[2] = scaleZ; + splat.rotation[0] = quatX; + splat.rotation[1] = quatY; + splat.rotation[2] = quatZ; + splat.rotation[3] = quatW; + splat.color[0] = r; + splat.color[1] = g; + splat.color[2] = b; + splat.opacity = opacity; + splats.push_back(splat); + }); } - } - bool KsplatLoader::load(const vector &data, vector &splats) - { - splats.clear(); + float KsplatLoader::fromHalf(uint16_t value) + { + // IEEE 754 half-float to single-float conversion + uint32_t sign = (value & 0x8000) << 16; + uint32_t exponent = (value & 0x7C00) >> 10; + uint32_t mantissa = value & 0x03FF; - return decodeKsplat( - data, - [&splats](int numSplats) - { - splats.reserve(numSplats); - }, - [&splats](int index, - float x, - float y, - float z, - float scaleX, - float scaleY, - float scaleZ, - float quatX, - float quatY, - float quatZ, - float quatW, - float opacity, - float r, - float g, - float b) + if (exponent == 0) { - builtin_scene::GaussianSplat splat; - splat.position[0] = x; - splat.position[1] = y; - splat.position[2] = z; - splat.scale[0] = scaleX; - splat.scale[1] = scaleY; - splat.scale[2] = scaleZ; - splat.rotation[0] = quatX; - splat.rotation[1] = quatY; - splat.rotation[2] = quatZ; - splat.rotation[3] = quatW; - splat.color[0] = r; - splat.color[1] = g; - splat.color[2] = b; - splat.opacity = opacity; - splats.push_back(splat); - }); - } - - float KsplatLoader::fromHalf(uint16_t value) - { - // IEEE 754 half-float to single-float conversion - uint32_t sign = (value & 0x8000) << 16; - uint32_t exponent = (value & 0x7C00) >> 10; - uint32_t mantissa = value & 0x03FF; - - if (exponent == 0) - { - if (mantissa == 0) + if (mantissa == 0) + { + // Zero + uint32_t result = sign; + return *reinterpret_cast(&result); + } + else + { + // Denormalized number + exponent = 127 - 15; + while ((mantissa & 0x400) == 0) + { + mantissa <<= 1; + exponent--; + } + mantissa &= 0x3FF; + } + } + else if (exponent == 31) { - // Zero - uint32_t result = sign; - return *reinterpret_cast(&result); + // Infinity or NaN + exponent = 255; } else { - // Denormalized number - exponent = 127 - 15; - while ((mantissa & 0x400) == 0) - { - mantissa <<= 1; - exponent--; - } - mantissa &= 0x3FF; + // Normalized number + exponent += 127 - 15; } - } - else if (exponent == 31) - { - // Infinity or NaN - exponent = 255; - } - else - { - // Normalized number - exponent += 127 - 15; - } - uint32_t result = sign | (exponent << 23) | (mantissa << 13); - return *reinterpret_cast(&result); - } + uint32_t result = sign | (exponent << 23) | (mantissa << 13); + return *reinterpret_cast(&result); + } - template - bool KsplatLoader::readBinary(const vector &data, size_t offset, T &value) - { - if (offset + sizeof(T) > data.size()) + template + bool KsplatLoader::readBinary(const vector &data, size_t offset, T &value) { - return false; + if (offset + sizeof(T) > data.size()) + { + return false; + } + + memcpy(&value, &data[offset], sizeof(T)); + return true; } - memcpy(&value, &data[offset], sizeof(T)); - return true; + // Explicit template instantiations for the types we use + template bool KsplatLoader::readBinary(const vector &, size_t, float &); + template bool KsplatLoader::readBinary(const vector &, size_t, uint8_t &); + template bool KsplatLoader::readBinary(const vector &, size_t, uint16_t &); + template bool KsplatLoader::readBinary(const vector &, size_t, uint32_t &); } - - // Explicit template instantiations for the types we use - template bool KsplatLoader::readBinary(const vector &, size_t, float &); - template bool KsplatLoader::readBinary(const vector &, size_t, uint8_t &); - template bool KsplatLoader::readBinary(const vector &, size_t, uint16_t &); - template bool KsplatLoader::readBinary(const vector &, size_t, uint32_t &); -} +} // namespace endor diff --git a/src/client/builtin_scene/meshes/loaders/ksplat_loader.hpp b/src/client/builtin_scene/meshes/loaders/ksplat_loader.hpp index 1200415b4..8e8297c41 100644 --- a/src/client/builtin_scene/meshes/loaders/ksplat_loader.hpp +++ b/src/client/builtin_scene/meshes/loaders/ksplat_loader.hpp @@ -7,99 +7,102 @@ #include #include -namespace builtin_scene::model_loaders +namespace endor { - /** + namespace builtin_scene::model_loaders + { + /** * Ksplat file loader for 3D Gaussian Splatting models. * Follows the Spark implementation pattern with correct endianness handling: * https://github.com/sparkjsdev/spark/blob/main/src/ksplat.ts */ - class KsplatLoader - { - public: - /** + class KsplatLoader + { + public: + /** * Callback function type for processing each splat during loading. * Parameters: index, x, y, z, scaleX, scaleY, scaleZ, quatX, quatY, quatZ, quatW, opacity, r, g, b * * Note: the returned values are in the OpenGL coordinate system (Y-up). */ - using SplatCallback = std::function; + using SplatCallback = std::function; - /** + /** * Decode .ksplat file from data buffer using callback-based approach. * @param fileBytes Raw .ksplat file data * @param initNumSplats Callback called with total number of splats * @param splatCallback Callback called for each splat * @return true if loading was successful, false otherwise */ - static bool decodeKsplat( - const std::vector &fileBytes, - std::function initNumSplats, - SplatCallback splatCallback); + static bool decodeKsplat( + const std::vector &fileBytes, + std::function initNumSplats, + SplatCallback splatCallback); - /** + /** * Convenience method to load splats into a vector (for backward compatibility). * @param data Raw .ksplat file data * @param splats Output vector to store loaded splats * @return true if loading was successful, false otherwise */ - static bool load(const std::vector &data, std::vector &splats); + static bool load(const std::vector &data, std::vector &splats); - private: - // Compression level definitions - struct KsplatCompression - { - int bytesPerCenter; - int bytesPerScale; - int bytesPerRotation; - int bytesPerColor; - int bytesPerSphericalHarmonicsComponent; - int scaleOffsetBytes; - int rotationOffsetBytes; - int colorOffsetBytes; - int sphericalHarmonicsOffsetBytes; - int scaleRange; - }; + private: + // Compression level definitions + struct KsplatCompression + { + int bytesPerCenter; + int bytesPerScale; + int bytesPerRotation; + int bytesPerColor; + int bytesPerSphericalHarmonicsComponent; + int scaleOffsetBytes; + int rotationOffsetBytes; + int colorOffsetBytes; + int sphericalHarmonicsOffsetBytes; + int scaleRange; + }; - static const KsplatCompression KSPLAT_COMPRESSION[3]; - static const std::unordered_map KSPLAT_SH_DEGREE_TO_COMPONENTS; + static const KsplatCompression KSPLAT_COMPRESSION[3]; + static const std::unordered_map KSPLAT_SH_DEGREE_TO_COMPONENTS; - // Little-endian data reading functions (matching JavaScript DataView with littleEndian=true) - static float readFloat32LE(const uint8_t *data, size_t offset); - static uint32_t readUint32LE(const uint8_t *data, size_t offset); - static uint16_t readUint16LE(const uint8_t *data, size_t offset); - static uint8_t readUint8(const uint8_t *data, size_t offset); + // Little-endian data reading functions (matching JavaScript DataView with littleEndian=true) + static float readFloat32LE(const uint8_t *data, size_t offset); + static uint32_t readUint32LE(const uint8_t *data, size_t offset); + static uint16_t readUint16LE(const uint8_t *data, size_t offset); + static uint8_t readUint8(const uint8_t *data, size_t offset); - /** + /** * Convert from half-float to float (for compression level 1). * @param value Half-float value as uint16_t * @return Full float value */ - static float fromHalf(uint16_t value); + static float fromHalf(uint16_t value); - /** + /** * Read binary data at the given offset with proper endianness handling. * @param data Raw file data * @param offset Offset in bytes * @param value Output value * @return true if read was successful, false if out of bounds */ - template - static bool readBinary(const std::vector &data, size_t offset, T &value); - }; -} + template + static bool readBinary(const std::vector &data, size_t offset, T &value); + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes/loaders/ply_loader.cpp b/src/client/builtin_scene/meshes/loaders/ply_loader.cpp index a1c9aef52..1268b7d61 100644 --- a/src/client/builtin_scene/meshes/loaders/ply_loader.cpp +++ b/src/client/builtin_scene/meshes/loaders/ply_loader.cpp @@ -7,504 +7,507 @@ #include "./ply_loader.hpp" -namespace builtin_scene::model_loaders +namespace endor { - using namespace std; + namespace builtin_scene::model_loaders + { + using namespace std; - static constexpr float SH_C0 = 0.28209479177387814f; + static constexpr float SH_C0 = 0.28209479177387814f; - bool PlyLoader::DecodePly( - const vector &fileBytes, - function initNumSplats, - SplatCallback splatCallback) - { - try + bool PlyLoader::DecodePly( + const vector &fileBytes, + function initNumSplats, + SplatCallback splatCallback) { - size_t headerEnd; - unordered_map elements; - bool littleEndian; - - // Parse header - if (!ParseHeader(fileBytes, headerEnd, elements, littleEndian)) + try { - logging::LogError("PlyLoader: Failed to parse PLY header"); - return false; - } + size_t headerEnd; + unordered_map elements; + bool littleEndian; - // Check if we have vertex element (required for splats) - auto vertexIt = elements.find("vertex"); - if (vertexIt == elements.end()) - { - logging::LogError("PlyLoader: No vertex element found in PLY file"); - return false; - } + // Parse header + if (!ParseHeader(fileBytes, headerEnd, elements, littleEndian)) + { + logging::LogError("PlyLoader: Failed to parse PLY header"); + return false; + } - const PlyElement &vertexElement = vertexIt->second; + // Check if we have vertex element (required for splats) + auto vertexIt = elements.find("vertex"); + if (vertexIt == elements.end()) + { + logging::LogError("PlyLoader: No vertex element found in PLY file"); + return false; + } - // Initialize with number of splats - initNumSplats(vertexElement.count); + const PlyElement &vertexElement = vertexIt->second; - // Parse vertex data - const char *binaryData = fileBytes.data() + headerEnd; - ParseElementData(binaryData, - vertexElement, - littleEndian, - [&splatCallback](int index, const unordered_map &properties) - { ExtractSplatData(index, properties, splatCallback); }); + // Initialize with number of splats + initNumSplats(vertexElement.count); - return true; - } - catch (const exception &e) - { - logging::LogError("PlyLoader: Error decoding PLY file - " + string(e.what())); - return false; - } - } + // Parse vertex data + const char *binaryData = fileBytes.data() + headerEnd; + ParseElementData(binaryData, + vertexElement, + littleEndian, + [&splatCallback](int index, const unordered_map &properties) + { ExtractSplatData(index, properties, splatCallback); }); - bool PlyLoader::Load(const vector &data, vector &splats) - { - auto initSplats = [&splats](int numSplats) - { - splats.clear(); - splats.reserve(numSplats); - }; - auto addSplat = [&splats](int index, - float x, - float y, - float z, - float scaleX, - float scaleY, - float scaleZ, - float quatX, - float quatY, - float quatZ, - float quatW, - float opacity, - float r, - float g, - float b) - { - builtin_scene::GaussianSplat splat; - splat.position[0] = x; - splat.position[1] = -y; - splat.position[2] = -z; - splat.scale[0] = scaleX; - splat.scale[1] = scaleY; - splat.scale[2] = scaleZ; - splat.rotation[0] = quatX; - splat.rotation[1] = -quatY; - splat.rotation[2] = -quatZ; - splat.rotation[3] = quatW; - splat.color[0] = r; - splat.color[1] = g; - splat.color[2] = b; - splat.opacity = opacity; - - splats.push_back(splat); - }; - return DecodePly(data, initSplats, addSplat); - } + return true; + } + catch (const exception &e) + { + logging::LogError("PlyLoader: Error decoding PLY file - " + string(e.what())); + return false; + } + } - bool PlyLoader::ParseHeader( - const vector &fileBytes, - size_t &headerEnd, - unordered_map &elements, - bool &littleEndian) - { - // Find header terminator - const string headerTerminator = "end_header\n"; - string header(fileBytes.begin(), fileBytes.end()); - size_t endPos = header.find(headerTerminator); - if (endPos == string::npos) + bool PlyLoader::Load(const vector &data, vector &splats) { - logging::LogError("PlyLoader: Header terminator not found"); - return false; + auto initSplats = [&splats](int numSplats) + { + splats.clear(); + splats.reserve(numSplats); + }; + auto addSplat = [&splats](int index, + float x, + float y, + float z, + float scaleX, + float scaleY, + float scaleZ, + float quatX, + float quatY, + float quatZ, + float quatW, + float opacity, + float r, + float g, + float b) + { + builtin_scene::GaussianSplat splat; + splat.position[0] = x; + splat.position[1] = -y; + splat.position[2] = -z; + splat.scale[0] = scaleX; + splat.scale[1] = scaleY; + splat.scale[2] = scaleZ; + splat.rotation[0] = quatX; + splat.rotation[1] = -quatY; + splat.rotation[2] = -quatZ; + splat.rotation[3] = quatW; + splat.color[0] = r; + splat.color[1] = g; + splat.color[2] = b; + splat.opacity = opacity; + + splats.push_back(splat); + }; + return DecodePly(data, initSplats, addSplat); } - headerEnd = endPos + headerTerminator.length(); - header = header.substr(0, headerEnd); - - // Parse header lines - istringstream headerStream(header); - string line; - bool firstLine = true; - PlyElement *currentElement = nullptr; - littleEndian = true; // Default - - while (getline(headerStream, line)) + bool PlyLoader::ParseHeader( + const vector &fileBytes, + size_t &headerEnd, + unordered_map &elements, + bool &littleEndian) { - // Remove carriage return if present - if (!line.empty() && line.back() == '\r') + // Find header terminator + const string headerTerminator = "end_header\n"; + string header(fileBytes.begin(), fileBytes.end()); + size_t endPos = header.find(headerTerminator); + if (endPos == string::npos) { - line.pop_back(); + logging::LogError("PlyLoader: Header terminator not found"); + return false; } - // Skip empty lines - if (line.empty()) - continue; + headerEnd = endPos + headerTerminator.length(); + header = header.substr(0, headerEnd); - istringstream lineStream(line); - string keyword; - lineStream >> keyword; + // Parse header lines + istringstream headerStream(header); + string line; + bool firstLine = true; + PlyElement *currentElement = nullptr; + littleEndian = true; // Default - if (firstLine) + while (getline(headerStream, line)) { - if (keyword != "ply") + // Remove carriage return if present + if (!line.empty() && line.back() == '\r') { - logging::LogError("PlyLoader: Invalid PLY header - does not start with 'ply'"); - return false; + line.pop_back(); } - firstLine = false; - continue; - } - if (keyword == "format") - { - string format, version; - lineStream >> format >> version; + // Skip empty lines + if (line.empty()) + continue; - if (format == "binary_little_endian") - { - littleEndian = true; - } - else if (format == "binary_big_endian") - { - littleEndian = false; - } - else + istringstream lineStream(line); + string keyword; + lineStream >> keyword; + + if (firstLine) { - logging::LogError("PlyLoader: Unsupported PLY format - " + format); - return false; + if (keyword != "ply") + { + logging::LogError("PlyLoader: Invalid PLY header - does not start with 'ply'"); + return false; + } + firstLine = false; + continue; } - if (version != "1.0") + if (keyword == "format") { - logging::LogError("PlyLoader: Unsupported PLY version - " + version); - return false; - } - } - else if (keyword == "element") - { - string name; - int count; - lineStream >> name >> count; + string format, version; + lineStream >> format >> version; - PlyElement element; - element.name = name; - element.count = count; + if (format == "binary_little_endian") + { + littleEndian = true; + } + else if (format == "binary_big_endian") + { + littleEndian = false; + } + else + { + logging::LogError("PlyLoader: Unsupported PLY format - " + format); + return false; + } - elements[name] = element; - currentElement = &elements[name]; - } - else if (keyword == "property") - { - if (!currentElement) + if (version != "1.0") + { + logging::LogError("PlyLoader: Unsupported PLY version - " + version); + return false; + } + } + else if (keyword == "element") { - logging::LogError("PlyLoader: Property found without element"); - return false; + string name; + int count; + lineStream >> name >> count; + + PlyElement element; + element.name = name; + element.count = count; + + elements[name] = element; + currentElement = &elements[name]; } + else if (keyword == "property") + { + if (!currentElement) + { + logging::LogError("PlyLoader: Property found without element"); + return false; + } - string typeOrList; - lineStream >> typeOrList; + string typeOrList; + lineStream >> typeOrList; - PlyProperty property; + PlyProperty property; - if (typeOrList == "list") + if (typeOrList == "list") + { + property.isList = true; + string countTypeStr, typeStr, name; + lineStream >> countTypeStr >> typeStr >> name; + property.countType = StringToPropertyType(countTypeStr); + property.type = StringToPropertyType(typeStr); + currentElement->properties.push_back(make_pair(name, property)); + } + else + { + property.isList = false; + property.type = StringToPropertyType(typeOrList); + string name; + lineStream >> name; + currentElement->properties.push_back(make_pair(name, property)); + } + } + else if (keyword == "comment") { - property.isList = true; - string countTypeStr, typeStr, name; - lineStream >> countTypeStr >> typeStr >> name; - property.countType = StringToPropertyType(countTypeStr); - property.type = StringToPropertyType(typeStr); - currentElement->properties.push_back(make_pair(name, property)); + // Skip comments + continue; } - else + else if (keyword == "end_header") { - property.isList = false; - property.type = StringToPropertyType(typeOrList); - string name; - lineStream >> name; - currentElement->properties.push_back(make_pair(name, property)); + break; } } - else if (keyword == "comment") - { - // Skip comments - continue; - } - else if (keyword == "end_header") - { - break; - } - } - return true; - } - - size_t PlyLoader::ParseElementData( - const char *data, - const PlyElement &element, - bool littleEndian, - function &item)> callback) - { - size_t offset = 0; + return true; + } - for (int i = 0; i < element.count; ++i) + size_t PlyLoader::ParseElementData( + const char *data, + const PlyElement &element, + bool littleEndian, + function &item)> callback) { - unordered_map item; + size_t offset = 0; - for (const auto &prop : element.properties) + for (int i = 0; i < element.count; ++i) { - const string &propName = prop.first; - const PlyProperty &property = prop.second; + unordered_map item; - if (property.isList) + for (const auto &prop : element.properties) { - // Read list count - float count = ParsePropertyValue(data, offset, property.countType, littleEndian); - offset += GetPropertyTypeSize(property.countType); + const string &propName = prop.first; + const PlyProperty &property = prop.second; - // For now, we'll just read the first value of lists or skip them - // In a full implementation, you might want to handle lists properly - if (count > 0) + if (property.isList) + { + // Read list count + float count = ParsePropertyValue(data, offset, property.countType, littleEndian); + offset += GetPropertyTypeSize(property.countType); + + // For now, we'll just read the first value of lists or skip them + // In a full implementation, you might want to handle lists properly + if (count > 0) + { + item[propName] = ParsePropertyValue(data, offset, property.type, littleEndian); + } + + // Skip remaining list items + offset += static_cast(count) * GetPropertyTypeSize(property.type); + } + else { item[propName] = ParsePropertyValue(data, offset, property.type, littleEndian); + offset += GetPropertyTypeSize(property.type); } - - // Skip remaining list items - offset += static_cast(count) * GetPropertyTypeSize(property.type); - } - else - { - item[propName] = ParsePropertyValue(data, offset, property.type, littleEndian); - offset += GetPropertyTypeSize(property.type); } + + callback(i, item); } - callback(i, item); + return offset; } - return offset; - } - - size_t PlyLoader::GetPropertyTypeSize(PropertyType type) - { - switch (type) + size_t PlyLoader::GetPropertyTypeSize(PropertyType type) { - case PropertyType::CHAR: - case PropertyType::UCHAR: - return 1; - case PropertyType::SHORT: - case PropertyType::USHORT: - return 2; - case PropertyType::INT: - case PropertyType::UINT: - case PropertyType::FLOAT: - return 4; - case PropertyType::DOUBLE: - return 8; - default: - return 0; + switch (type) + { + case PropertyType::CHAR: + case PropertyType::UCHAR: + return 1; + case PropertyType::SHORT: + case PropertyType::USHORT: + return 2; + case PropertyType::INT: + case PropertyType::UINT: + case PropertyType::FLOAT: + return 4; + case PropertyType::DOUBLE: + return 8; + default: + return 0; + } } - } - float PlyLoader::ParsePropertyValue(const char *data, size_t offset, PropertyType type, bool littleEndian) - { - switch (type) - { - case PropertyType::CHAR: - { - int8_t value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - case PropertyType::UCHAR: + float PlyLoader::ParsePropertyValue(const char *data, size_t offset, PropertyType type, bool littleEndian) { - uint8_t value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - case PropertyType::SHORT: - { - int16_t value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - case PropertyType::USHORT: - { - uint16_t value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - case PropertyType::INT: - { - int32_t value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - case PropertyType::UINT: - { - uint32_t value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - case PropertyType::FLOAT: - { - float value; - ReadBinary(data, offset, value, littleEndian); - return value; - } - case PropertyType::DOUBLE: - { - double value; - ReadBinary(data, offset, value, littleEndian); - return static_cast(value); - } - default: - return 0.0f; + switch (type) + { + case PropertyType::CHAR: + { + int8_t value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + case PropertyType::UCHAR: + { + uint8_t value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + case PropertyType::SHORT: + { + int16_t value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + case PropertyType::USHORT: + { + uint16_t value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + case PropertyType::INT: + { + int32_t value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + case PropertyType::UINT: + { + uint32_t value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + case PropertyType::FLOAT: + { + float value; + ReadBinary(data, offset, value, littleEndian); + return value; + } + case PropertyType::DOUBLE: + { + double value; + ReadBinary(data, offset, value, littleEndian); + return static_cast(value); + } + default: + return 0.0f; + } } - } - - PlyLoader::PropertyType PlyLoader::StringToPropertyType(const string &typeStr) - { - if (typeStr == "char") - return PropertyType::CHAR; - if (typeStr == "uchar") - return PropertyType::UCHAR; - if (typeStr == "short") - return PropertyType::SHORT; - if (typeStr == "ushort") - return PropertyType::USHORT; - if (typeStr == "int") - return PropertyType::INT; - if (typeStr == "uint") - return PropertyType::UINT; - if (typeStr == "float") - return PropertyType::FLOAT; - if (typeStr == "double") - return PropertyType::DOUBLE; - - logging::LogWarning("PlyLoader: Unknown property type - " + typeStr); - return PropertyType::FLOAT; // Default fallback - } - void PlyLoader::ExtractSplatData( - int index, - const unordered_map &properties, - SplatCallback splatCallback) - { - // Extract position (required) - float x = 0.0f, y = 0.0f, z = 0.0f; - if (properties.find("x") != properties.end()) - x = properties.at("x"); - if (properties.find("y") != properties.end()) - y = properties.at("y"); - if (properties.find("z") != properties.end()) - z = properties.at("z"); - - bool hasScales = properties.find("scale_0") != properties.end() && - properties.find("scale_1") != properties.end() && - properties.find("scale_2") != properties.end(); - bool hasRotations = properties.find("rot_0") != properties.end() && - properties.find("rot_1") != properties.end() && - properties.find("rot_2") != properties.end() && - properties.find("rot_3") != properties.end(); - - // Extract scale (with defaults) - static constexpr float DEFAULT_SCALE = 0.001f; - float scaleX = hasScales ? exp(properties.at("scale_0")) : DEFAULT_SCALE; - float scaleY = hasScales ? exp(properties.at("scale_1")) : DEFAULT_SCALE; - float scaleZ = hasScales ? exp(properties.at("scale_2")) : DEFAULT_SCALE; - - // Extract rotation quaternion (with defaults) - float quatW = hasRotations ? properties.at("rot_0") : 1.0f; // W component - float quatX = hasRotations ? properties.at("rot_1") : 0.0f; // X component - float quatY = hasRotations ? properties.at("rot_2") : 0.0f; // Y component - float quatZ = hasRotations ? properties.at("rot_3") : 0.0f; // Z component - - // Extract opacity (with default) - float opacity = 1.0f; - if (properties.find("opacity") != properties.end()) + PlyLoader::PropertyType PlyLoader::StringToPropertyType(const string &typeStr) { - opacity = 1.0f / (1.0f + exp(-properties.at("opacity"))); // Sigmoid activation + if (typeStr == "char") + return PropertyType::CHAR; + if (typeStr == "uchar") + return PropertyType::UCHAR; + if (typeStr == "short") + return PropertyType::SHORT; + if (typeStr == "ushort") + return PropertyType::USHORT; + if (typeStr == "int") + return PropertyType::INT; + if (typeStr == "uint") + return PropertyType::UINT; + if (typeStr == "float") + return PropertyType::FLOAT; + if (typeStr == "double") + return PropertyType::DOUBLE; + + logging::LogWarning("PlyLoader: Unknown property type - " + typeStr); + return PropertyType::FLOAT; // Default fallback } - // Extract color (with defaults) - float r = 0.5f, g = 0.5f, b = 0.5f; - if (properties.find("f_dc_0") != properties.end()) - r = 0.5f + SH_C0 * properties.at("f_dc_0"); - if (properties.find("f_dc_1") != properties.end()) - g = 0.5f + SH_C0 * properties.at("f_dc_1"); - if (properties.find("f_dc_2") != properties.end()) - b = 0.5f + SH_C0 * properties.at("f_dc_2"); - - // Clamp colors to valid range - r = max(0.0f, min(1.0f, r)); - g = max(0.0f, min(1.0f, g)); - b = max(0.0f, min(1.0f, b)); - - splatCallback(index, x, y, z, scaleX, scaleY, scaleZ, quatX, quatY, quatZ, quatW, opacity, r, g, b); - } - - template - bool PlyLoader::ReadBinary(const char *data, size_t offset, T &value, bool littleEndian) - { - static_assert(sizeof(T) <= 8, "Unsupported type size"); + void PlyLoader::ExtractSplatData( + int index, + const unordered_map &properties, + SplatCallback splatCallback) + { + // Extract position (required) + float x = 0.0f, y = 0.0f, z = 0.0f; + if (properties.find("x") != properties.end()) + x = properties.at("x"); + if (properties.find("y") != properties.end()) + y = properties.at("y"); + if (properties.find("z") != properties.end()) + z = properties.at("z"); + + bool hasScales = properties.find("scale_0") != properties.end() && + properties.find("scale_1") != properties.end() && + properties.find("scale_2") != properties.end(); + bool hasRotations = properties.find("rot_0") != properties.end() && + properties.find("rot_1") != properties.end() && + properties.find("rot_2") != properties.end() && + properties.find("rot_3") != properties.end(); + + // Extract scale (with defaults) + static constexpr float DEFAULT_SCALE = 0.001f; + float scaleX = hasScales ? exp(properties.at("scale_0")) : DEFAULT_SCALE; + float scaleY = hasScales ? exp(properties.at("scale_1")) : DEFAULT_SCALE; + float scaleZ = hasScales ? exp(properties.at("scale_2")) : DEFAULT_SCALE; + + // Extract rotation quaternion (with defaults) + float quatW = hasRotations ? properties.at("rot_0") : 1.0f; // W component + float quatX = hasRotations ? properties.at("rot_1") : 0.0f; // X component + float quatY = hasRotations ? properties.at("rot_2") : 0.0f; // Y component + float quatZ = hasRotations ? properties.at("rot_3") : 0.0f; // Z component + + // Extract opacity (with default) + float opacity = 1.0f; + if (properties.find("opacity") != properties.end()) + { + opacity = 1.0f / (1.0f + exp(-properties.at("opacity"))); // Sigmoid activation + } - // Copy bytes from data - memcpy(&value, data + offset, sizeof(T)); + // Extract color (with defaults) + float r = 0.5f, g = 0.5f, b = 0.5f; + if (properties.find("f_dc_0") != properties.end()) + r = 0.5f + SH_C0 * properties.at("f_dc_0"); + if (properties.find("f_dc_1") != properties.end()) + g = 0.5f + SH_C0 * properties.at("f_dc_1"); + if (properties.find("f_dc_2") != properties.end()) + b = 0.5f + SH_C0 * properties.at("f_dc_2"); + + // Clamp colors to valid range + r = max(0.0f, min(1.0f, r)); + g = max(0.0f, min(1.0f, g)); + b = max(0.0f, min(1.0f, b)); + + splatCallback(index, x, y, z, scaleX, scaleY, scaleZ, quatX, quatY, quatZ, quatW, opacity, r, g, b); + } - // Handle endianness if needed for multi-byte types - if (sizeof(T) > 1) + template + bool PlyLoader::ReadBinary(const char *data, size_t offset, T &value, bool littleEndian) { - // Detect system endianness more reliably - static const bool systemLittleEndian = []() - { - const uint32_t test = 0x01234567; - return *reinterpret_cast(&test) == 0x67; - }(); + static_assert(sizeof(T) <= 8, "Unsupported type size"); - if (systemLittleEndian != littleEndian) + // Copy bytes from data + memcpy(&value, data + offset, sizeof(T)); + + // Handle endianness if needed for multi-byte types + if (sizeof(T) > 1) { - // Swap bytes using more efficient approach - if (sizeof(T) == 2) - { - uint16_t *ptr = reinterpret_cast(&value); - *ptr = (*ptr << 8) | (*ptr >> 8); - } - else if (sizeof(T) == 4) + // Detect system endianness more reliably + static const bool systemLittleEndian = []() { - uint32_t *ptr = reinterpret_cast(&value); - *ptr = ((*ptr << 24) & 0xFF000000) | - ((*ptr << 8) & 0x00FF0000) | - ((*ptr >> 8) & 0x0000FF00) | - ((*ptr >> 24) & 0x000000FF); - } - else if (sizeof(T) == 8) + const uint32_t test = 0x01234567; + return *reinterpret_cast(&test) == 0x67; + }(); + + if (systemLittleEndian != littleEndian) { - uint64_t *ptr = reinterpret_cast(&value); - *ptr = ((*ptr << 56) & 0xFF00000000000000ULL) | - ((*ptr << 40) & 0x00FF000000000000ULL) | - ((*ptr << 24) & 0x0000FF0000000000ULL) | - ((*ptr << 8) & 0x000000FF00000000ULL) | - ((*ptr >> 8) & 0x00000000FF000000ULL) | - ((*ptr >> 24) & 0x0000000000FF0000ULL) | - ((*ptr >> 40) & 0x000000000000FF00ULL) | - ((*ptr >> 56) & 0x00000000000000FFULL); + // Swap bytes using more efficient approach + if (sizeof(T) == 2) + { + uint16_t *ptr = reinterpret_cast(&value); + *ptr = (*ptr << 8) | (*ptr >> 8); + } + else if (sizeof(T) == 4) + { + uint32_t *ptr = reinterpret_cast(&value); + *ptr = ((*ptr << 24) & 0xFF000000) | + ((*ptr << 8) & 0x00FF0000) | + ((*ptr >> 8) & 0x0000FF00) | + ((*ptr >> 24) & 0x000000FF); + } + else if (sizeof(T) == 8) + { + uint64_t *ptr = reinterpret_cast(&value); + *ptr = ((*ptr << 56) & 0xFF00000000000000ULL) | + ((*ptr << 40) & 0x00FF000000000000ULL) | + ((*ptr << 24) & 0x0000FF0000000000ULL) | + ((*ptr << 8) & 0x000000FF00000000ULL) | + ((*ptr >> 8) & 0x00000000FF000000ULL) | + ((*ptr >> 24) & 0x0000000000FF0000ULL) | + ((*ptr >> 40) & 0x000000000000FF00ULL) | + ((*ptr >> 56) & 0x00000000000000FFULL); + } } } + + return true; } - return true; + // Explicit template instantiations for the types we use + template bool PlyLoader::ReadBinary(const char *, size_t, int8_t &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, uint8_t &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, int16_t &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, uint16_t &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, int32_t &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, uint32_t &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, float &, bool); + template bool PlyLoader::ReadBinary(const char *, size_t, double &, bool); } - - // Explicit template instantiations for the types we use - template bool PlyLoader::ReadBinary(const char *, size_t, int8_t &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, uint8_t &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, int16_t &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, uint16_t &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, int32_t &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, uint32_t &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, float &, bool); - template bool PlyLoader::ReadBinary(const char *, size_t, double &, bool); -} +} // namespace endor diff --git a/src/client/builtin_scene/meshes/loaders/ply_loader.hpp b/src/client/builtin_scene/meshes/loaders/ply_loader.hpp index a954ce4df..34520a8a2 100644 --- a/src/client/builtin_scene/meshes/loaders/ply_loader.hpp +++ b/src/client/builtin_scene/meshes/loaders/ply_loader.hpp @@ -7,9 +7,11 @@ #include #include -namespace builtin_scene::model_loaders +namespace endor { - /** + namespace builtin_scene::model_loaders + { + /** * PLY file loader for 3D Gaussian Splatting models. * Follows the Spark implementation pattern: * https://github.com/sparkjsdev/spark/blob/main/src/ply.ts @@ -17,87 +19,87 @@ namespace builtin_scene::model_loaders * PLY is a polygon file format that can store 3D model data including * vertex positions, colors, and properties for Gaussian splatting. */ - class PlyLoader - { - public: - /** + class PlyLoader + { + public: + /** * Callback function type for processing each splat during loading. * Parameters: index, x, y, z, scaleX, scaleY, scaleZ, quatX, quatY, quatZ, quatW, opacity, r, g, b */ - using SplatCallback = std::function; + using SplatCallback = std::function; - /** + /** * PLY property types supported by the loader */ - enum class PropertyType - { - CHAR, - UCHAR, - SHORT, - USHORT, - INT, - UINT, - FLOAT, - DOUBLE - }; + enum class PropertyType + { + CHAR, + UCHAR, + SHORT, + USHORT, + INT, + UINT, + FLOAT, + DOUBLE + }; - /** + /** * PLY property definition */ - struct PlyProperty - { - bool isList; - PropertyType type; - PropertyType countType; // For list properties - }; + struct PlyProperty + { + bool isList; + PropertyType type; + PropertyType countType; // For list properties + }; - /** + /** * PLY element definition */ - struct PlyElement - { - std::string name; - int count; - std::vector> properties; - }; + struct PlyElement + { + std::string name; + int count; + std::vector> properties; + }; - /** + /** * Decode .ply file from data buffer using callback-based approach. * @param fileBytes Raw .ply file data * @param initNumSplats Callback called with total number of splats * @param splatCallback Callback called for each splat * @return true if loading was successful, false otherwise */ - static bool DecodePly( - const std::vector &fileBytes, - std::function initNumSplats, - SplatCallback splatCallback); + static bool DecodePly( + const std::vector &fileBytes, + std::function initNumSplats, + SplatCallback splatCallback); - /** + /** * Convenience method to load splats into a vector (for backward compatibility). * @param data Raw .ply file data * @param splats Output vector to store loaded splats * @return true if loading was successful, false otherwise */ - static bool Load(const std::vector &data, std::vector &splats); + static bool Load(const std::vector &data, std::vector &splats); - private: - /** + private: + /** * Parse the PLY text header to extract element and property definitions. * @param fileBytes Raw file data * @param headerEnd Output: position where header ends and binary data begins @@ -105,13 +107,13 @@ namespace builtin_scene::model_loaders * @param littleEndian Output: whether binary data is little-endian * @return true if header was parsed successfully */ - static bool ParseHeader( - const std::vector &fileBytes, - size_t &headerEnd, - std::unordered_map &elements, - bool &littleEndian); + static bool ParseHeader( + const std::vector &fileBytes, + size_t &headerEnd, + std::unordered_map &elements, + bool &littleEndian); - /** + /** * Parse binary data for a specific element using its property definitions. * @param data Binary data starting at the element's data * @param element Element definition with properties @@ -119,20 +121,20 @@ namespace builtin_scene::model_loaders * @param callback Function to call for each item parsed * @return number of bytes consumed */ - static size_t ParseElementData( - const char *data, - const PlyElement &element, - bool littleEndian, - std::function &item)> callback); + static size_t ParseElementData( + const char *data, + const PlyElement &element, + bool littleEndian, + std::function &item)> callback); - /** + /** * Get the size in bytes of a property type. * @param type Property type * @return Size in bytes */ - static size_t GetPropertyTypeSize(PropertyType type); + static size_t GetPropertyTypeSize(PropertyType type); - /** + /** * Parse a single property value from binary data. * @param data Binary data * @param offset Offset within data @@ -140,27 +142,27 @@ namespace builtin_scene::model_loaders * @param littleEndian Whether data is little-endian * @return Parsed value as float */ - static float ParsePropertyValue(const char *data, size_t offset, PropertyType type, bool littleEndian); + static float ParsePropertyValue(const char *data, size_t offset, PropertyType type, bool littleEndian); - /** + /** * Convert property type string to PropertyType enum. * @param typeStr Property type string from PLY header * @return PropertyType enum value */ - static PropertyType StringToPropertyType(const std::string &typeStr); + static PropertyType StringToPropertyType(const std::string &typeStr); - /** + /** * Extract splat data from parsed vertex properties. * @param index Vertex index * @param properties Map of property name to value * @param splatCallback Callback to invoke with splat data */ - static void ExtractSplatData( - int index, - const std::unordered_map &properties, - SplatCallback splatCallback); + static void ExtractSplatData( + int index, + const std::unordered_map &properties, + SplatCallback splatCallback); - /** + /** * Read binary data with proper endianness handling. * @param data Raw binary data * @param offset Offset in bytes @@ -168,7 +170,8 @@ namespace builtin_scene::model_loaders * @param littleEndian Whether data is little-endian * @return true if read was successful, false if out of bounds */ - template - static bool ReadBinary(const char *data, size_t offset, T &value, bool littleEndian); - }; -} + template + static bool ReadBinary(const char *data, size_t offset, T &value, bool littleEndian); + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes/loaders/spz_loader.cpp b/src/client/builtin_scene/meshes/loaders/spz_loader.cpp index 102b386c5..7503f6483 100644 --- a/src/client/builtin_scene/meshes/loaders/spz_loader.cpp +++ b/src/client/builtin_scene/meshes/loaders/spz_loader.cpp @@ -6,408 +6,411 @@ #include "./spz_loader.hpp" -namespace builtin_scene::model_loaders +namespace endor { - static const char *LOG_TAG = "SpzLoader"; - - bool SpzLoader::decodeSpz( - const std::vector &fileBytes, - std::function initNumSplats, - SplatCallback splatCallback) + namespace builtin_scene::model_loaders { - try + static const char *LOG_TAG = "SpzLoader"; + + bool SpzLoader::decodeSpz( + const std::vector &fileBytes, + std::function initNumSplats, + SplatCallback splatCallback) { - // First decompress the gzip data - std::vector decompressedData; - if (!decompressGzip(fileBytes, decompressedData)) + try { - DEBUG(LOG_TAG, "Failed to decompress SPZ file"); - return false; - } + // First decompress the gzip data + std::vector decompressedData; + if (!decompressGzip(fileBytes, decompressedData)) + { + DEBUG(LOG_TAG, "Failed to decompress SPZ file"); + return false; + } - if (decompressedData.size() < 16) - { - DEBUG(LOG_TAG, "SPZ file too small for header. Expected at least 16 bytes, got %zu", decompressedData.size()); - return false; - } + if (decompressedData.size() < 16) + { + DEBUG(LOG_TAG, "SPZ file too small for header. Expected at least 16 bytes, got %zu", decompressedData.size()); + return false; + } - const uint8_t *data = reinterpret_cast(decompressedData.data()); - size_t offset = 0; - - // Read and validate header - uint32_t magic; - uint32_t version; - uint32_t numSplats; - uint8_t shDegree; - uint8_t fractionalBits; - uint8_t flags; - uint8_t reserved; - - memcpy(&magic, &data[offset], sizeof(uint32_t)); - offset += sizeof(uint32_t); - if (magic != SPZ_MAGIC) - { - DEBUG(LOG_TAG, "Invalid SPZ file magic: 0x%08x (expected 0x%08x)", magic, SPZ_MAGIC); - return false; - } + const uint8_t *data = reinterpret_cast(decompressedData.data()); + size_t offset = 0; + + // Read and validate header + uint32_t magic; + uint32_t version; + uint32_t numSplats; + uint8_t shDegree; + uint8_t fractionalBits; + uint8_t flags; + uint8_t reserved; + + memcpy(&magic, &data[offset], sizeof(uint32_t)); + offset += sizeof(uint32_t); + if (magic != SPZ_MAGIC) + { + DEBUG(LOG_TAG, "Invalid SPZ file magic: 0x%08x (expected 0x%08x)", magic, SPZ_MAGIC); + return false; + } - memcpy(&version, &data[offset], sizeof(uint32_t)); - offset += sizeof(uint32_t); - if (version < 1 || version > 2) - { - DEBUG(LOG_TAG, "Unsupported SPZ version: %u", version); - return false; - } + memcpy(&version, &data[offset], sizeof(uint32_t)); + offset += sizeof(uint32_t); + if (version < 1 || version > 2) + { + DEBUG(LOG_TAG, "Unsupported SPZ version: %u", version); + return false; + } - memcpy(&numSplats, &data[offset], sizeof(uint32_t)); - offset += sizeof(uint32_t); - memcpy(&shDegree, &data[offset], sizeof(uint8_t)); - offset += sizeof(uint8_t); - memcpy(&fractionalBits, &data[offset], sizeof(uint8_t)); - offset += sizeof(uint8_t); - memcpy(&flags, &data[offset], sizeof(uint8_t)); - offset += sizeof(uint8_t); - memcpy(&reserved, &data[offset], sizeof(uint8_t)); - offset += sizeof(uint8_t); - - if (numSplats == 0) - { - DEBUG(LOG_TAG, "No splats found in SPZ file"); - return false; - } + memcpy(&numSplats, &data[offset], sizeof(uint32_t)); + offset += sizeof(uint32_t); + memcpy(&shDegree, &data[offset], sizeof(uint8_t)); + offset += sizeof(uint8_t); + memcpy(&fractionalBits, &data[offset], sizeof(uint8_t)); + offset += sizeof(uint8_t); + memcpy(&flags, &data[offset], sizeof(uint8_t)); + offset += sizeof(uint8_t); + memcpy(&reserved, &data[offset], sizeof(uint8_t)); + offset += sizeof(uint8_t); + + if (numSplats == 0) + { + DEBUG(LOG_TAG, "No splats found in SPZ file"); + return false; + } - // Initialize with total splat count - initNumSplats(numSplats); + // Initialize with total splat count + initNumSplats(numSplats); - // Read centers - std::vector centers(numSplats * 3); - if (version == 1) - { - // Version 1: float16 centers - const size_t centerBytesNeeded = numSplats * 3 * 2; - if (offset + centerBytesNeeded > decompressedData.size()) + // Read centers + std::vector centers(numSplats * 3); + if (version == 1) + { + // Version 1: float16 centers + const size_t centerBytesNeeded = numSplats * 3 * 2; + if (offset + centerBytesNeeded > decompressedData.size()) + { + DEBUG(LOG_TAG, "SPZ file too small for centers"); + return false; + } + + const uint16_t *centerUint16 = reinterpret_cast(&data[offset]); + for (uint32_t i = 0; i < numSplats; i++) + { + const uint32_t i3 = i * 3; + centers[i3] = fromHalf(centerUint16[i3]); + centers[i3 + 1] = fromHalf(centerUint16[i3 + 1]); + centers[i3 + 2] = fromHalf(centerUint16[i3 + 2]); + } + offset += centerBytesNeeded; + } + else if (version == 2) { - DEBUG(LOG_TAG, "SPZ file too small for centers"); + // Version 2: 24-bit fixed-point centers + const uint32_t fixed = 1 << fractionalBits; + const size_t centerBytesNeeded = numSplats * 3 * 3; + if (offset + centerBytesNeeded > decompressedData.size()) + { + DEBUG(LOG_TAG, "SPZ file too small for centers"); + return false; + } + + for (uint32_t i = 0; i < numSplats; i++) + { + const uint32_t i9 = i * 9; + const uint32_t i3 = i * 3; + + // Read 3 bytes per coordinate, sign-extend to 32-bit + const int32_t x = (static_cast((data[offset + i9 + 2] << 24) | + (data[offset + i9 + 1] << 16) | + (data[offset + i9] << 8)) >> + 8); + const int32_t y = (static_cast((data[offset + i9 + 5] << 24) | + (data[offset + i9 + 4] << 16) | + (data[offset + i9 + 3] << 8)) >> + 8); + const int32_t z = (static_cast((data[offset + i9 + 8] << 24) | + (data[offset + i9 + 7] << 16) | + (data[offset + i9 + 6] << 8)) >> + 8); + + centers[i3] = static_cast(x) / fixed; + centers[i3 + 1] = static_cast(y) / fixed; + centers[i3 + 2] = static_cast(z) / fixed; + } + offset += centerBytesNeeded; + } + + // Read alpha values + if (offset + numSplats > decompressedData.size()) + { + DEBUG(LOG_TAG, "SPZ file too small for alpha values"); return false; } + std::vector alphas(numSplats); + for (uint32_t i = 0; i < numSplats; i++) + { + alphas[i] = data[offset + i] / 255.0f; + } + offset += numSplats; - const uint16_t *centerUint16 = reinterpret_cast(&data[offset]); + // Read RGB values + const size_t rgbBytesNeeded = numSplats * 3; + if (offset + rgbBytesNeeded > decompressedData.size()) + { + DEBUG(LOG_TAG, "SPZ file too small for RGB values"); + return false; + } + std::vector colors(numSplats * 3); + const float scale = SH_C0 / 0.15f; for (uint32_t i = 0; i < numSplats; i++) { const uint32_t i3 = i * 3; - centers[i3] = fromHalf(centerUint16[i3]); - centers[i3 + 1] = fromHalf(centerUint16[i3 + 1]); - centers[i3 + 2] = fromHalf(centerUint16[i3 + 2]); + colors[i3] = (data[offset + i3] / 255.0f - 0.5f) * scale + 0.5f; + colors[i3 + 1] = (data[offset + i3 + 1] / 255.0f - 0.5f) * scale + 0.5f; + colors[i3 + 2] = (data[offset + i3 + 2] / 255.0f - 0.5f) * scale + 0.5f; } - offset += centerBytesNeeded; - } - else if (version == 2) - { - // Version 2: 24-bit fixed-point centers - const uint32_t fixed = 1 << fractionalBits; - const size_t centerBytesNeeded = numSplats * 3 * 3; - if (offset + centerBytesNeeded > decompressedData.size()) + offset += rgbBytesNeeded; + + // Read scales + const size_t scalesBytesNeeded = numSplats * 3; + if (offset + scalesBytesNeeded > decompressedData.size()) { - DEBUG(LOG_TAG, "SPZ file too small for centers"); + DEBUG(LOG_TAG, "SPZ file too small for scale values"); return false; } + std::vector scales(numSplats * 3); + for (uint32_t i = 0; i < numSplats; i++) + { + const uint32_t i3 = i * 3; + scales[i3] = std::exp(data[offset + i3] / 16.0f - 10.0f); + scales[i3 + 1] = std::exp(data[offset + i3 + 1] / 16.0f - 10.0f); + scales[i3 + 2] = std::exp(data[offset + i3 + 2] / 16.0f - 10.0f); + } + offset += scalesBytesNeeded; + // Read quaternions + const size_t quatBytesNeeded = numSplats * 3; + if (offset + quatBytesNeeded > decompressedData.size()) + { + DEBUG(LOG_TAG, "SPZ file too small for quaternion values"); + return false; + } + std::vector quaternions(numSplats * 4); for (uint32_t i = 0; i < numSplats; i++) { - const uint32_t i9 = i * 9; const uint32_t i3 = i * 3; + const uint32_t i4 = i * 4; - // Read 3 bytes per coordinate, sign-extend to 32-bit - const int32_t x = (static_cast((data[offset + i9 + 2] << 24) | - (data[offset + i9 + 1] << 16) | - (data[offset + i9] << 8)) >> - 8); - const int32_t y = (static_cast((data[offset + i9 + 5] << 24) | - (data[offset + i9 + 4] << 16) | - (data[offset + i9 + 3] << 8)) >> - 8); - const int32_t z = (static_cast((data[offset + i9 + 8] << 24) | - (data[offset + i9 + 7] << 16) | - (data[offset + i9 + 6] << 8)) >> - 8); - - centers[i3] = static_cast(x) / fixed; - centers[i3 + 1] = static_cast(y) / fixed; - centers[i3 + 2] = static_cast(z) / fixed; + const float quatX = data[offset + i3] / 127.5f - 1.0f; + const float quatY = data[offset + i3 + 1] / 127.5f - 1.0f; + const float quatZ = data[offset + i3 + 2] / 127.5f - 1.0f; + const float quatW = std::sqrt(std::max(0.0f, 1.0f - quatX * quatX - quatY * quatY - quatZ * quatZ)); + + quaternions[i4] = quatX; + quaternions[i4 + 1] = quatY; + quaternions[i4 + 2] = quatZ; + quaternions[i4 + 3] = quatW; } - offset += centerBytesNeeded; - } + offset += quatBytesNeeded; - // Read alpha values - if (offset + numSplats > decompressedData.size()) - { - DEBUG(LOG_TAG, "SPZ file too small for alpha values"); - return false; - } - std::vector alphas(numSplats); - for (uint32_t i = 0; i < numSplats; i++) - { - alphas[i] = data[offset + i] / 255.0f; - } - offset += numSplats; + // Call the splat callback for each splat + for (uint32_t i = 0; i < numSplats; i++) + { + const uint32_t i3 = i * 3; + const uint32_t i4 = i * 4; + + float x = centers[i3]; + float y = centers[i3 + 1]; + float z = centers[i3 + 2]; + float scaleX = scales[i3]; + float scaleY = scales[i3 + 1]; + float scaleZ = scales[i3 + 2]; + float quatX = quaternions[i4]; + float quatY = quaternions[i4 + 1]; + float quatZ = quaternions[i4 + 2]; + float quatW = quaternions[i4 + 3]; + + // Convert from 3DGS coordinate system (Y-down) to OpenGL coordinate system (Y-up) + splatCallback( + i, + x, + -y, + -z, + scaleX, + scaleY, + scaleZ, + quatX, + -quatY, + -quatZ, + quatW, + alphas[i], + colors[i3], + colors[i3 + 1], + colors[i3 + 2]); + } - // Read RGB values - const size_t rgbBytesNeeded = numSplats * 3; - if (offset + rgbBytesNeeded > decompressedData.size()) - { - DEBUG(LOG_TAG, "SPZ file too small for RGB values"); - return false; - } - std::vector colors(numSplats * 3); - const float scale = SH_C0 / 0.15f; - for (uint32_t i = 0; i < numSplats; i++) - { - const uint32_t i3 = i * 3; - colors[i3] = (data[offset + i3] / 255.0f - 0.5f) * scale + 0.5f; - colors[i3 + 1] = (data[offset + i3 + 1] / 255.0f - 0.5f) * scale + 0.5f; - colors[i3 + 2] = (data[offset + i3 + 2] / 255.0f - 0.5f) * scale + 0.5f; + DEBUG(LOG_TAG, "Successfully decoded SPZ file with %u splats", numSplats); + return true; } - offset += rgbBytesNeeded; - - // Read scales - const size_t scalesBytesNeeded = numSplats * 3; - if (offset + scalesBytesNeeded > decompressedData.size()) + catch (const std::exception &e) { - DEBUG(LOG_TAG, "SPZ file too small for scale values"); + DEBUG(LOG_TAG, "Error decoding SPZ file: %s", e.what()); return false; } - std::vector scales(numSplats * 3); - for (uint32_t i = 0; i < numSplats; i++) - { - const uint32_t i3 = i * 3; - scales[i3] = std::exp(data[offset + i3] / 16.0f - 10.0f); - scales[i3 + 1] = std::exp(data[offset + i3 + 1] / 16.0f - 10.0f); - scales[i3 + 2] = std::exp(data[offset + i3 + 2] / 16.0f - 10.0f); - } - offset += scalesBytesNeeded; + } - // Read quaternions - const size_t quatBytesNeeded = numSplats * 3; - if (offset + quatBytesNeeded > decompressedData.size()) + bool SpzLoader::load(const std::vector &data, std::vector &splats) + { + splats.clear(); + + return decodeSpz( + data, + [&splats](int numSplats) + { + splats.reserve(numSplats); + }, + [&splats](int index, float x, float y, float z, float scaleX, float scaleY, float scaleZ, float quatX, float quatY, float quatZ, float quatW, float opacity, float r, float g, float b) + { + builtin_scene::GaussianSplat splat; + // Note: Coordinate conversion is already applied in the callback from decodeSpz + splat.position[0] = x; + splat.position[1] = y; + splat.position[2] = z; + splat.scale[0] = scaleX; + splat.scale[1] = scaleY; + splat.scale[2] = scaleZ; + splat.rotation[0] = quatX; + splat.rotation[1] = quatY; + splat.rotation[2] = quatZ; + splat.rotation[3] = quatW; + splat.color[0] = r; + splat.color[1] = g; + splat.color[2] = b; + splat.opacity = opacity; + splats.push_back(splat); + }); + } + + bool SpzLoader::decompressGzip(const std::vector &compressedData, std::vector &decompressedData) + { + if (compressedData.empty()) { - DEBUG(LOG_TAG, "SPZ file too small for quaternion values"); + DEBUG(LOG_TAG, "Empty compressed data"); return false; } - std::vector quaternions(numSplats * 4); - for (uint32_t i = 0; i < numSplats; i++) - { - const uint32_t i3 = i * 3; - const uint32_t i4 = i * 4; - - const float quatX = data[offset + i3] / 127.5f - 1.0f; - const float quatY = data[offset + i3 + 1] / 127.5f - 1.0f; - const float quatZ = data[offset + i3 + 2] / 127.5f - 1.0f; - const float quatW = std::sqrt(std::max(0.0f, 1.0f - quatX * quatX - quatY * quatY - quatZ * quatZ)); - - quaternions[i4] = quatX; - quaternions[i4 + 1] = quatY; - quaternions[i4 + 2] = quatZ; - quaternions[i4 + 3] = quatW; - } - offset += quatBytesNeeded; - // Call the splat callback for each splat - for (uint32_t i = 0; i < numSplats; i++) + z_stream stream; + memset(&stream, 0, sizeof(stream)); + + // Initialize for gzip decompression (windowBits = 15 + 16 for gzip) + if (inflateInit2(&stream, 15 + 16) != Z_OK) { - const uint32_t i3 = i * 3; - const uint32_t i4 = i * 4; - - float x = centers[i3]; - float y = centers[i3 + 1]; - float z = centers[i3 + 2]; - float scaleX = scales[i3]; - float scaleY = scales[i3 + 1]; - float scaleZ = scales[i3 + 2]; - float quatX = quaternions[i4]; - float quatY = quaternions[i4 + 1]; - float quatZ = quaternions[i4 + 2]; - float quatW = quaternions[i4 + 3]; - - // Convert from 3DGS coordinate system (Y-down) to OpenGL coordinate system (Y-up) - splatCallback( - i, - x, - -y, - -z, - scaleX, - scaleY, - scaleZ, - quatX, - -quatY, - -quatZ, - quatW, - alphas[i], - colors[i3], - colors[i3 + 1], - colors[i3 + 2]); + DEBUG(LOG_TAG, "Failed to initialize zlib for gzip decompression"); + return false; } - DEBUG(LOG_TAG, "Successfully decoded SPZ file with %u splats", numSplats); - return true; - } - catch (const std::exception &e) - { - DEBUG(LOG_TAG, "Error decoding SPZ file: %s", e.what()); - return false; - } - } + stream.next_in = reinterpret_cast(const_cast(compressedData.data())); + stream.avail_in = compressedData.size(); - bool SpzLoader::load(const std::vector &data, std::vector &splats) - { - splats.clear(); + // Start with a reasonable buffer size and grow as needed + const size_t chunkSize = 16384; + decompressedData.clear(); + decompressedData.reserve(compressedData.size() * 4); // Initial guess - return decodeSpz( - data, - [&splats](int numSplats) + int ret; + do { - splats.reserve(numSplats); - }, - [&splats](int index, float x, float y, float z, float scaleX, float scaleY, float scaleZ, float quatX, float quatY, float quatZ, float quatW, float opacity, float r, float g, float b) - { - builtin_scene::GaussianSplat splat; - // Note: Coordinate conversion is already applied in the callback from decodeSpz - splat.position[0] = x; - splat.position[1] = y; - splat.position[2] = z; - splat.scale[0] = scaleX; - splat.scale[1] = scaleY; - splat.scale[2] = scaleZ; - splat.rotation[0] = quatX; - splat.rotation[1] = quatY; - splat.rotation[2] = quatZ; - splat.rotation[3] = quatW; - splat.color[0] = r; - splat.color[1] = g; - splat.color[2] = b; - splat.opacity = opacity; - splats.push_back(splat); - }); - } + // Expand output buffer + size_t currentSize = decompressedData.size(); + decompressedData.resize(currentSize + chunkSize); - bool SpzLoader::decompressGzip(const std::vector &compressedData, std::vector &decompressedData) - { - if (compressedData.empty()) - { - DEBUG(LOG_TAG, "Empty compressed data"); - return false; - } - - z_stream stream; - memset(&stream, 0, sizeof(stream)); + stream.next_out = reinterpret_cast(decompressedData.data() + currentSize); + stream.avail_out = chunkSize; - // Initialize for gzip decompression (windowBits = 15 + 16 for gzip) - if (inflateInit2(&stream, 15 + 16) != Z_OK) - { - DEBUG(LOG_TAG, "Failed to initialize zlib for gzip decompression"); - return false; - } + ret = inflate(&stream, Z_NO_FLUSH); - stream.next_in = reinterpret_cast(const_cast(compressedData.data())); - stream.avail_in = compressedData.size(); + if (ret != Z_OK && ret != Z_STREAM_END) + { + DEBUG(LOG_TAG, "Gzip decompression failed with error: %d", ret); + inflateEnd(&stream); + return false; + } - // Start with a reasonable buffer size and grow as needed - const size_t chunkSize = 16384; - decompressedData.clear(); - decompressedData.reserve(compressedData.size() * 4); // Initial guess + // Adjust actual size + decompressedData.resize(currentSize + chunkSize - stream.avail_out); - int ret; - do - { - // Expand output buffer - size_t currentSize = decompressedData.size(); - decompressedData.resize(currentSize + chunkSize); + } while (ret != Z_STREAM_END && stream.avail_in > 0); - stream.next_out = reinterpret_cast(decompressedData.data() + currentSize); - stream.avail_out = chunkSize; + inflateEnd(&stream); - ret = inflate(&stream, Z_NO_FLUSH); - - if (ret != Z_OK && ret != Z_STREAM_END) + if (ret != Z_STREAM_END) { - DEBUG(LOG_TAG, "Gzip decompression failed with error: %d", ret); - inflateEnd(&stream); + DEBUG(LOG_TAG, "Incomplete gzip decompression"); return false; } - // Adjust actual size - decompressedData.resize(currentSize + chunkSize - stream.avail_out); - - } while (ret != Z_STREAM_END && stream.avail_in > 0); - - inflateEnd(&stream); - - if (ret != Z_STREAM_END) - { - DEBUG(LOG_TAG, "Incomplete gzip decompression"); - return false; + DEBUG(LOG_TAG, "Successfully decompressed gzip data: %zu -> %zu bytes", compressedData.size(), decompressedData.size()); + return true; } - DEBUG(LOG_TAG, "Successfully decompressed gzip data: %zu -> %zu bytes", compressedData.size(), decompressedData.size()); - return true; - } - - float SpzLoader::fromHalf(uint16_t value) - { - // IEEE 754 half-float to single-float conversion - uint32_t sign = (value & 0x8000) << 16; - uint32_t exponent = (value & 0x7C00) >> 10; - uint32_t mantissa = value & 0x03FF; - - if (exponent == 0) + float SpzLoader::fromHalf(uint16_t value) { - if (mantissa == 0) + // IEEE 754 half-float to single-float conversion + uint32_t sign = (value & 0x8000) << 16; + uint32_t exponent = (value & 0x7C00) >> 10; + uint32_t mantissa = value & 0x03FF; + + if (exponent == 0) + { + if (mantissa == 0) + { + // Zero + uint32_t result = sign; + return *reinterpret_cast(&result); + } + else + { + // Denormalized number + exponent = 127 - 15; + while ((mantissa & 0x400) == 0) + { + mantissa <<= 1; + exponent--; + } + mantissa &= 0x3FF; + } + } + else if (exponent == 31) { - // Zero - uint32_t result = sign; - return *reinterpret_cast(&result); + // Infinity or NaN + exponent = 255; } else { - // Denormalized number - exponent = 127 - 15; - while ((mantissa & 0x400) == 0) - { - mantissa <<= 1; - exponent--; - } - mantissa &= 0x3FF; + // Normalized number + exponent += 127 - 15; } - } - else if (exponent == 31) - { - // Infinity or NaN - exponent = 255; - } - else - { - // Normalized number - exponent += 127 - 15; - } - uint32_t result = sign | (exponent << 23) | (mantissa << 13); - return *reinterpret_cast(&result); - } + uint32_t result = sign | (exponent << 23) | (mantissa << 13); + return *reinterpret_cast(&result); + } - template - bool SpzLoader::readBinary(const std::vector &data, size_t offset, T &value) - { - if (offset + sizeof(T) > data.size()) + template + bool SpzLoader::readBinary(const std::vector &data, size_t offset, T &value) { - return false; + if (offset + sizeof(T) > data.size()) + { + return false; + } + + std::memcpy(&value, &data[offset], sizeof(T)); + return true; } - std::memcpy(&value, &data[offset], sizeof(T)); - return true; + // Explicit template instantiations for the types we use + template bool SpzLoader::readBinary(const std::vector &, size_t, float &); + template bool SpzLoader::readBinary(const std::vector &, size_t, uint8_t &); + template bool SpzLoader::readBinary(const std::vector &, size_t, uint16_t &); + template bool SpzLoader::readBinary(const std::vector &, size_t, uint32_t &); } - - // Explicit template instantiations for the types we use - template bool SpzLoader::readBinary(const std::vector &, size_t, float &); - template bool SpzLoader::readBinary(const std::vector &, size_t, uint8_t &); - template bool SpzLoader::readBinary(const std::vector &, size_t, uint16_t &); - template bool SpzLoader::readBinary(const std::vector &, size_t, uint32_t &); -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/meshes/loaders/spz_loader.hpp b/src/client/builtin_scene/meshes/loaders/spz_loader.hpp index 8383d2750..1ad8c56c1 100644 --- a/src/client/builtin_scene/meshes/loaders/spz_loader.hpp +++ b/src/client/builtin_scene/meshes/loaders/spz_loader.hpp @@ -6,87 +6,90 @@ #include #include -namespace builtin_scene::model_loaders +namespace endor { - /** + namespace builtin_scene::model_loaders + { + /** * SPZ file loader for 3D Gaussian Splatting models. * Follows the Spark implementation pattern: * https://github.com/sparkjsdev/spark/blob/main/src/spz.ts * * SPZ is a compressed format (gzip) with fixed-point encoding optimized for file size. */ - class SpzLoader - { - public: - /** + class SpzLoader + { + public: + /** * Callback function type for processing each splat during loading. * Parameters: index, x, y, z, scaleX, scaleY, scaleZ, quatX, quatY, quatZ, quatW, opacity, r, g, b */ - using SplatCallback = std::function; + using SplatCallback = std::function; - /** + /** * Decode .spz file from data buffer using callback-based approach. * @param fileBytes Raw .spz file data (gzip compressed) * @param initNumSplats Callback called with total number of splats * @param splatCallback Callback called for each splat * @return true if loading was successful, false otherwise */ - static bool decodeSpz( - const std::vector &fileBytes, - std::function initNumSplats, - SplatCallback splatCallback); + static bool decodeSpz( + const std::vector &fileBytes, + std::function initNumSplats, + SplatCallback splatCallback); - /** + /** * Convenience method to load splats into a vector (for backward compatibility). * @param data Raw .spz file data * @param splats Output vector to store loaded splats * @return true if loading was successful, false otherwise */ - static bool load(const std::vector &data, std::vector &splats); + static bool load(const std::vector &data, std::vector &splats); - private: - // SPZ format constants following Spark implementation - static constexpr uint32_t SPZ_MAGIC = 0x5053474e; // NGSP = Niantic gaussian splat - static constexpr float SH_C0 = 0.28209479177387814f; + private: + // SPZ format constants following Spark implementation + static constexpr uint32_t SPZ_MAGIC = 0x5053474e; // NGSP = Niantic gaussian splat + static constexpr float SH_C0 = 0.28209479177387814f; - /** + /** * Simple gzip decompression using zlib. * @param compressedData Gzip compressed data * @param decompressedData Output decompressed data * @return true if decompression was successful */ - static bool decompressGzip(const std::vector &compressedData, std::vector &decompressedData); + static bool decompressGzip(const std::vector &compressedData, std::vector &decompressedData); - /** + /** * Convert from half-float to float (for SPZ version 1). * @param value Half-float value as uint16_t * @return Full float value */ - static float fromHalf(uint16_t value); + static float fromHalf(uint16_t value); - /** + /** * Read binary data at the given offset with proper endianness handling. * @param data Raw file data * @param offset Offset in bytes * @param value Output value * @return true if read was successful, false if out of bounds */ - template - static bool readBinary(const std::vector &data, size_t offset, T &value); - }; -} \ No newline at end of file + template + static bool readBinary(const std::vector &data, size_t offset, T &value); + }; + } +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/meshes/plane.cpp b/src/client/builtin_scene/meshes/plane.cpp index 3596a04a5..a0c8c007b 100644 --- a/src/client/builtin_scene/meshes/plane.cpp +++ b/src/client/builtin_scene/meshes/plane.cpp @@ -1,46 +1,49 @@ #include "./plane.hpp" -namespace builtin_scene::meshes +namespace endor { - void Plane::build() + namespace builtin_scene::meshes { - auto zVertexCount = subDivisions_ + 2; - auto xVertexCount = subDivisions_ + 2; - auto vertexCount = zVertexCount * xVertexCount; - auto indicesCount = (zVertexCount - 1) * (xVertexCount - 1) * 6; + void Plane::build() + { + auto zVertexCount = subDivisions_ + 2; + auto xVertexCount = subDivisions_ + 2; + auto vertexCount = zVertexCount * xVertexCount; + auto indicesCount = (zVertexCount - 1) * (xVertexCount - 1) * 6; - auto rotation = math::Quat::FromRotationArc(math::Dir3::Y(), normal()); - auto size = halfSize() * 2.0f; + auto rotation = math::Quat::FromRotationArc(math::Dir3::Y(), normal()); + auto size = halfSize() * 2.0f; - for (uint32_t z = 0; z < zVertexCount; z++) - { - for (uint32_t x = 0; x < xVertexCount; x++) + for (uint32_t z = 0; z < zVertexCount; z++) { - auto tx = static_cast(x) / static_cast(xVertexCount - 1); - auto tz = static_cast(z) / static_cast(zVertexCount - 1); - glm::vec3 pos = rotation * math::Vec3((-0.5f + tx) * size.x, 0.0f, (-0.5f + tz) * size.y); - insertVertex(pos, normal(), glm::vec2(tx, tz)); + for (uint32_t x = 0; x < xVertexCount; x++) + { + auto tx = static_cast(x) / static_cast(xVertexCount - 1); + auto tz = static_cast(z) / static_cast(zVertexCount - 1); + glm::vec3 pos = rotation * math::Vec3((-0.5f + tx) * size.x, 0.0f, (-0.5f + tz) * size.y); + insertVertex(pos, normal(), glm::vec2(tx, tz)); + } } - } - Indices indices(indicesCount); - for (uint32_t z = 0; z < zVertexCount - 1; z++) - { - for (uint32_t x = 0; x < xVertexCount - 1; x++) + Indices indices(indicesCount); + for (uint32_t z = 0; z < zVertexCount - 1; z++) { - auto quad = z * xVertexCount + x; - indices.push_back(quad + 1); // t1[1] - indices.push_back(quad + xVertexCount + 1); // t1[2] - indices.push_back(quad + xVertexCount); // t1[0] - indices.push_back(quad + xVertexCount); // t2[2] - indices.push_back(quad); // t2[0] - indices.push_back(quad + 1); // t2[1] + for (uint32_t x = 0; x < xVertexCount - 1; x++) + { + auto quad = z * xVertexCount + x; + indices.push_back(quad + 1); // t1[1] + indices.push_back(quad + xVertexCount + 1); // t1[2] + indices.push_back(quad + xVertexCount); // t1[0] + indices.push_back(quad + xVertexCount); // t2[2] + indices.push_back(quad); // t2[0] + indices.push_back(quad + 1); // t2[1] + } } - } - updateIndices(indices); - enableAttribute(Vertex::ATTRIBUTE_POSITION); - // enableAttribute(Vertex::ATTRIBUTE_NORMAL); - enableAttribute(Vertex::ATTRIBUTE_UV0); + updateIndices(indices); + enableAttribute(Vertex::ATTRIBUTE_POSITION); + // enableAttribute(Vertex::ATTRIBUTE_NORMAL); + enableAttribute(Vertex::ATTRIBUTE_UV0); + } } -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/meshes/plane.hpp b/src/client/builtin_scene/meshes/plane.hpp index 038aec3fe..9d05b4573 100644 --- a/src/client/builtin_scene/meshes/plane.hpp +++ b/src/client/builtin_scene/meshes/plane.hpp @@ -5,53 +5,56 @@ #include "../mesh_base.hpp" #include "./builder.hpp" -namespace builtin_scene::meshes +namespace endor { - class Plane : public Mesh, - public MeshBuilder, - public Primitive3d + namespace builtin_scene::meshes { - public: - Plane(math::Dir3 normal, glm::vec2 halfSize) - : Mesh("Plane", PrimitiveTopology::kTriangles) - , normal_(normal) - , halfSize_(halfSize) + class Plane : public Mesh, + public MeshBuilder, + public Primitive3d { - } - // Constructs a plane with the given normal and a default half size of 0.5f. - Plane(math::Dir3 normal, float halfSize = 0.5f) - : Plane(normal, glm::vec2(halfSize, halfSize)) - { - } + public: + Plane(math::Dir3 normal, glm::vec2 halfSize) + : Mesh("Plane", PrimitiveTopology::kTriangles) + , normal_(normal) + , halfSize_(halfSize) + { + } + // Constructs a plane with the given normal and a default half size of 0.5f. + Plane(math::Dir3 normal, float halfSize = 0.5f) + : Plane(normal, glm::vec2(halfSize, halfSize)) + { + } - public: - inline math::Dir3 normal() - { - return normal_; - } - inline glm::vec2 halfSize() - { - return halfSize_; - } - inline void setSubDivisions(uint32_t subDivisions) - { - subDivisions_ = subDivisions; - } + public: + inline math::Dir3 normal() + { + return normal_; + } + inline glm::vec2 halfSize() + { + return halfSize_; + } + inline void setSubDivisions(uint32_t subDivisions) + { + subDivisions_ = subDivisions; + } - public: - float area() override - { - return halfSize_.x * halfSize_.y * 4.0f; - } - float volume() override - { - return 0.0f; - } - void build() override; + public: + float area() override + { + return halfSize_.x * halfSize_.y * 4.0f; + } + float volume() override + { + return 0.0f; + } + void build() override; - private: - math::Dir3 normal_ = math::Dir3::Y(); - glm::vec2 halfSize_ = glm::vec2(0.5f, 0.5f); - uint32_t subDivisions_ = 0; - }; -} + private: + math::Dir3 normal_ = math::Dir3::Y(); + glm::vec2 halfSize_ = glm::vec2(0.5f, 0.5f); + uint32_t subDivisions_ = 0; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/meshes/sphere.hpp b/src/client/builtin_scene/meshes/sphere.hpp index d3eb77a6a..e76d3788f 100644 --- a/src/client/builtin_scene/meshes/sphere.hpp +++ b/src/client/builtin_scene/meshes/sphere.hpp @@ -4,73 +4,75 @@ #include #include "../mesh_base.hpp" -namespace builtin_scene +namespace endor { - namespace meshes + namespace builtin_scene { - template - class Sphere : public Mesh, - public MeshBuilder, - public Primitive3d + namespace meshes { - friend BuilderType; - - public: - Sphere(float radius) - : Mesh("Sphere", PrimitiveTopology::kTriangles) - , radius_(radius) + template + class Sphere : public Mesh, + public MeshBuilder, + public Primitive3d { - } - virtual ~Sphere() = default; + friend BuilderType; - public: - inline float radius() - { - return radius_; - } - inline float diameter() - { - return radius_ * 2.0f; - } + public: + Sphere(float radius) + : Mesh("Sphere", PrimitiveTopology::kTriangles) + , radius_(radius) + { + } + virtual ~Sphere() = default; - public: - float area() override - { - return 4.0f * glm::pi() * std::pow(radius_, 2); - } - float volume() override - { - return (4.0f / 3.0f) * glm::pi() * std::pow(radius_, 3); - } - void build() override = 0; + public: + inline float radius() + { + return radius_; + } + inline float diameter() + { + return radius_ * 2.0f; + } - private: - float radius_; - }; + public: + float area() override + { + return 4.0f * glm::pi() * std::pow(radius_, 2); + } + float volume() override + { + return (4.0f / 3.0f) * glm::pi() * std::pow(radius_, 3); + } + void build() override = 0; - class IcoSphere : public Sphere - { - public: - IcoSphere(float radius, uint32_t subdivisions) - : Sphere(radius) - , subdivisions_(subdivisions) - { - } + private: + float radius_; + }; - public: - void build() override + class IcoSphere : public Sphere { - throw std::runtime_error("IcoSphere: Not implemented"); - } + public: + IcoSphere(float radius, uint32_t subdivisions) + : Sphere(radius) + , subdivisions_(subdivisions) + { + } - private: - uint32_t subdivisions_; - }; + public: + void build() override + { + throw std::runtime_error("IcoSphere: Not implemented"); + } - class UvSphere : public Sphere - { - public: - /** + private: + uint32_t subdivisions_; + }; + + class UvSphere : public Sphere + { + public: + /** * Creates a UV sphere `Mesh` with the given number of longitudinal sectors and latitudinal stacks, aka horizontal and vertical resolution. * * A good default is 32 sectors and 18 stacks. @@ -79,81 +81,82 @@ namespace builtin_scene * @param sectors The number of longitudinal sectors. * @param stacks The number of latitudinal stacks. */ - UvSphere(float radius, uint32_t sectors, uint32_t stacks) - : Sphere(radius) - , sectors_(sectors) - , stacks_(stacks) - { - } - - public: - void build() override - { - float sectors = sectors_; - float stacks = stacks_; - auto invLength = 1.0f / radius(); - auto sectorStep = 2.0f * glm::pi() / sectors; - auto stackStep = glm::pi() / stacks; - - size_t vertexCount = stacks * sectors; - Indices indices(vertexCount * 2 * 3); - - for (uint32_t i = 0; i <= stacks; i++) + UvSphere(float radius, uint32_t sectors, uint32_t stacks) + : Sphere(radius) + , sectors_(sectors) + , stacks_(stacks) { - float stackAngle = glm::pi() / 2.0f - (static_cast(i) * stackStep); - float xy = radius() * std::cos(stackAngle); - float z = radius() * std::sin(stackAngle); - - for (uint32_t j = 0; j <= sectors; j++) - { - float sectorAngle = static_cast(j) * sectorStep; - float x = xy * std::cos(sectorAngle); - float y = xy * std::sin(sectorAngle); - - auto pos = glm::vec3(x, y, z); - auto u = static_cast(j) / sectors; - auto v = static_cast(i) / stacks; - insertVertex(pos, pos * invLength, glm::vec2(u, v)); - } } - // indices - // k1--k1+1 - // | / | - // | / | - // k2--k2+1 - for (uint32_t i = 0; i < stacks; i++) + public: + void build() override { - uint32_t k1 = i * (sectors + 1); - uint32_t k2 = k1 + sectors + 1; + float sectors = sectors_; + float stacks = stacks_; + auto invLength = 1.0f / radius(); + auto sectorStep = 2.0f * glm::pi() / sectors; + auto stackStep = glm::pi() / stacks; + + size_t vertexCount = stacks * sectors; + Indices indices(vertexCount * 2 * 3); - for (uint32_t j = 0; j < sectors; j++, k1++, k2++) + for (uint32_t i = 0; i <= stacks; i++) { - if (i != 0) + float stackAngle = glm::pi() / 2.0f - (static_cast(i) * stackStep); + float xy = radius() * std::cos(stackAngle); + float z = radius() * std::sin(stackAngle); + + for (uint32_t j = 0; j <= sectors; j++) { - indices.push_back(k1); - indices.push_back(k2); - indices.push_back(k1 + 1); + float sectorAngle = static_cast(j) * sectorStep; + float x = xy * std::cos(sectorAngle); + float y = xy * std::sin(sectorAngle); + + auto pos = glm::vec3(x, y, z); + auto u = static_cast(j) / sectors; + auto v = static_cast(i) / stacks; + insertVertex(pos, pos * invLength, glm::vec2(u, v)); } + } - if (i != (stacks - 1)) + // indices + // k1--k1+1 + // | / | + // | / | + // k2--k2+1 + for (uint32_t i = 0; i < stacks; i++) + { + uint32_t k1 = i * (sectors + 1); + uint32_t k2 = k1 + sectors + 1; + + for (uint32_t j = 0; j < sectors; j++, k1++, k2++) { - indices.push_back(k1 + 1); - indices.push_back(k2); - indices.push_back(k2 + 1); + if (i != 0) + { + indices.push_back(k1); + indices.push_back(k2); + indices.push_back(k1 + 1); + } + + if (i != (stacks - 1)) + { + indices.push_back(k1 + 1); + indices.push_back(k2); + indices.push_back(k2 + 1); + } } } - } - updateIndices(indices); - enableAttribute(Vertex::ATTRIBUTE_POSITION); - enableAttribute(Vertex::ATTRIBUTE_NORMAL); - enableAttribute(Vertex::ATTRIBUTE_UV0); - } + updateIndices(indices); + enableAttribute(Vertex::ATTRIBUTE_POSITION); + enableAttribute(Vertex::ATTRIBUTE_NORMAL); + enableAttribute(Vertex::ATTRIBUTE_UV0); + } - private: - uint32_t sectors_; - uint32_t stacks_; - }; + private: + uint32_t sectors_; + uint32_t stacks_; + }; + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/meshes/splat.cpp b/src/client/builtin_scene/meshes/splat.cpp index c0f974f88..d6f3d2b6d 100644 --- a/src/client/builtin_scene/meshes/splat.cpp +++ b/src/client/builtin_scene/meshes/splat.cpp @@ -1,57 +1,60 @@ #include "splat.hpp" -namespace builtin_scene::meshes +namespace endor { - Splat::Splat() - : Mesh("Splat", PrimitiveTopology::kTriangles) + namespace builtin_scene::meshes { - } - - void Splat::build() - { - createQuadGeometry(); - } - - float Splat::area() - { - return 1.0f; // Unit quad area - } - - float Splat::volume() - { - return 0.0f; // 2D quad has no volume - } - - void Splat::createQuadGeometry() - { - // Create a simple quad geometry for splat rendering - // Each splat will be rendered as an instanced quad - - // Quad vertices: position (x, y) and texture coordinates (u, v) - // clang-format off + Splat::Splat() + : Mesh("Splat", PrimitiveTopology::kTriangles) + { + } + + void Splat::build() + { + createQuadGeometry(); + } + + float Splat::area() + { + return 1.0f; // Unit quad area + } + + float Splat::volume() + { + return 0.0f; // 2D quad has no volume + } + + void Splat::createQuadGeometry() + { + // Create a simple quad geometry for splat rendering + // Each splat will be rendered as an instanced quad + + // Quad vertices: position (x, y) and texture coordinates (u, v) + // clang-format off vector vertices = { -0.5f, -0.5f, +0.5f, -0.5f, +0.5f, +0.5f, -0.5f, +0.5f }; - // clang-format on + // clang-format on - insertVertex(glm::vec3(vertices[0], vertices[1], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 0.0f)); - insertVertex(glm::vec3(vertices[2], vertices[3], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 0.0f)); - insertVertex(glm::vec3(vertices[4], vertices[5], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 1.0f)); - insertVertex(glm::vec3(vertices[6], vertices[7], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 1.0f)); + insertVertex(glm::vec3(vertices[0], vertices[1], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 0.0f)); + insertVertex(glm::vec3(vertices[2], vertices[3], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 0.0f)); + insertVertex(glm::vec3(vertices[4], vertices[5], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 1.0f)); + insertVertex(glm::vec3(vertices[6], vertices[7], 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 1.0f)); - // Quad indices for two triangles - // clang-format off + // Quad indices for two triangles + // clang-format off Indices indices = { 0, 1, 2, // First triangle 2, 3, 0 // Second triangle }; - // clang-format on + // clang-format on - updateIndices(indices); - enableAttribute(Vertex::ATTRIBUTE_POSITION); - enableAttribute(Vertex::ATTRIBUTE_UV0); + updateIndices(indices); + enableAttribute(Vertex::ATTRIBUTE_POSITION); + enableAttribute(Vertex::ATTRIBUTE_UV0); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/meshes/splat.hpp b/src/client/builtin_scene/meshes/splat.hpp index c4bc3331d..f0d91d718 100644 --- a/src/client/builtin_scene/meshes/splat.hpp +++ b/src/client/builtin_scene/meshes/splat.hpp @@ -5,26 +5,29 @@ #include "../mesh_base.hpp" #include "./builder.hpp" -namespace builtin_scene::meshes +namespace endor { - /** + namespace builtin_scene::meshes + { + /** * Base mesh class for Gaussian splat rendering. * Provides a simple quad geometry for instanced splat rendering. */ - class Splat : public Mesh, - public MeshBuilder, - public Primitive3d - { - public: - Splat(); - virtual ~Splat() = default; + class Splat : public Mesh, + public MeshBuilder, + public Primitive3d + { + public: + Splat(); + virtual ~Splat() = default; - public: - void build() override; - float area() override; - float volume() override; + public: + void build() override; + float area() override; + float volume() override; - private: - void createQuadGeometry(); - }; -} \ No newline at end of file + private: + void createQuadGeometry(); + }; + } +} // namespace endor \ No newline at end of file diff --git a/src/client/builtin_scene/render_layer.cpp b/src/client/builtin_scene/render_layer.cpp index 1008d8634..6f357bfd9 100644 --- a/src/client/builtin_scene/render_layer.cpp +++ b/src/client/builtin_scene/render_layer.cpp @@ -1,5 +1,8 @@ #include "./render_layer.hpp" -namespace builtin_scene +namespace endor { -} + namespace builtin_scene + { + } +} // namespace endor diff --git a/src/client/builtin_scene/render_layer.hpp b/src/client/builtin_scene/render_layer.hpp index a76e4e7fe..aba558afd 100644 --- a/src/client/builtin_scene/render_layer.hpp +++ b/src/client/builtin_scene/render_layer.hpp @@ -3,57 +3,60 @@ #include #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - class RenderLayer : ecs::Component + namespace builtin_scene { - using ecs::Component::Component; - - public: - RenderLayer(int index = 0) - : ecs::Component() - , index_(index) + class RenderLayer : ecs::Component { - } + using ecs::Component::Component; - int index() const - { - return index_; - } + public: + RenderLayer(int index = 0) + : ecs::Component() + , index_(index) + { + } - operator int() const - { - return index_; - } + int index() const + { + return index_; + } - bool operator==(const RenderLayer &other) const - { - return index_ == other.index_; - } - bool operator!=(const RenderLayer &other) const - { - return index_ != other.index_; - } - bool operator<(const RenderLayer &other) const - { - return index_ < other.index_; - } - bool operator>(const RenderLayer &other) const - { - return index_ > other.index_; - } + operator int() const + { + return index_; + } - private: - int index_; - }; -} + bool operator==(const RenderLayer &other) const + { + return index_ == other.index_; + } + bool operator!=(const RenderLayer &other) const + { + return index_ != other.index_; + } + bool operator<(const RenderLayer &other) const + { + return index_ < other.index_; + } + bool operator>(const RenderLayer &other) const + { + return index_ > other.index_; + } + + private: + int index_; + }; + } +} // namespace endor namespace std { template <> - struct hash + struct hash { - inline size_t operator()(const builtin_scene::RenderLayer &layer) const noexcept + inline size_t operator()(const endor::builtin_scene::RenderLayer &layer) const noexcept { return hash()(layer.index()); } diff --git a/src/client/builtin_scene/render_queue.cpp b/src/client/builtin_scene/render_queue.cpp index 706c03313..b681a8d0c 100644 --- a/src/client/builtin_scene/render_queue.cpp +++ b/src/client/builtin_scene/render_queue.cpp @@ -1,38 +1,41 @@ #include #include "./render_queue.hpp" -namespace builtin_scene +namespace endor { - using namespace transmute::common; - - bool RenderQueue::operator<(const RenderQueue &other) const + namespace builtin_scene { - // Priority is determined by: - // 1. translateZ: Physical depth in 3D space - // 2. zIndex: Logical stacking order - // 3. base: Base queue number - // - if (!math_utils::ApproximatelyEqual(translateZ, other.translateZ)) - return translateZ < other.translateZ; - if (zIndex != other.zIndex) - return zIndex < other.zIndex; - return base < other.base; - } + using namespace transmute::common; - bool RenderQueue::operator>(const RenderQueue &other) const - { - return !(*this < other) && !(*this == other); - } + bool RenderQueue::operator<(const RenderQueue &other) const + { + // Priority is determined by: + // 1. translateZ: Physical depth in 3D space + // 2. zIndex: Logical stacking order + // 3. base: Base queue number + // + if (!math_utils::ApproximatelyEqual(translateZ, other.translateZ)) + return translateZ < other.translateZ; + if (zIndex != other.zIndex) + return zIndex < other.zIndex; + return base < other.base; + } - bool RenderQueue::operator==(const RenderQueue &other) const - { - return base == other.base && - zIndex == other.zIndex && - math_utils::ApproximatelyEqual(translateZ, other.translateZ); - } + bool RenderQueue::operator>(const RenderQueue &other) const + { + return !(*this < other) && !(*this == other); + } - bool RenderQueue::operator!=(const RenderQueue &other) const - { - return !(*this == other); + bool RenderQueue::operator==(const RenderQueue &other) const + { + return base == other.base && + zIndex == other.zIndex && + math_utils::ApproximatelyEqual(translateZ, other.translateZ); + } + + bool RenderQueue::operator!=(const RenderQueue &other) const + { + return !(*this == other); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/render_queue.hpp b/src/client/builtin_scene/render_queue.hpp index cf8382791..67234cf2f 100644 --- a/src/client/builtin_scene/render_queue.hpp +++ b/src/client/builtin_scene/render_queue.hpp @@ -3,44 +3,47 @@ #include #include -namespace builtin_scene +namespace endor { - /** + namespace builtin_scene + { + /** * RenderQueue is used to determine the rendering order of elements in the scene. */ - class RenderQueue - { - public: - RenderQueue(uint32_t base = 0) - : base(base) - , zIndex(0) - , translateZ(0) + class RenderQueue { - } - RenderQueue(const RenderQueue &) = default; - ~RenderQueue() = default; + public: + RenderQueue(uint32_t base = 0) + : base(base) + , zIndex(0) + , translateZ(0) + { + } + RenderQueue(const RenderQueue &) = default; + ~RenderQueue() = default; - public: - bool operator<(const RenderQueue &other) const; - bool operator>(const RenderQueue &other) const; - bool operator==(const RenderQueue &other) const; - bool operator!=(const RenderQueue &other) const; + public: + bool operator<(const RenderQueue &other) const; + bool operator>(const RenderQueue &other) const; + bool operator==(const RenderQueue &other) const; + bool operator!=(const RenderQueue &other) const; - friend std::ostream &operator<<(std::ostream &os, const RenderQueue &queue) - { - os << "RenderQueue(base=" << queue.base - << ", zIndex=" << queue.zIndex - << ", translateZ=" << queue.translateZ - << ")"; - return os; - } + friend std::ostream &operator<<(std::ostream &os, const RenderQueue &queue) + { + os << "RenderQueue(base=" << queue.base + << ", zIndex=" << queue.zIndex + << ", translateZ=" << queue.translateZ + << ")"; + return os; + } - public: - // The base number of the render queue. - uint32_t base; - // CSS: z-index - float zIndex = 0; - // CSS: transform: translateZ() - float translateZ = 0; - }; -} + public: + // The base number of the render queue. + uint32_t base; + // CSS: z-index + float zIndex = 0; + // CSS: transform: translateZ() + float translateZ = 0; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/renderable_object.hpp b/src/client/builtin_scene/renderable_object.hpp index f868bafdc..2e68c7a42 100644 --- a/src/client/builtin_scene/renderable_object.hpp +++ b/src/client/builtin_scene/renderable_object.hpp @@ -3,56 +3,59 @@ #include #include "./material_base.hpp" -namespace builtin_scene +namespace endor { - class IRenderableObject + namespace builtin_scene { - public: - virtual ~IRenderableObject() = default; + class IRenderableObject + { + public: + virtual ~IRenderableObject() = default; - public: - Material material; - }; + public: + Material material; + }; - class Transform : public IRenderableObject - { - public: - Transform() + class Transform : public IRenderableObject { - } - virtual ~Transform() = default; + public: + Transform() + { + } + virtual ~Transform() = default; - public: - void setPosition(float x, float y, float z) - { - position = glm::vec3(x, y, z); - updateTransform(); - } - void setRotation(float x, float y, float z) - { - rotation = glm::vec3(x, y, z); - updateTransform(); - } - void setScale(float x, float y, float z) - { - scale = glm::vec3(x, y, z); - updateTransform(); - } + public: + void setPosition(float x, float y, float z) + { + position = glm::vec3(x, y, z); + updateTransform(); + } + void setRotation(float x, float y, float z) + { + rotation = glm::vec3(x, y, z); + updateTransform(); + } + void setScale(float x, float y, float z) + { + scale = glm::vec3(x, y, z); + updateTransform(); + } - protected: - void updateTransform() - { - transform = glm::translate(glm::mat4(1.0f), position) * - glm::rotate(glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(1, 0, 0)) * - glm::rotate(glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(0, 1, 0)) * - glm::rotate(glm::mat4(1.0f), glm::radians(rotation.z), glm::vec3(0, 0, 1)) * - glm::scale(glm::mat4(1.0f), scale); - } + protected: + void updateTransform() + { + transform = glm::translate(glm::mat4(1.0f), position) * + glm::rotate(glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(1, 0, 0)) * + glm::rotate(glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(0, 1, 0)) * + glm::rotate(glm::mat4(1.0f), glm::radians(rotation.z), glm::vec3(0, 0, 1)) * + glm::scale(glm::mat4(1.0f), scale); + } - protected: - glm::vec3 position; - glm::vec3 rotation; - glm::vec3 scale; - glm::mat4 transform; - }; -} + protected: + glm::vec3 position; + glm::vec3 rotation; + glm::vec3 scale; + glm::mat4 transform; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/renderer/render_pass.hpp b/src/client/builtin_scene/renderer/render_pass.hpp index b5bead790..8bfde36a1 100644 --- a/src/client/builtin_scene/renderer/render_pass.hpp +++ b/src/client/builtin_scene/renderer/render_pass.hpp @@ -3,27 +3,30 @@ #include #include -namespace builtin_scene +namespace endor { - enum class RenderPass + namespace builtin_scene { - kOpaques, - kTransparents, - kPostProcessing, - }; + enum class RenderPass + { + kOpaques, + kTransparents, + kPostProcessing, + }; - inline std::ostream &operator<<(std::ostream &os, RenderPass pass) - { - switch (pass) + inline std::ostream &operator<<(std::ostream &os, RenderPass pass) { - case RenderPass::kOpaques: - return os << "RenderPass(Opaques)"; - case RenderPass::kTransparents: - return os << "RenderPass(Transparents)"; - case RenderPass::kPostProcessing: - return os << "RenderPass(PostProcessing)"; - default: - return os << "RenderPass(Unknown)"; + switch (pass) + { + case RenderPass::kOpaques: + return os << "RenderPass(Opaques)"; + case RenderPass::kTransparents: + return os << "RenderPass(Transparents)"; + case RenderPass::kPostProcessing: + return os << "RenderPass(PostProcessing)"; + default: + return os << "RenderPass(Unknown)"; + } } } -} +} // namespace endor diff --git a/src/client/builtin_scene/renderer/render_target.hpp b/src/client/builtin_scene/renderer/render_target.hpp index 47f474846..dfada5f86 100644 --- a/src/client/builtin_scene/renderer/render_target.hpp +++ b/src/client/builtin_scene/renderer/render_target.hpp @@ -4,63 +4,66 @@ #include #include -namespace builtin_scene +namespace endor { - /** - * The WebXR render target for the renderer, which stores the view or views to render. - */ - class XRRenderTarget + namespace builtin_scene { - public: /** + * The WebXR render target for the renderer, which stores the view or views to render. + */ + class XRRenderTarget + { + public: + /** * Construct the render target for the single view. * * @param view The view to render. */ - XRRenderTarget(std::shared_ptr view) - : multiview_(false) - , view_(view) - { - } - /** + XRRenderTarget(std::shared_ptr view) + : multiview_(false) + , view_(view) + { + } + /** * Construct the render target for the multiple views. * * @param views The views to render. */ - XRRenderTarget(const std::vector> &views) - : multiview_(true) - , views_(&views) - { - } + XRRenderTarget(const std::vector> &views) + : multiview_(true) + , views_(&views) + { + } - public: - /** + public: + /** * @returns Whether the render target is multiview. */ - inline bool isMultiview() const - { - return multiview_; - } - /** + inline bool isMultiview() const + { + return multiview_; + } + /** * @returns The view to render, it is `nullptr` if the render target is multiview. */ - inline std::shared_ptr view() const - { - return view_; - } - /** + inline std::shared_ptr view() const + { + return view_; + } + /** * @returns The views to render, and it will throw an exception if the render target is not multiview. */ - inline const std::vector> &views() const - { - if (views_ == nullptr) - throw std::runtime_error("The render target is not multiview."); - return *views_; - } + inline const std::vector> &views() const + { + if (views_ == nullptr) + throw std::runtime_error("The render target is not multiview."); + return *views_; + } - private: - bool multiview_ = false; - std::shared_ptr view_ = nullptr; - const std::vector> *views_ = nullptr; - }; -} + private: + bool multiview_ = false; + std::shared_ptr view_ = nullptr; + const std::vector> *views_ = nullptr; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/renderer/scene_renderer.cpp b/src/client/builtin_scene/renderer/scene_renderer.cpp index 704260d0a..e7e65f2aa 100644 --- a/src/client/builtin_scene/renderer/scene_renderer.cpp +++ b/src/client/builtin_scene/renderer/scene_renderer.cpp @@ -4,358 +4,361 @@ #include #include "./scene_renderer.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace ecs; - using namespace client_graphics; - using namespace client_xr; - - SceneRenderer::SceneRenderer(shared_ptr glContext, math::Size3 volumeSize) - : glContext_(glContext) - , volumeSize_(volumeSize) + namespace builtin_scene { - glContext_->enable(WEBGL_CULL_FACE); - } - - void SceneRenderer::setViewport(XRViewport &viewport) - { - glContext_->viewport(viewport.x, - viewport.y, - viewport.width, - viewport.height); - } - - void SceneRenderer::setPolygonOffset(bool enabled) - { - if (enabled) + using namespace std; + using namespace ecs; + using namespace client_graphics; + using namespace client_xr; + + SceneRenderer::SceneRenderer(shared_ptr glContext, math::Size3 volumeSize) + : glContext_(glContext) + , volumeSize_(volumeSize) { - glContext_->enable(WEBGL_POLYGON_OFFSET_FILL); - glContext_->polygonOffset(-1.0f, -1.0f); + glContext_->enable(WEBGL_CULL_FACE); } - else + + void SceneRenderer::setViewport(XRViewport &viewport) { - glContext_->disable(WEBGL_POLYGON_OFFSET_FILL); + glContext_->viewport(viewport.x, + viewport.y, + viewport.width, + viewport.height); } - } - - void SceneRenderer::initializeMesh3d(shared_ptr mesh3d) - { - auto vao = glContext_->createVertexArray(); - auto vbo = glContext_->createBuffer(); - auto ebo = glContext_->createBuffer(); + void SceneRenderer::setPolygonOffset(bool enabled) { - // Bind the vertex array object, vertex buffer object, and element buffer object. - WebGLVertexArrayScope vaoScope(glContext_, vao); - glContext_->bindBuffer(WebGLBufferBindingTarget::kElementArrayBuffer, ebo); + if (enabled) + { + glContext_->enable(WEBGL_POLYGON_OFFSET_FILL); + glContext_->polygonOffset(-1.0f, -1.0f); + } + else + { + glContext_->disable(WEBGL_POLYGON_OFFSET_FILL); + } } - mesh3d->initialize(glContext_, vao, vbo, ebo); - } - void SceneRenderer::configureMeshVertexData(shared_ptr mesh3d, shared_ptr program) - { - /** - * Configure the vertext attributes. - */ - auto configureAttribute = [this](const IVertexAttribute &attrib, - int index, - size_t stride, - size_t offset) - { - glContext_->vertexAttribPointer(index, - attrib.size(), - attrib.type(), - attrib.normalized(), - stride, - offset); - glContext_->enableVertexAttribArray(index); - }; - - auto vao = mesh3d->vertexArrayObject(); + void SceneRenderer::initializeMesh3d(shared_ptr mesh3d) { - WebGLVertexArrayScope vaoScope(glContext_, vao); - - // Configure the vertex attributes - glContext_->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, mesh3d->vertexBufferObject()); - mesh3d->iterateEnabledAttributes(program, configureAttribute); - - // Configure the vertex buffer data - auto &vertexBufferData = mesh3d->vertexBuffer().data(); - glContext_->bufferData(WebGLBufferBindingTarget::kArrayBuffer, - vertexBufferData.size(), - const_cast(vertexBufferData.data()), - WebGLBufferUsage::kStaticDraw); - - // Configure the element buffer object - auto indices = mesh3d->indices(); - glContext_->bufferData(WebGLBufferBindingTarget::kElementArrayBuffer, - indices.dataSize(), - indices.dataBuffer(), - WebGLBufferUsage::kStaticDraw); + auto vao = glContext_->createVertexArray(); + auto vbo = glContext_->createBuffer(); + auto ebo = glContext_->createBuffer(); + + { + // Bind the vertex array object, vertex buffer object, and element buffer object. + WebGLVertexArrayScope vaoScope(glContext_, vao); + glContext_->bindBuffer(WebGLBufferBindingTarget::kElementArrayBuffer, ebo); + } + mesh3d->initialize(glContext_, vao, vbo, ebo); } - // Configure the instance vbo and related attributes if it's an instanced mesh. - if (mesh3d->isInstancedMesh()) + void SceneRenderer::configureMeshVertexData(shared_ptr mesh3d, shared_ptr program) { /** - * Configure the instance attributes. - */ - auto configureInstanceAttribute = [this](const IVertexAttribute &attrib, - int index, - size_t stride, - size_t offset) + * Configure the vertext attributes. + */ + auto configureAttribute = [this](const IVertexAttribute &attrib, + int index, + size_t stride, + size_t offset) { - glContext_->enableVertexAttribArray(index); glContext_->vertexAttribPointer(index, attrib.size(), attrib.type(), attrib.normalized(), stride, offset); - glContext_->vertexAttribDivisor(index, 1); + glContext_->enableVertexAttribArray(index); }; - // Handle GaussianSplatsMesh specifically - if (mesh3d->is()) + auto vao = mesh3d->vertexArrayObject(); { - auto &gaussianMesh = mesh3d->getHandleCheckedAsRef(); + WebGLVertexArrayScope vaoScope(glContext_, vao); + + // Configure the vertex attributes + glContext_->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, mesh3d->vertexBufferObject()); + mesh3d->iterateEnabledAttributes(program, configureAttribute); + + // Configure the vertex buffer data + auto &vertexBufferData = mesh3d->vertexBuffer().data(); + glContext_->bufferData(WebGLBufferBindingTarget::kArrayBuffer, + vertexBufferData.size(), + const_cast(vertexBufferData.data()), + WebGLBufferUsage::kStaticDraw); + + // Configure the element buffer object + auto indices = mesh3d->indices(); + glContext_->bufferData(WebGLBufferBindingTarget::kElementArrayBuffer, + indices.dataSize(), + indices.dataBuffer(), + WebGLBufferUsage::kStaticDraw); + } - // Configure for Gaussian splats (they use the main VAO) - WebGLVertexArrayScope vaoScope(glContext_, mesh3d->vertexArrayObject()); + // Configure the instance vbo and related attributes if it's an instanced mesh. + if (mesh3d->isInstancedMesh()) + { + /** + * Configure the instance attributes. + */ + auto configureInstanceAttribute = [this](const IVertexAttribute &attrib, + int index, + size_t stride, + size_t offset) + { + glContext_->enableVertexAttribArray(index); + glContext_->vertexAttribPointer(index, + attrib.size(), + attrib.type(), + attrib.normalized(), + stride, + offset); + glContext_->vertexAttribDivisor(index, 1); + }; + + // Handle GaussianSplatsMesh specifically + if (mesh3d->is()) + { + auto &gaussianMesh = mesh3d->getHandleCheckedAsRef(); - // Configure instance attributes - gaussianMesh.setupSplatBuffer(glContext_, mesh3d->vertexArrayObject()); + // Configure for Gaussian splats (they use the main VAO) + WebGLVertexArrayScope vaoScope(glContext_, mesh3d->vertexArrayObject()); - // Bind the splat instance buffer and configure instance attributes - auto splatBuffer = gaussianMesh.getSplatInstanceBuffer(); - if (splatBuffer) - { - glContext_->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, splatBuffer); - gaussianMesh.iterateInstanceAttributes(program, configureInstanceAttribute); + // Configure instance attributes + gaussianMesh.setupSplatBuffer(glContext_, mesh3d->vertexArrayObject()); + + // Bind the splat instance buffer and configure instance attributes + auto splatBuffer = gaussianMesh.getSplatInstanceBuffer(); + if (splatBuffer) + { + glContext_->bindBuffer(WebGLBufferBindingTarget::kArrayBuffer, splatBuffer); + gaussianMesh.iterateInstanceAttributes(program, configureInstanceAttribute); + } } } } - } - void SceneRenderer::updateMeshVertexData(shared_ptr mesh3d, shared_ptr program) - { - } + void SceneRenderer::updateMeshVertexData(shared_ptr mesh3d, shared_ptr program) + { + } - void SceneRenderer::initializeMeshMaterial3d(shared_ptr mesh3d, shared_ptr meshMaterial3d) - { - auto program = glContext_->createProgram(); - auto vertexShader = glContext_->createShader(WebGLShaderType::kVertex); - auto fragmentShader = glContext_->createShader(WebGLShaderType::kFragment); - glContext_->shaderSource(vertexShader, - meshMaterial3d->getShaderSource(WebGLShaderType::kVertex)); - glContext_->shaderSource(fragmentShader, - meshMaterial3d->getShaderSource(WebGLShaderType::kFragment)); - glContext_->compileShader(vertexShader); - glContext_->compileShader(fragmentShader); - glContext_->attachShader(program, vertexShader); - glContext_->attachShader(program, fragmentShader); - glContext_->linkProgram(program); - - // Configure the vertex data: vertex array object, vertex buffer object, and element buffer object. - // Configure the vertex attributes and the vertex buffer data. - configureMeshVertexData(mesh3d, program); - - // Configure the initial uniform values + void SceneRenderer::initializeMeshMaterial3d(shared_ptr mesh3d, shared_ptr meshMaterial3d) { - WebGLProgramScope programScope(glContext_, program); - updateTransformationMatrix(program, nullptr, nullptr, true); // forcily update the transformation matrix. + auto program = glContext_->createProgram(); + auto vertexShader = glContext_->createShader(WebGLShaderType::kVertex); + auto fragmentShader = glContext_->createShader(WebGLShaderType::kFragment); + glContext_->shaderSource(vertexShader, + meshMaterial3d->getShaderSource(WebGLShaderType::kVertex)); + glContext_->shaderSource(fragmentShader, + meshMaterial3d->getShaderSource(WebGLShaderType::kFragment)); + glContext_->compileShader(vertexShader); + glContext_->compileShader(fragmentShader); + glContext_->attachShader(program, vertexShader); + glContext_->attachShader(program, fragmentShader); + glContext_->linkProgram(program); + + // Configure the vertex data: vertex array object, vertex buffer object, and element buffer object. + // Configure the vertex attributes and the vertex buffer data. + configureMeshVertexData(mesh3d, program); + + // Configure the initial uniform values + { + WebGLProgramScope programScope(glContext_, program); + updateTransformationMatrix(program, nullptr, nullptr, true); // forcily update the transformation matrix. - meshMaterial3d->initialize(glContext_, program, mesh3d); + meshMaterial3d->initialize(glContext_, program, mesh3d); + } } - } - - void SceneRenderer::tryUpdateMeshMaterial3d(shared_ptr mesh3d, shared_ptr meshMaterial3d) - { - auto program = meshMaterial3d->program(); - if (program == nullptr) [[unlikely]] - return; - // Update the vertex data if it's dirty - updateMeshVertexData(mesh3d, program); - // TODO: update the instance data - } + void SceneRenderer::tryUpdateMeshMaterial3d(shared_ptr mesh3d, shared_ptr meshMaterial3d) + { + auto program = meshMaterial3d->program(); + if (program == nullptr) [[unlikely]] + return; - void SceneRenderer::drawMesh3d(const EntityId &entity, - shared_ptr mesh, - shared_ptr material, - shared_ptr transform, - shared_ptr parentTransform, - RenderPass renderPass, - optional renderTarget) - { - assert(mesh != nullptr && material != nullptr); - assert(mesh->initialized()); - assert(material->initialized()); - WebGLProgramScope programScope(glContext_, material->program()); + // Update the vertex data if it's dirty + updateMeshVertexData(mesh3d, program); + // TODO: update the instance data + } - // Call lifecycle methods - material->onBeforeDrawMesh(mesh); + void SceneRenderer::drawMesh3d(const EntityId &entity, + shared_ptr mesh, + shared_ptr material, + shared_ptr transform, + shared_ptr parentTransform, + RenderPass renderPass, + optional renderTarget) + { + assert(mesh != nullptr && material != nullptr); + assert(mesh->initialized()); + assert(material->initialized()); + WebGLProgramScope programScope(glContext_, material->program()); - // Update matrices - updateViewProjectionMatrix(programScope.program(), renderTarget); - updateTransformationMatrix(programScope.program(), transform, parentTransform); + // Call lifecycle methods + material->onBeforeDrawMesh(mesh); - // Draw the mesh - { - WebGLVertexArrayScope vaoScope(glContext_, mesh->vertexArrayObject()); - material->drawMeshImpl(mesh, renderPass, renderTarget); - } + // Update matrices + updateViewProjectionMatrix(programScope.program(), renderTarget); + updateTransformationMatrix(programScope.program(), transform, parentTransform); - // Call lifecycle methods - material->onAfterDrawMesh(mesh); - } + // Draw the mesh + { + WebGLVertexArrayScope vaoScope(glContext_, mesh->vertexArrayObject()); + material->drawMeshImpl(mesh, renderPass, renderTarget); + } - void SceneRenderer::updateViewProjectionMatrix(shared_ptr program, - optional renderTarget) - { - assert(program != nullptr); + // Call lifecycle methods + material->onAfterDrawMesh(mesh); + } - // forcibly set to right-handed - auto handedness = MatrixHandedness::MATRIX_RIGHT_HANDED; - auto setUniformMatrix4x4 = [this, &handedness](WebGLUniformLocation &loc, WebGLMatrixPlaceholderId placeholder) - { - MatrixComputationGraph graph(placeholder, handedness); - glContext_->uniformMatrix4fv(loc, false, graph); - }; - auto updateMatricesForMultiview = [this, - &program, - &renderTarget, - &setUniformMatrix4x4](WebGLUniformLocation &loc, - WebGLMatrixPlaceholderId placeholder, - const char *nameForRightEye) + void SceneRenderer::updateViewProjectionMatrix(shared_ptr program, + optional renderTarget) { - if (renderTarget != nullopt) + assert(program != nullptr); + + // forcibly set to right-handed + auto handedness = MatrixHandedness::MATRIX_RIGHT_HANDED; + auto setUniformMatrix4x4 = [this, &handedness](WebGLUniformLocation &loc, WebGLMatrixPlaceholderId placeholder) + { + MatrixComputationGraph graph(placeholder, handedness); + glContext_->uniformMatrix4fv(loc, false, graph); + }; + auto updateMatricesForMultiview = [this, + &program, + &renderTarget, + &setUniformMatrix4x4](WebGLUniformLocation &loc, + WebGLMatrixPlaceholderId placeholder, + const char *nameForRightEye) { - if (renderTarget->isMultiview()) + if (renderTarget != nullopt) { - auto locR = glContext_->getUniformLocation(program, nameForRightEye); - if (!locR.has_value()) + if (renderTarget->isMultiview()) { - cerr << "The " << nameForRightEye << " uniform location is not found in multiview mode." << endl; - assert(false && "The right eye uniform location is not found."); - return; + auto locR = glContext_->getUniformLocation(program, nameForRightEye); + if (!locR.has_value()) + { + cerr << "The " << nameForRightEye << " uniform location is not found in multiview mode." << endl; + assert(false && "The right eye uniform location is not found."); + return; + } + + setUniformMatrix4x4(loc, placeholder); + setUniformMatrix4x4(locR.value(), static_cast((int)placeholder + 1)); } + else + { + auto view = renderTarget->view(); + assert(view != nullptr); - setUniformMatrix4x4(loc, placeholder); - setUniformMatrix4x4(locR.value(), static_cast((int)placeholder + 1)); + if (view->eye() == XREye::kRight) + setUniformMatrix4x4(loc, static_cast((int)placeholder + 1)); + else + setUniformMatrix4x4(loc, placeholder); + } } else { - auto view = renderTarget->view(); - assert(view != nullptr); - - if (view->eye() == XREye::kRight) - setUniformMatrix4x4(loc, static_cast((int)placeholder + 1)); - else - setUniformMatrix4x4(loc, placeholder); + // Default view projection matrix + setUniformMatrix4x4(loc, placeholder); } + }; + + // Update `view` if present + auto viewMatrix = glContext_->getUniformLocation(program, "view"); + if (viewMatrix.has_value()) + { + updateMatricesForMultiview(viewMatrix.value(), + WebGLMatrixPlaceholderId::ViewMatrix, + "viewR"); } - else + + // Update `projectionM` if present + auto projectionMatrix = glContext_->getUniformLocation(program, "projection"); + if (projectionMatrix.has_value()) { - // Default view projection matrix - setUniformMatrix4x4(loc, placeholder); + updateMatricesForMultiview(projectionMatrix.value(), + WebGLMatrixPlaceholderId::ProjectionMatrix, + "projectionR"); } - }; - // Update `view` if present - auto viewMatrix = glContext_->getUniformLocation(program, "view"); - if (viewMatrix.has_value()) - { - updateMatricesForMultiview(viewMatrix.value(), - WebGLMatrixPlaceholderId::ViewMatrix, - "viewR"); + // Update `viewProjection`. + auto viewProjection = glContext_->getUniformLocation(program, "viewProjection"); + if (viewProjection.has_value()) + { + updateMatricesForMultiview(viewProjection.value(), + WebGLMatrixPlaceholderId::ViewProjectionMatrix, + "viewProjectionR"); + } } - // Update `projectionM` if present - auto projectionMatrix = glContext_->getUniformLocation(program, "projection"); - if (projectionMatrix.has_value()) + optional SceneRenderer::updateTransformationMatrix(shared_ptr program, + shared_ptr transform, + shared_ptr parentTransform, + bool forceUpdate) { - updateMatricesForMultiview(projectionMatrix.value(), - WebGLMatrixPlaceholderId::ProjectionMatrix, - "projectionR"); - } + assert(program != nullptr); - // Update `viewProjection`. - auto viewProjection = glContext_->getUniformLocation(program, "viewProjection"); - if (viewProjection.has_value()) - { - updateMatricesForMultiview(viewProjection.value(), - WebGLMatrixPlaceholderId::ViewProjectionMatrix, - "viewProjectionR"); - } - } + auto loc = glContext_->getUniformLocation(program, "modelMatrix"); + if (!loc.has_value()) + { + return nullopt; + } - optional SceneRenderer::updateTransformationMatrix(shared_ptr program, - shared_ptr transform, - shared_ptr parentTransform, - bool forceUpdate) - { - assert(program != nullptr); + glm::mat4 matToUpdate; + if (transform == nullptr || !transform->isDirty()) + { + if (!forceUpdate) + return nullopt; - auto loc = glContext_->getUniformLocation(program, "modelMatrix"); - if (!loc.has_value()) - { - return nullopt; - } + if (transform != nullptr) + matToUpdate = transform->matrix(); + else + matToUpdate = glm::mat4(1.0f); + } + else + matToUpdate = transform->matrix(); - glm::mat4 matToUpdate; - if (transform == nullptr || !transform->isDirty()) - { - if (!forceUpdate) - return nullopt; + // Handle the post transform + glm::mat4 postMat = glm::mat4(1.0f); + if (parentTransform != nullptr && parentTransform->hasPostTransform()) + { + auto &parentPostTransform = parentTransform->getOrInitPostTransform(); + postMat = parentPostTransform.accumulatedMatrix(); + } + if (transform != nullptr && transform->hasPostTransform()) + { + auto &postTransform = transform->getOrInitPostTransform(); + postMat = postTransform.matrix() * postMat; + postTransform.setAccumulatedMatrix(postMat); + } + matToUpdate = postMat * matToUpdate; - if (transform != nullptr) - matToUpdate = transform->matrix(); - else - matToUpdate = glm::mat4(1.0f); + glContext_->uniformMatrix4fv(loc.value(), false, matToUpdate); + return matToUpdate; } - else - matToUpdate = transform->matrix(); - // Handle the post transform - glm::mat4 postMat = glm::mat4(1.0f); - if (parentTransform != nullptr && parentTransform->hasPostTransform()) - { - auto &parentPostTransform = parentTransform->getOrInitPostTransform(); - postMat = parentPostTransform.accumulatedMatrix(); - } - if (transform != nullptr && transform->hasPostTransform()) + void SceneRenderer::onBeforeRender(const RenderPass renderPass, std::optional renderTarget) { - auto &postTransform = transform->getOrInitPostTransform(); - postMat = postTransform.matrix() * postMat; - postTransform.setAccumulatedMatrix(postMat); + if (renderPass == RenderPass::kOpaques) + { + glContext_->enable(WEBGL_DEPTH_TEST); + glContext_->depthFunc(WEBGL_LEQUAL); + glContext_->depthMask(true); + glContext_->disable(WEBGL_BLEND); + } + else if (renderPass == RenderPass::kTransparents) + { + glContext_->enable(WEBGL_DEPTH_TEST); + glContext_->depthMask(false); + glContext_->enable(WEBGL_BLEND); + glContext_->blendFunc(WEBGL_SRC_ALPHA, WEBGL_ONE_MINUS_SRC_ALPHA); + } } - matToUpdate = postMat * matToUpdate; - glContext_->uniformMatrix4fv(loc.value(), false, matToUpdate); - return matToUpdate; - } - - void SceneRenderer::onBeforeRender(const RenderPass renderPass, std::optional renderTarget) - { - if (renderPass == RenderPass::kOpaques) - { - glContext_->enable(WEBGL_DEPTH_TEST); - glContext_->depthFunc(WEBGL_LEQUAL); - glContext_->depthMask(true); - glContext_->disable(WEBGL_BLEND); - } - else if (renderPass == RenderPass::kTransparents) + void SceneRenderer::onAfterRender(const RenderPass renderPass, std::optional renderTarget) { - glContext_->enable(WEBGL_DEPTH_TEST); - glContext_->depthMask(false); - glContext_->enable(WEBGL_BLEND); - glContext_->blendFunc(WEBGL_SRC_ALPHA, WEBGL_ONE_MINUS_SRC_ALPHA); } } - - void SceneRenderer::onAfterRender(const RenderPass renderPass, std::optional renderTarget) - { - } -} +} // namespace endor diff --git a/src/client/builtin_scene/renderer/scene_renderer.hpp b/src/client/builtin_scene/renderer/scene_renderer.hpp index 433e6690d..95d431014 100644 --- a/src/client/builtin_scene/renderer/scene_renderer.hpp +++ b/src/client/builtin_scene/renderer/scene_renderer.hpp @@ -15,53 +15,55 @@ #include "./render_pass.hpp" #include "./render_target.hpp" -namespace builtin_scene +namespace endor { - class SceneRenderer : public ecs::Resource + namespace builtin_scene { - friend class RendererScope; - friend class RenderSystem; + class SceneRenderer : public ecs::Resource + { + friend class RendererScope; + friend class RenderSystem; - public: - SceneRenderer(std::shared_ptr glContext, math::Size3 volumeSize); + public: + SceneRenderer(std::shared_ptr glContext, math::Size3 volumeSize); - public: - /** + public: + /** * @returns The WebGL context of the renderer. */ - inline std::shared_ptr glContext() const - { - return glContext_; - } - /** + inline std::shared_ptr glContext() const + { + return glContext_; + } + /** * Set the viewport of the renderer. * * @param viewport The viewport to set. */ - void setViewport(client_xr::XRViewport &viewport); - /** + void setViewport(client_xr::XRViewport &viewport); + /** * Set polygon offset enabled or disabled. * * @param enabled Whether to enable the stencil write mask. */ - void setPolygonOffset(bool enabled = true); - /** + void setPolygonOffset(bool enabled = true); + /** * Initialize the mesh with the given WebGL context, it will create the vertex array object, * vertex buffer object, and element buffer object, and upload the data to the GPU. * * @param mesh3d The mesh to initialize. */ - void initializeMesh3d(std::shared_ptr mesh3d); - /** + void initializeMesh3d(std::shared_ptr mesh3d); + /** * Configure the mesh vertex data with the given WebGL context, it will upload the dirty vertex data * to the GPU. * * @param mesh3d The mesh to configure the vertex data. * @param program The WebGL program to configure the vertex data with. */ - void configureMeshVertexData(std::shared_ptr mesh3d, std::shared_ptr program); - void updateMeshVertexData(std::shared_ptr mesh3d, std::shared_ptr program); - /** + void configureMeshVertexData(std::shared_ptr mesh3d, std::shared_ptr program); + void updateMeshVertexData(std::shared_ptr mesh3d, std::shared_ptr program); + /** * Initialize the mesh material with the given WebGL context, it will create the program, shaders * and link the program. * @@ -71,15 +73,15 @@ namespace builtin_scene * * @param meshMaterial3d The mesh material to initialize. */ - void initializeMeshMaterial3d(std::shared_ptr mesh3d, std::shared_ptr meshMaterial3d); - /** + void initializeMeshMaterial3d(std::shared_ptr mesh3d, std::shared_ptr meshMaterial3d); + /** * Try to update the mesh material: * * 1. update the mesh vertext data if it's dirty. * 2. update the instanced data like color, texture or other attributes. */ - void tryUpdateMeshMaterial3d(std::shared_ptr mesh3d, std::shared_ptr meshMaterial3d); - /** + void tryUpdateMeshMaterial3d(std::shared_ptr mesh3d, std::shared_ptr meshMaterial3d); + /** * Draw the `Mesh3d` with the given `MeshMaterial3d` and `XRView`. * * @param mesh The mesh to draw. @@ -89,22 +91,22 @@ namespace builtin_scene * @param renderPass The render pass to use, which can be used to filter the objects or instances to render. * @param renderTarget The XR render target to draw the mesh with. */ - void drawMesh3d(const ecs::EntityId &entity, - std::shared_ptr mesh, - std::shared_ptr material, - std::shared_ptr transform, - std::shared_ptr parentTransform, - RenderPass renderPass, - std::optional renderTarget); - /** + void drawMesh3d(const ecs::EntityId &entity, + std::shared_ptr mesh, + std::shared_ptr material, + std::shared_ptr transform, + std::shared_ptr parentTransform, + RenderPass renderPass, + std::optional renderTarget); + /** * Update the view projection matrix. * * @param program The WebGL program to update the view projection matrix with. * @param renderTarget The XR render target to update the view projection matrix with. */ - void updateViewProjectionMatrix(std::shared_ptr program, - std::optional renderTarget); - /** + void updateViewProjectionMatrix(std::shared_ptr program, + std::optional renderTarget); + /** * Update the transformation matrix for the given program and mesh. * * If the `forceUpdate` is `false`, this method will only update the transformation matrix if the @@ -115,17 +117,18 @@ namespace builtin_scene * @param transform The transform to update the transformation matrix with, `nullptr` for the identity matrix. * @param forceUpdate Whether to force update the transformation matrix. */ - std::optional updateTransformationMatrix(std::shared_ptr program, - std::shared_ptr transform, - std::shared_ptr parentTransform = nullptr, - bool forceUpdate = false); + std::optional updateTransformationMatrix(std::shared_ptr program, + std::shared_ptr transform, + std::shared_ptr parentTransform = nullptr, + bool forceUpdate = false); - public: - void onBeforeRender(const RenderPass renderPass, std::optional renderTarget); - void onAfterRender(const RenderPass renderPass, std::optional renderTarget); + public: + void onBeforeRender(const RenderPass renderPass, std::optional renderTarget); + void onAfterRender(const RenderPass renderPass, std::optional renderTarget); - private: - std::shared_ptr glContext_; - math::Size3 volumeSize_; - }; -} + private: + std::shared_ptr glContext_; + math::Size3 volumeSize_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/scene.cpp b/src/client/builtin_scene/scene.cpp index bda5a5756..946ee45bb 100644 --- a/src/client/builtin_scene/scene.cpp +++ b/src/client/builtin_scene/scene.cpp @@ -4,242 +4,245 @@ #include "./client_renderer.hpp" #include "./gaussian_splatting.hpp" -namespace builtin_scene +namespace endor { - using namespace std; + namespace builtin_scene + { + using namespace std; - /** + /** * The `DefaultPlugin` loads the default components and systems for the builtin scene. */ - class DefaultPlugin final : public ecs::Plugin - { - public: - using ecs::Plugin::Plugin; - - protected: - void build(ecs::App &app) override + class DefaultPlugin final : public ecs::Plugin { - using namespace ecs; - - // Resources - app.addResource(Resource::Make(16)); - app.addResource(Resource::Make()); - app.addResource(Resource::Make()); - app.addResource(Resource::Make()); - - // Components - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - app.registerComponent(); - - // Systems - app.addSystem(SchedulerLabel::kStartup, System::Make()); - app.addSystem(SchedulerLabel::kStartup, System::Make()); - app.addSystem(SchedulerLabel::kStartup, System::Make()); - app.addSystem(SchedulerLabel::kPreUpdate, System::Make()); - - auto updateCamera = System::Make(); - auto updateSplats = System::Make(); - auto renderScene = System::Make(); - updateCamera - ->chain(updateSplats) - ->chain(renderScene); - app.addSystem(SchedulerLabel::kUpdate, updateCamera); - } - }; + public: + using ecs::Plugin::Plugin; - Scene::Scene(TrClientContextPerProcess *clientContext) - : ecs::App() - , clientContext_(clientContext) - , glContext_(clientContext->createHostWebGLContext()) - { - assert(glContext_ != nullptr); - - // Set platform-specific global defines for all materials -#ifdef __ANDROID__ - Material::SetGlobalDefines("__ANDROID__"); -#endif + protected: + void build(ecs::App &app) override + { + using namespace ecs; + + // Resources + app.addResource(Resource::Make(16)); + app.addResource(Resource::Make()); + app.addResource(Resource::Make()); + app.addResource(Resource::Make()); + + // Components + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + app.registerComponent(); + + // Systems + app.addSystem(SchedulerLabel::kStartup, System::Make()); + app.addSystem(SchedulerLabel::kStartup, System::Make()); + app.addSystem(SchedulerLabel::kStartup, System::Make()); + app.addSystem(SchedulerLabel::kPreUpdate, System::Make()); + + auto updateCamera = System::Make(); + auto updateSplats = System::Make(); + auto renderScene = System::Make(); + updateCamera + ->chain(updateSplats) + ->chain(renderScene); + app.addSystem(SchedulerLabel::kUpdate, updateCamera); + } + }; - frameCallback_ = [this](uint32_t time, shared_ptr frame, void *env_) + Scene::Scene(TrClientContextPerProcess *clientContext) + : ecs::App() + , clientContext_(clientContext) + , glContext_(clientContext->createHostWebGLContext()) { - if (paused_) - return; - assert(xrSession_ != nullptr); // ensure the WebXR session is ready. - update(time, frame); - xrSession_->requestAnimationFrame(frameCallback_); - }; + assert(glContext_ != nullptr); - addPlugin(); - addPlugin(); - addPlugin(); - addResource(ecs::Resource::Make(glContext_, volumeSize_)); - } + // Set platform-specific global defines for all materials +#ifdef __ANDROID__ + Material::SetGlobalDefines("__ANDROID__"); +#endif - void Scene::bootstrap() - { - ecs::App::startup(); - } + frameCallback_ = [this](uint32_t time, shared_ptr frame, void *env_) + { + if (paused_) + return; + assert(xrSession_ != nullptr); // ensure the WebXR session is ready. + update(time, frame); + xrSession_->requestAnimationFrame(frameCallback_); + }; + + addPlugin(); + addPlugin(); + addPlugin(); + addResource(ecs::Resource::Make(glContext_, volumeSize_)); + } - void Scene::start(optional volumeSize) - { - if (started_) + void Scene::bootstrap() { - resume(); + ecs::App::startup(); } - else - { - started_ = true; - setVolumeSize(volumeSize); - if (clientContext_->isScriptingEventLoopReady()) + void Scene::start(optional volumeSize) + { + if (started_) { - setupXRSession(); + resume(); } else { - clientContext_->addEventListener( - TrClientContextEventType::ScriptingEventLoopReady, [this](auto _type, auto _event) - { setupXRSession(); }); + started_ = true; + setVolumeSize(volumeSize); + + if (clientContext_->isScriptingEventLoopReady()) + { + setupXRSession(); + } + else + { + clientContext_->addEventListener( + TrClientContextEventType::ScriptingEventLoopReady, [this](auto _type, auto _event) + { setupXRSession(); }); + } } } - } - void Scene::pause() - { - paused_ = true; - } - - void Scene::resume() - { - if (!started_) - throw runtime_error("Scene is not started yet"); - paused_ = false; - xrSession_->requestAnimationFrame(frameCallback_); - } - - ecs::EntityId Scene::createElement(string name, shared_ptr node, optional parent) - { - Transform defaultTransform = Transform::FromXYZ(0.0f, 0.0f, 0.0f); - BoundingBox defaultBoundingBox = BoundingBox(); + void Scene::pause() + { + paused_ = true; + } - if (!parent.has_value()) + void Scene::resume() { - bool isRootRenderable = false; - auto elementNode = dynamic_pointer_cast(node); - if (elementNode != nullptr && elementNode->is("body")) - isRootRenderable = true; // Only the body element as Root is renderable by default. - return spawn( - hierarchy::Element(name, node), - hierarchy::Children(), - hierarchy::Root(isRootRenderable), - BoundingBox(), - RenderLayer(), - defaultTransform); + if (!started_) + throw runtime_error("Scene is not started yet"); + paused_ = false; + xrSession_->requestAnimationFrame(frameCallback_); } - else + + ecs::EntityId Scene::createElement(string name, shared_ptr node, optional parent) { - ecs::EntityId rootEntity; - ecs::EntityId parentEntity = parent.value(); - auto parentRoot = getComponent(parentEntity); - if (parentRoot != nullptr) + Transform defaultTransform = Transform::FromXYZ(0.0f, 0.0f, 0.0f); + BoundingBox defaultBoundingBox = BoundingBox(); + + if (!parent.has_value()) { - rootEntity = parentEntity; + bool isRootRenderable = false; + auto elementNode = dynamic_pointer_cast(node); + if (elementNode != nullptr && elementNode->is("body")) + isRootRenderable = true; // Only the body element as Root is renderable by default. + return spawn( + hierarchy::Element(name, node), + hierarchy::Children(), + hierarchy::Root(isRootRenderable), + BoundingBox(), + RenderLayer(), + defaultTransform); } else { - auto parentParent = getComponent(parentEntity); - assert(parentParent != nullptr && "Parent entity must have a parent component"); - rootEntity = parentParent->root(); + ecs::EntityId rootEntity; + ecs::EntityId parentEntity = parent.value(); + auto parentRoot = getComponent(parentEntity); + if (parentRoot != nullptr) + { + rootEntity = parentEntity; + } + else + { + auto parentParent = getComponent(parentEntity); + assert(parentParent != nullptr && "Parent entity must have a parent component"); + rootEntity = parentParent->root(); + } + + auto newEntity = spawn( + hierarchy::Element(name, node), + hierarchy::Children(), + hierarchy::Parent(parentEntity, rootEntity), + BoundingBox(), + RenderLayer(), + defaultTransform); + + // Update the parent's children + auto children = getComponent(parentEntity); + if (children != nullptr) + children->addChild(newEntity); + return newEntity; } - - auto newEntity = spawn( - hierarchy::Element(name, node), - hierarchy::Children(), - hierarchy::Parent(parentEntity, rootEntity), - BoundingBox(), - RenderLayer(), - defaultTransform); - - // Update the parent's children - auto children = getComponent(parentEntity); - if (children != nullptr) - children->addChild(newEntity); - return newEntity; } - } - bool Scene::removeElement(ecs::EntityId entity) - { - return removeEntity(entity); - } - - std::shared_ptr Scene::getWebXRExperience() - { - return getResource(); - } - - optional Scene::selectRayForHitTesting() - { - auto xrExperience = getResource(); - assert(xrExperience != nullptr); - return xrExperience->selectRayForHitTesting(); - } + bool Scene::removeElement(ecs::EntityId entity) + { + return removeEntity(entity); + } - void Scene::onSelectStart(SelectEventHandler handler) - { - getResource()->resetSelectStartHandler(handler); - } + std::shared_ptr Scene::getWebXRExperience() + { + return getResource(); + } - void Scene::onSelectEnd(SelectEventHandler handler) - { - getResource()->resetSelectEndHandler(handler); - } + optional Scene::selectRayForHitTesting() + { + auto xrExperience = getResource(); + assert(xrExperience != nullptr); + return xrExperience->selectRayForHitTesting(); + } - void Scene::update(uint32_t time, shared_ptr frame) - { - // Update the time and frame to the WebXRSession resource. - auto xrExperience = getResource(); - assert(xrExperience != nullptr); - xrExperience->updateCurrentFrame(time, frame); + void Scene::onSelectStart(SelectEventHandler handler) + { + getResource()->resetSelectStartHandler(handler); + } - // Trigger the ECS update(). - ecs::App::update(); - } + void Scene::onSelectEnd(SelectEventHandler handler) + { + getResource()->resetSelectEndHandler(handler); + } - void Scene::setupXRSession() - { - shared_ptr xrExperience = getResource(); - assert(xrExperience != nullptr); - xrSession_ = xrExperience->requestSession(); + void Scene::update(uint32_t time, shared_ptr frame) + { + // Update the time and frame to the WebXRSession resource. + auto xrExperience = getResource(); + assert(xrExperience != nullptr); + xrExperience->updateCurrentFrame(time, frame); - // Update the render state - client_xr::XRRenderState newRenderState; - newRenderState.baseLayer = client_xr::XRWebGLLayer::Make(xrSession_, glContext_); - xrSession_->updateRenderState(newRenderState); + // Trigger the ECS update(). + ecs::App::update(); + } - // Initialize the WebXR experience + void Scene::setupXRSession() { - xrExperience->updateReferenceSpace( - xrSession_->requestReferenceSpace(client_xr::XRReferenceSpaceType::kLocal)); + shared_ptr xrExperience = getResource(); + assert(xrExperience != nullptr); + xrSession_ = xrExperience->requestSession(); - // Update the multiview flag if required - if (xrExperience->multiviewRequired()) + // Update the render state + client_xr::XRRenderState newRenderState; + newRenderState.baseLayer = client_xr::XRWebGLLayer::Make(xrSession_, glContext_); + xrSession_->updateRenderState(newRenderState); + + // Initialize the WebXR experience { - xrExperience->enableMultiview(true); - Material::SetGlobalDefines("MULTIVIEW"); - Material::SetMultiviewRequired(true); + xrExperience->updateReferenceSpace( + xrSession_->requestReferenceSpace(client_xr::XRReferenceSpaceType::kLocal)); + + // Update the multiview flag if required + if (xrExperience->multiviewRequired()) + { + xrExperience->enableMultiview(true); + Material::SetGlobalDefines("MULTIVIEW"); + Material::SetMultiviewRequired(true); + } } + resume(); } - resume(); } -} +} // namespace endor diff --git a/src/client/builtin_scene/scene.hpp b/src/client/builtin_scene/scene.hpp index 550087402..0e029e7db 100644 --- a/src/client/builtin_scene/scene.hpp +++ b/src/client/builtin_scene/scene.hpp @@ -27,124 +27,127 @@ #include "../xr/webxr_session.hpp" #include "../per_process.hpp" -namespace builtin_scene +namespace endor { - /** - * The main class for the builtin scene which inherits from the `ecs::App`. - */ - class Scene : public ecs::App + namespace builtin_scene { - public: /** + * The main class for the builtin scene which inherits from the `ecs::App`. + */ + class Scene : public ecs::App + { + public: + /** * Create a new instance of the Scene. * * @param clientContext The client context to use. * @returns The new instance of the Scene. */ - static std::shared_ptr Make(TrClientContextPerProcess *clientContext) - { - if (TR_UNLIKELY(clientContext == nullptr)) - clientContext = TrClientContextPerProcess::Get(); - assert(clientContext != nullptr); - return std::make_shared(clientContext); - } - - public: - Scene(TrClientContextPerProcess *clientContext); - ~Scene() = default; - - public: - /** + static std::shared_ptr Make(TrClientContextPerProcess *clientContext) + { + if (TR_UNLIKELY(clientContext == nullptr)) + clientContext = TrClientContextPerProcess::Get(); + assert(clientContext != nullptr); + return std::make_shared(clientContext); + } + + public: + Scene(TrClientContextPerProcess *clientContext); + ~Scene() = default; + + public: + /** * Bootstrap the scene, it should be called after you created the scene instance. */ - void bootstrap(); + void bootstrap(); - /** + /** * Start the scene rendering. * * @param newSize The new size of the scene. */ - void start(std::optional volumeSize = std::nullopt); + void start(std::optional volumeSize = std::nullopt); - /** + /** * Pause the scene rendering. */ - void pause(); + void pause(); - /** + /** * Resuming the scene rendering. */ - void resume(); + void resume(); - /** + /** * Create a new element to the scene for rendering. * * @param name The tag name of the element. * @param node The element's node instance. * @param parent The parent element of the new element. */ - [[nodiscard]] ecs::EntityId createElement(std::string name, - std::shared_ptr node, - std::optional parent = std::nullopt); + [[nodiscard]] ecs::EntityId createElement(std::string name, + std::shared_ptr node, + std::optional parent = std::nullopt); - /** + /** * Remove the element from the scene. * * @param entity The entity to remove. * @returns Whether the element is removed successfully. */ - bool removeElement(ecs::EntityId entity); + bool removeElement(ecs::EntityId entity); - /** + /** * @returns The volume size of this scene. */ - math::Size3 volumeSize() const - { - return volumeSize_; - } + math::Size3 volumeSize() const + { + return volumeSize_; + } - /** + /** * Update the volume size of the scene. * * @param size The new volume size. */ - void setVolumeSize(std::optional newSize) - { - if (newSize.has_value()) - volumeSize_ = newSize.value(); - } + void setVolumeSize(std::optional newSize) + { + if (newSize.has_value()) + volumeSize_ = newSize.value(); + } - /** + /** * Get the WebXR experience instance to use. * * @returns The WebXR experience instance or `nullptr` if not available such as not in XR mode. */ - std::shared_ptr getWebXRExperience(); + std::shared_ptr getWebXRExperience(); - /** + /** * Select a ray for hit testing. */ - std::optional selectRayForHitTesting(); - - // Events - - typedef std::function SelectEventHandler; - void onSelectStart(SelectEventHandler); - void onSelectEnd(SelectEventHandler); - - private: - void update(uint32_t time, std::shared_ptr frame); - void setupXRSession(); - - private: - TrClientContextPerProcess *clientContext_ = nullptr; - std::shared_ptr glContext_; - std::shared_ptr xrSession_; - client_xr::XRFrameCallback frameCallback_; - math::Size3 volumeSize_ = math::Size3(client_cssom::ScreenWidth, - client_cssom::ScreenHeight, - client_cssom::VolumeDepth); - bool started_ = false; - atomic paused_ = false; - }; -} + std::optional selectRayForHitTesting(); + + // Events + + typedef std::function SelectEventHandler; + void onSelectStart(SelectEventHandler); + void onSelectEnd(SelectEventHandler); + + private: + void update(uint32_t time, std::shared_ptr frame); + void setupXRSession(); + + private: + TrClientContextPerProcess *clientContext_ = nullptr; + std::shared_ptr glContext_; + std::shared_ptr xrSession_; + client_xr::XRFrameCallback frameCallback_; + math::Size3 volumeSize_ = math::Size3(client_cssom::ScreenWidth, + client_cssom::ScreenHeight, + client_cssom::VolumeDepth); + bool started_ = false; + atomic paused_ = false; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/shader_base.cpp b/src/client/builtin_scene/shader_base.cpp index f0bf13270..a8ea864d1 100644 --- a/src/client/builtin_scene/shader_base.cpp +++ b/src/client/builtin_scene/shader_base.cpp @@ -3,56 +3,59 @@ #include "./material_base.hpp" #include "./shaders_store.gen.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace client_graphics; - - string ShaderPreprocessor::PreprocessSource(string source, const vector &defines, WebGLShaderType shaderType) + namespace builtin_scene { - // The flag to check if the shader has multiview - bool shouldMultiview = false; + using namespace std; + using namespace client_graphics; - // Add the defines - for (const auto &define : defines) + string ShaderPreprocessor::PreprocessSource(string source, const vector &defines, WebGLShaderType shaderType) { - source = "#define " + define + "\n" + source; - if (define == Material::kRequireMultiviewDefine) - shouldMultiview = true; - } + // The flag to check if the shader has multiview + bool shouldMultiview = false; - if (shaderType == WebGLShaderType::kVertex) - { - if (shouldMultiview) + // Add the defines + for (const auto &define : defines) { - // Add the multiview extension - std::vector prependLines = { - "#extension GL_OVR_multiview2 : require", - "layout(num_views = 2) in;", - "#define VIEW_ID gl_ViewID_OVR"}; - source = ConcatSource(source, prependLines); + source = "#define " + define + "\n" + source; + if (define == Material::kRequireMultiviewDefine) + shouldMultiview = true; } - } - // Prepend WebGL 2.0 version: #version 300 es - source = "#version 300 es\n" + source; + if (shaderType == WebGLShaderType::kVertex) + { + if (shouldMultiview) + { + // Add the multiview extension + std::vector prependLines = { + "#extension GL_OVR_multiview2 : require", + "layout(num_views = 2) in;", + "#define VIEW_ID gl_ViewID_OVR"}; + source = ConcatSource(source, prependLines); + } + } - // Return the preprocessed source - return source; - } + // Prepend WebGL 2.0 version: #version 300 es + source = "#version 300 es\n" + source; - string ShaderPreprocessor::ConcatSource(const string &source, const vector &lines) - { - string r; - for (const auto &line : lines) - r += line + "\n"; - return r + source; - } + // Return the preprocessed source + return source; + } - ShaderSource ShaderRef::shader(const std::vector &defines) const - { - if (shaders::SHADERS_STORE.find(name) == shaders::SHADERS_STORE.end()) - throw std::runtime_error("The shader is not found: " + name); - return ShaderSource(name, shaders::SHADERS_STORE.at(name), defines, type); + string ShaderPreprocessor::ConcatSource(const string &source, const vector &lines) + { + string r; + for (const auto &line : lines) + r += line + "\n"; + return r + source; + } + + ShaderSource ShaderRef::shader(const std::vector &defines) const + { + if (shaders::SHADERS_STORE.find(name) == shaders::SHADERS_STORE.end()) + throw std::runtime_error("The shader is not found: " + name); + return ShaderSource(name, shaders::SHADERS_STORE.at(name), defines, type); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/shader_base.hpp b/src/client/builtin_scene/shader_base.hpp index d8c43333b..ae4041790 100644 --- a/src/client/builtin_scene/shader_base.hpp +++ b/src/client/builtin_scene/shader_base.hpp @@ -4,17 +4,19 @@ #include #include -namespace builtin_scene +namespace endor { - using ShaderType = client_graphics::WebGLShaderType; + namespace builtin_scene + { + using ShaderType = client_graphics::WebGLShaderType; - /** + /** * The preprocessor for shaders, such as adding #version, #extension, etc. */ - class ShaderPreprocessor - { - public: - /** + class ShaderPreprocessor + { + public: + /** * Preprocess the source code of a shader. * * @param source The source code of the shader. @@ -22,74 +24,75 @@ namespace builtin_scene * @param shaderType The type of the shader, such as vertex or fragment shader. * @return The preprocessed source code of the shader. */ - static std::string PreprocessSource(std::string source, - const std::vector &defines, - client_graphics::WebGLShaderType shaderType); + static std::string PreprocessSource(std::string source, + const std::vector &defines, + client_graphics::WebGLShaderType shaderType); - /** + /** * Concatenate the source code with the given lines. * * @param source The source code to concatenate. * @param lines The list of lines to concatenate. * @return The concatenated source code. */ - static std::string ConcatSource(const std::string &source, const std::vector &lines); + static std::string ConcatSource(const std::string &source, const std::vector &lines); - public: - ShaderPreprocessor() = delete; - }; + public: + ShaderPreprocessor() = delete; + }; - /** + /** * The `ShaderSource` class represents a shader source code. */ - class ShaderSource - { - public: - ShaderSource(std::string name, std::string source, const std::vector &defines, client_graphics::WebGLShaderType shaderType) - : name(name) - , source(ShaderPreprocessor::PreprocessSource(source, defines, shaderType)) + class ShaderSource { - } + public: + ShaderSource(std::string name, std::string source, const std::vector &defines, client_graphics::WebGLShaderType shaderType) + : name(name) + , source(ShaderPreprocessor::PreprocessSource(source, defines, shaderType)) + { + } - public: - std::string name; - std::string source; - }; + public: + std::string name; + std::string source; + }; - /** + /** * A reference to a `ShaderSource` object. */ - class ShaderRef - { - public: - /** + class ShaderRef + { + public: + /** * Construct a `ShaderRef` object with the given name. * * @param name The relative path of the builtin shader, such as "materials/color.frag". */ - ShaderRef(client_graphics::WebGLShaderType type, std::string name) - : type(type) - , name(name) - { - } + ShaderRef(client_graphics::WebGLShaderType type, std::string name) + : type(type) + , name(name) + { + } - public: - /** + public: + /** * Get the `ShaderSource` object of the shader. * * @param defines The list of defines to add to the shader. * @returns The `ShaderSource` object. */ - ShaderSource shader(const std::vector &defines = {}) const; + ShaderSource shader(const std::vector &defines = {}) const; - public: - /** + public: + /** * The shader type, such as vertex or fragment shader. */ - client_graphics::WebGLShaderType type; - /** + client_graphics::WebGLShaderType type; + /** * The relative path of the builtin shader, such as "materials/color.frag". */ - std::string name; - }; -} + std::string name; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/text.hpp b/src/client/builtin_scene/text.hpp index 5f60979dd..040448def 100644 --- a/src/client/builtin_scene/text.hpp +++ b/src/client/builtin_scene/text.hpp @@ -3,17 +3,20 @@ #include #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - class Text2d : public ecs::Component + namespace builtin_scene { - public: - Text2d(std::string content) - : content(content) + class Text2d : public ecs::Component { - } + public: + Text2d(std::string content) + : content(content) + { + } - public: - std::string content; - }; -} + public: + std::string content; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/text/sdf/generator.cpp b/src/client/builtin_scene/text/sdf/generator.cpp index 171de09b0..b38744b36 100644 --- a/src/client/builtin_scene/text/sdf/generator.cpp +++ b/src/client/builtin_scene/text/sdf/generator.cpp @@ -3,164 +3,167 @@ #include #include "./generator.hpp" -namespace builtin_scene::text::sdf +namespace endor { - using namespace std; - - static constexpr double INF = 1e20; - - SDFGenerator::SDFGenerator(const SDFParams ¶ms) - : params_(params) - { - } - - bool SDFGenerator::generateOnPixels(unsigned char *pixels, int width, int height) + namespace builtin_scene::text::sdf { - if (!pixels || width <= 0 || height <= 0) - return false; + using namespace std; - int len = width * height; + static constexpr double INF = 1e20; - // Create temporary grids for distance transform - vector gridOuter(len, INF); - vector gridInner(len, 0.0); + SDFGenerator::SDFGenerator(const SDFParams ¶ms) + : params_(params) + { + } - // Initialize grids based on alpha values - for (int y = 0; y < height; y++) + bool SDFGenerator::generateOnPixels(unsigned char *pixels, int width, int height) { - for (int x = 0; x < width; x++) - { - const int pixelIndex = (y * width + x) * 4; - const double alpha = pixels[pixelIndex + 3] / 255.0; // Normalize the alpha to [0,1] - if (alpha == 0.0) - continue; // Skip empty pixels + if (!pixels || width <= 0 || height <= 0) + return false; - const int j = y * width + x; + int len = width * height; - if (alpha == 1.0) - { - // Fully drawn pixels - gridOuter[j] = 0.0; - gridInner[j] = INF; - } - else + // Create temporary grids for distance transform + vector gridOuter(len, INF); + vector gridInner(len, 0.0); + + // Initialize grids based on alpha values + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - // Anti-aliased pixels - const double d = 0.5 - alpha; - gridOuter[j] = d > 0 ? d * d : 0.0; - gridInner[j] = d < 0 ? d * d : 0.0; + const int pixelIndex = (y * width + x) * 4; + const double alpha = pixels[pixelIndex + 3] / 255.0; // Normalize the alpha to [0,1] + if (alpha == 0.0) + continue; // Skip empty pixels + + const int j = y * width + x; + + if (alpha == 1.0) + { + // Fully drawn pixels + gridOuter[j] = 0.0; + gridInner[j] = INF; + } + else + { + // Anti-aliased pixels + const double d = 0.5 - alpha; + gridOuter[j] = d > 0 ? d * d : 0.0; + gridInner[j] = d < 0 ? d * d : 0.0; + } } } - } - - // Create temporary arrays for EDT algorithm - vector f(max(width, height)); - vector z(max(width, height) + 1); - vector v(max(width, height)); - - // Apply Euclidean Distance Transform - edt(gridOuter, 0, 0, width, height, width, f, v, z); - edt(gridInner, 0, 0, width, height, width, f, v, z); - // Write SDF and update pixel alpha channel - return writeFromGrids(pixels, width, height, gridOuter, gridInner); - } - - bool SDFGenerator::writeFromGrids(unsigned char *pixels, - int width, - int height, - const vector &gridOuter, - const vector &gridInner) - { - if (!pixels || width <= 0 || height <= 0) - return false; + // Create temporary arrays for EDT algorithm + vector f(max(width, height)); + vector z(max(width, height) + 1); + vector v(max(width, height)); - int len = width * height; + // Apply Euclidean Distance Transform + edt(gridOuter, 0, 0, width, height, width, f, v, z); + edt(gridInner, 0, 0, width, height, width, f, v, z); - if (gridOuter.size() != static_cast(len) || - gridInner.size() != static_cast(len)) - return false; + // Write SDF and update pixel alpha channel + return writeFromGrids(pixels, width, height, gridOuter, gridInner); + } - // Update only the alpha channel, preserving RGB channels - for (int i = 0; i < len; i++) + bool SDFGenerator::writeFromGrids(unsigned char *pixels, + int width, + int height, + const vector &gridOuter, + const vector &gridInner) { - // Combine outer and inner distance fields to create signed distance - const double d = sqrt(gridOuter[i]) - sqrt(gridInner[i]); + if (!pixels || width <= 0 || height <= 0) + return false; - // Convert to SDF value in [0, 255] range - const double sdfValue = 255.0 - 255.0 * (d / params_.radius + params_.cutoff); - const uint8_t sdfByte = static_cast(round(clamp(sdfValue, 0.0, 255.0))); + int len = width * height; - // Update only alpha channel (4th component in RGBA) - const int pixelIndex = i * 4; - pixels[pixelIndex + 3] = sdfByte; - // RGB channels (pixels[pixelIndex], pixels[pixelIndex + 1], pixels[pixelIndex + 2]) remain unchanged - } + if (gridOuter.size() != static_cast(len) || + gridInner.size() != static_cast(len)) + return false; - return true; - } + // Update only the alpha channel, preserving RGB channels + for (int i = 0; i < len; i++) + { + // Combine outer and inner distance fields to create signed distance + const double d = sqrt(gridOuter[i]) - sqrt(gridInner[i]); - void SDFGenerator::edt(vector &data, - int x0, - int y0, - int width, - int height, - int gridSize, - vector &f, - vector &v, - vector &z) - { - // Transform along columns - for (int x = x0; x < x0 + width; x++) - { - edt1d(data, y0 * gridSize + x, gridSize, height, f, v, z); - } + // Convert to SDF value in [0, 255] range + const double sdfValue = 255.0 - 255.0 * (d / params_.radius + params_.cutoff); + const uint8_t sdfByte = static_cast(round(clamp(sdfValue, 0.0, 255.0))); - // Transform along rows - for (int y = y0; y < y0 + height; y++) - { - edt1d(data, y * gridSize + x0, 1, width, f, v, z); + // Update only alpha channel (4th component in RGBA) + const int pixelIndex = i * 4; + pixels[pixelIndex + 3] = sdfByte; + // RGB channels (pixels[pixelIndex], pixels[pixelIndex + 1], pixels[pixelIndex + 2]) remain unchanged + } + + return true; } - } - void SDFGenerator::edt1d(vector &grid, - int offset, - int stride, - int length, + void SDFGenerator::edt(vector &data, + int x0, + int y0, + int width, + int height, + int gridSize, vector &f, vector &v, vector &z) - { - v[0] = 0; - z[0] = -INF; - z[1] = INF; - f[0] = grid[offset]; - - for (int q = 1, k = 0; q < length; q++) { - f[q] = grid[offset + q * stride]; - const double q2 = q * q; - double s; - - do + // Transform along columns + for (int x = x0; x < x0 + width; x++) { - const int r = v[k]; - s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2.0; - } while (s <= z[k] && --k > -1); + edt1d(data, y0 * gridSize + x, gridSize, height, f, v, z); + } - k++; - v[k] = q; - z[k] = s; - z[k + 1] = INF; + // Transform along rows + for (int y = y0; y < y0 + height; y++) + { + edt1d(data, y * gridSize + x0, 1, width, f, v, z); + } } - for (int q = 0, k = 0; q < length; q++) + void SDFGenerator::edt1d(vector &grid, + int offset, + int stride, + int length, + vector &f, + vector &v, + vector &z) { - while (z[k + 1] < q) + v[0] = 0; + z[0] = -INF; + z[1] = INF; + f[0] = grid[offset]; + + for (int q = 1, k = 0; q < length; q++) + { + f[q] = grid[offset + q * stride]; + const double q2 = q * q; + double s; + + do + { + const int r = v[k]; + s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2.0; + } while (s <= z[k] && --k > -1); + k++; - const int r = v[k]; - const double qr = q - r; - grid[offset + q * stride] = f[r] + qr * qr; + v[k] = q; + z[k] = s; + z[k + 1] = INF; + } + + for (int q = 0, k = 0; q < length; q++) + { + while (z[k + 1] < q) + k++; + const int r = v[k]; + const double qr = q - r; + grid[offset + q * stride] = f[r] + qr * qr; + } } } -} +} // namespace endor diff --git a/src/client/builtin_scene/text/sdf/generator.hpp b/src/client/builtin_scene/text/sdf/generator.hpp index 67e2d2b78..a7c6efed2 100644 --- a/src/client/builtin_scene/text/sdf/generator.hpp +++ b/src/client/builtin_scene/text/sdf/generator.hpp @@ -4,25 +4,27 @@ #include #include -namespace builtin_scene::text::sdf +namespace endor { - /** + namespace builtin_scene::text::sdf + { + /** * Configuration parameters for SDF generation from bitmaps */ - struct SDFParams - { - int radius = 8; // Distance field radius - float cutoff = 0.25f; // Alpha cutoff for edge detection - - SDFParams() = default; - SDFParams(int radius, float cutoff = 0.25f) - : radius(radius) - , cutoff(cutoff) + struct SDFParams { - } - }; + int radius = 8; // Distance field radius + float cutoff = 0.25f; // Alpha cutoff for edge detection - /** + SDFParams() = default; + SDFParams(int radius, float cutoff = 0.25f) + : radius(radius) + , cutoff(cutoff) + { + } + }; + + /** * SDFGenerator - CPU-based SDF generator for raw pixel data * * This class generates signed distance field textures from raw pixel data using a CPU-based implementation inspired @@ -31,19 +33,19 @@ namespace builtin_scene::text::sdf * Reference: https://github.com/mapbox/tiny-sdf * Original implementation by Mapbox under BSD 2-Clause License */ - class SDFGenerator - { - public: - explicit SDFGenerator(const SDFParams ¶ms = SDFParams()); - ~SDFGenerator() = default; + class SDFGenerator + { + public: + explicit SDFGenerator(const SDFParams ¶ms = SDFParams()); + ~SDFGenerator() = default; - // Non-copyable but movable - SDFGenerator(const SDFGenerator &) = delete; - SDFGenerator &operator=(const SDFGenerator &) = delete; - SDFGenerator(SDFGenerator &&) = default; - SDFGenerator &operator=(SDFGenerator &&) = default; + // Non-copyable but movable + SDFGenerator(const SDFGenerator &) = delete; + SDFGenerator &operator=(const SDFGenerator &) = delete; + SDFGenerator(SDFGenerator &&) = default; + SDFGenerator &operator=(SDFGenerator &&) = default; - /** + /** * Generate SDF from raw pixel data and update alpha channel in place * Only modifies the alpha channel, preserving RGB channels. * @@ -52,51 +54,52 @@ namespace builtin_scene::text::sdf * @param height Height of the image in pixels * @return true if SDF generation succeeded, false otherwise */ - bool generateOnPixels(unsigned char *pixels, int width, int height); + bool generateOnPixels(unsigned char *pixels, int width, int height); - /** + /** * Get current SDF parameters */ - const SDFParams &getParams() const - { - return params_; - } + const SDFParams &getParams() const + { + return params_; + } - /** + /** * Update SDF parameters */ - void setParams(const SDFParams ¶ms) - { - params_ = params; - } + void setParams(const SDFParams ¶ms) + { + params_ = params; + } - private: - SDFParams params_; + private: + SDFParams params_; - // Internal methods for SDF generation using EDT algorithm - bool writeFromGrids(unsigned char *pixels, - int width, - int height, - const std::vector &gridOuter, - const std::vector &gridInner); + // Internal methods for SDF generation using EDT algorithm + bool writeFromGrids(unsigned char *pixels, + int width, + int height, + const std::vector &gridOuter, + const std::vector &gridInner); - // Euclidean Distance Transform (EDT) algorithm implementation - // Based on "Distance Transforms of Sampled Functions" by Felzenszwalb & Huttenlocher - void edt(std::vector &data, - int x0, - int y0, - int width, - int height, - int gridSize, - std::vector &f, - std::vector &v, - std::vector &z); - void edt1d(std::vector &grid, - int offset, - int stride, - int length, + // Euclidean Distance Transform (EDT) algorithm implementation + // Based on "Distance Transforms of Sampled Functions" by Felzenszwalb & Huttenlocher + void edt(std::vector &data, + int x0, + int y0, + int width, + int height, + int gridSize, std::vector &f, std::vector &v, std::vector &z); - }; -} + void edt1d(std::vector &grid, + int offset, + int stride, + int length, + std::vector &f, + std::vector &v, + std::vector &z); + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/texture_altas.cpp b/src/client/builtin_scene/texture_altas.cpp index c9c6ede95..0f53e924b 100644 --- a/src/client/builtin_scene/texture_altas.cpp +++ b/src/client/builtin_scene/texture_altas.cpp @@ -1,145 +1,148 @@ #include "./texture_altas.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace glm; - using namespace client_graphics; - - TextureAtlas::TextureAtlas(shared_ptr glContext, WebGLTextureUnit unit, int width, int height) - : glContext_(glContext) - , glTexture_(glContext->createTexture()) - , width_(width) - , height_(height) - , unit_(unit) - , handle_(make_unique(width, height, kMaxLayerCount)) + namespace builtin_scene { - assert(glContext != nullptr); - assert(unit_ >= WebGLTextureUnit::kTexture0 && - unit_ <= WebGLTextureUnit::kTexture31); - - glContext->activeTexture(unit_); - glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, glTexture_); + using namespace std; + using namespace glm; + using namespace client_graphics; + + TextureAtlas::TextureAtlas(shared_ptr glContext, WebGLTextureUnit unit, int width, int height) + : glContext_(glContext) + , glTexture_(glContext->createTexture()) + , width_(width) + , height_(height) + , unit_(unit) + , handle_(make_unique(width, height, kMaxLayerCount)) { - glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, - WebGLTextureParameterName::kTextureMinFilter, - WEBGL_LINEAR_MIPMAP_LINEAR); - glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, - WebGLTextureParameterName::kTextureMagFilter, - WEBGL_LINEAR); - glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, - WebGLTextureParameterName::kTextureWrapS, - WEBGL_CLAMP_TO_EDGE); - glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, - WebGLTextureParameterName::kTextureWrapT, - WEBGL_CLAMP_TO_EDGE); - - if (glContext->supportsExtension("EXT_texture_filter_anisotropic")) + assert(glContext != nullptr); + assert(unit_ >= WebGLTextureUnit::kTexture0 && + unit_ <= WebGLTextureUnit::kTexture31); + + glContext->activeTexture(unit_); + glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, glTexture_); { - glContext->texParameterf(WebGLTextureTarget::kTexture2DArray, - WebGLTextureParameterName::kTextureMaxAnisotropyEXT, - glContext->maxTextureMaxAnisotropy); + glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, + WebGLTextureParameterName::kTextureMinFilter, + WEBGL_LINEAR_MIPMAP_LINEAR); + glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, + WebGLTextureParameterName::kTextureMagFilter, + WEBGL_LINEAR); + glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, + WebGLTextureParameterName::kTextureWrapS, + WEBGL_CLAMP_TO_EDGE); + glContext->texParameteri(WebGLTextureTarget::kTexture2DArray, + WebGLTextureParameterName::kTextureWrapT, + WEBGL_CLAMP_TO_EDGE); + + if (glContext->supportsExtension("EXT_texture_filter_anisotropic")) + { + glContext->texParameterf(WebGLTextureTarget::kTexture2DArray, + WebGLTextureParameterName::kTextureMaxAnisotropyEXT, + glContext->maxTextureMaxAnisotropy); + } + + // Initialize the texture atlas with the default values. + glContext->texStorage3D(WebGLTexture3DTarget::kTexture2DArray, + 1, + WEBGL2_RGBA8, + width, + height, + kMaxLayerCount); + glContext->generateMipmap(WebGLTextureTarget::kTexture2DArray); } - - // Initialize the texture atlas with the default values. - glContext->texStorage3D(WebGLTexture3DTarget::kTexture2DArray, - 1, - WEBGL2_RGBA8, - width, - height, - kMaxLayerCount); - glContext->generateMipmap(WebGLTextureTarget::kTexture2DArray); + glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, nullptr); } - glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, nullptr); - } - TextureAtlas::~TextureAtlas() - { - auto glContext = glContext_.lock(); - if (glContext != nullptr) - glContext->deleteTexture(glTexture_); - } + TextureAtlas::~TextureAtlas() + { + auto glContext = glContext_.lock(); + if (glContext != nullptr) + glContext->deleteTexture(glTexture_); + } - shared_ptr TextureAtlas::addTexture(int width, int height, bool autoDownscale) - { - if (autoDownscale) + shared_ptr TextureAtlas::addTexture(int width, int height, bool autoDownscale) { - const float widthRatio = static_cast(width_) / width; - const float heightRatio = static_cast(height_) / height; - const float scale = fmin(widthRatio, heightRatio); - if (scale < 1.0f) + if (autoDownscale) { - int scaledWidth = static_cast(floor(width * scale)); - int scaledHeight = static_cast(floor(height * scale)); - - scaledWidth = scaledWidth > width_ ? width_ : scaledWidth; - scaledHeight = scaledHeight > height_ ? height_ : scaledHeight; - - if (scaledWidth <= 0 || scaledHeight <= 0) - throw runtime_error("Invalid texture size"); - - width = scaledWidth; - height = scaledHeight; + const float widthRatio = static_cast(width_) / width; + const float heightRatio = static_cast(height_) / height; + const float scale = fmin(widthRatio, heightRatio); + if (scale < 1.0f) + { + int scaledWidth = static_cast(floor(width * scale)); + int scaledHeight = static_cast(floor(height * scale)); + + scaledWidth = scaledWidth > width_ ? width_ : scaledWidth; + scaledHeight = scaledHeight > height_ ? height_ : scaledHeight; + + if (scaledWidth <= 0 || scaledHeight <= 0) + throw runtime_error("Invalid texture size"); + + width = scaledWidth; + height = scaledHeight; + } } + + if (width > width_ || height > height_) + throw runtime_error("Invalid texture size"); + return handle_->addTexture(width, height); } - if (width > width_ || height > height_) - throw runtime_error("Invalid texture size"); - return handle_->addTexture(width, height); - } + std::shared_ptr TextureAtlas::resizeTexture(std::shared_ptr texture, int width, int height, bool autoDownscale) + { + assert(texture != nullptr); + if (texture->width == width && + texture->height == height) + return texture; - std::shared_ptr TextureAtlas::resizeTexture(std::shared_ptr texture, int width, int height, bool autoDownscale) - { - assert(texture != nullptr); - if (texture->width == width && - texture->height == height) - return texture; + removeTexture(*texture); + return addTexture(width, height, autoDownscale); + } - removeTexture(*texture); - return addTexture(width, height, autoDownscale); - } + void TextureAtlas::removeTexture(const Texture &texture) + { + handle_->removeTexture(texture); + } - void TextureAtlas::removeTexture(const Texture &texture) - { - handle_->removeTexture(texture); - } + void TextureAtlas::updateTexture(const Texture &texture, const unsigned char *pixels, WebGLTextureFormat format, WebGLPixelType pixelType) + { + auto glContext = glContext_.lock(); + assert(glContext != nullptr); + + glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, glTexture_); + // Update the texture with the new pixels or the default values. + glContext->texSubImage3D(WebGLTexture3DTarget::kTexture2DArray, + 0, + texture.x, + texture.y, + texture.layer, + texture.width, + texture.height, + 1, + format, + pixelType, + const_cast(pixels)); - void TextureAtlas::updateTexture(const Texture &texture, const unsigned char *pixels, WebGLTextureFormat format, WebGLPixelType pixelType) - { - auto glContext = glContext_.lock(); - assert(glContext != nullptr); - - glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, glTexture_); - // Update the texture with the new pixels or the default values. - glContext->texSubImage3D(WebGLTexture3DTarget::kTexture2DArray, - 0, - texture.x, - texture.y, - texture.layer, - texture.width, - texture.height, - 1, - format, - pixelType, - const_cast(pixels)); - - glContext->generateMipmap(WebGLTextureTarget::kTexture2DArray); - glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, nullptr); - } + glContext->generateMipmap(WebGLTextureTarget::kTexture2DArray); + glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, nullptr); + } - void TextureAtlas::onBeforeDraw() - { - auto glContext = glContext_.lock(); - assert(glContext != nullptr); - glContext->activeTexture(unit_); - glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, glTexture_); - } + void TextureAtlas::onBeforeDraw() + { + auto glContext = glContext_.lock(); + assert(glContext != nullptr); + glContext->activeTexture(unit_); + glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, glTexture_); + } - void TextureAtlas::onAfterDraw() - { - auto glContext = glContext_.lock(); - assert(glContext != nullptr); - glContext->activeTexture(unit_); - glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, nullptr); + void TextureAtlas::onAfterDraw() + { + auto glContext = glContext_.lock(); + assert(glContext != nullptr); + glContext->activeTexture(unit_); + glContext->bindTexture(WebGLTextureTarget::kTexture2DArray, nullptr); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/texture_altas.hpp b/src/client/builtin_scene/texture_altas.hpp index b5a4fc804..fa90cb799 100644 --- a/src/client/builtin_scene/texture_altas.hpp +++ b/src/client/builtin_scene/texture_altas.hpp @@ -8,43 +8,46 @@ #include #include -namespace builtin_scene +namespace endor { - using Texture = crates::texture_atlas::TextureLayout; - class TextureAtlas + namespace builtin_scene { - static constexpr int kMaxLayerCount = 4; - static constexpr int kDefaultSize = 4096; + using Texture = crates::texture_atlas::TextureLayout; + class TextureAtlas + { + static constexpr int kMaxLayerCount = 4; + static constexpr int kDefaultSize = 4096; - public: - TextureAtlas(std::shared_ptr glContext, - client_graphics::WebGLTextureUnit unit = client_graphics::WebGLTextureUnit::kTexture0, - int width = kDefaultSize, - int height = kDefaultSize); - ~TextureAtlas(); + public: + TextureAtlas(std::shared_ptr glContext, + client_graphics::WebGLTextureUnit unit = client_graphics::WebGLTextureUnit::kTexture0, + int width = kDefaultSize, + int height = kDefaultSize); + ~TextureAtlas(); - public: - std::shared_ptr addTexture(int width, int height, bool autoDownscale = false); - std::shared_ptr resizeTexture(std::shared_ptr texture, - int width, - int height, - bool autoDownscale = false); - void removeTexture(const Texture &texture); - void updateTexture(const Texture &texture, - const unsigned char *pixels, - client_graphics::WebGLTextureFormat format = client_graphics::WebGLTextureFormat::kRGBA, - client_graphics::WebGLPixelType pixelType = client_graphics::WebGLPixelType::kUnsignedByte); + public: + std::shared_ptr addTexture(int width, int height, bool autoDownscale = false); + std::shared_ptr resizeTexture(std::shared_ptr texture, + int width, + int height, + bool autoDownscale = false); + void removeTexture(const Texture &texture); + void updateTexture(const Texture &texture, + const unsigned char *pixels, + client_graphics::WebGLTextureFormat format = client_graphics::WebGLTextureFormat::kRGBA, + client_graphics::WebGLPixelType pixelType = client_graphics::WebGLPixelType::kUnsignedByte); - public: - void onBeforeDraw(); - void onAfterDraw(); + public: + void onBeforeDraw(); + void onAfterDraw(); - private: - int width_; - int height_; - client_graphics::WebGLTextureUnit unit_; - std::weak_ptr glContext_; - std::shared_ptr glTexture_; - std::unique_ptr handle_; - }; -} + private: + int width_; + int height_; + client_graphics::WebGLTextureUnit unit_; + std::weak_ptr glContext_; + std::shared_ptr glTexture_; + std::unique_ptr handle_; + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/timer.hpp b/src/client/builtin_scene/timer.hpp index 00d6a0c1a..5d9cfff07 100644 --- a/src/client/builtin_scene/timer.hpp +++ b/src/client/builtin_scene/timer.hpp @@ -3,63 +3,66 @@ #include #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - /** + namespace builtin_scene + { + /** * Get the current timestamp in milliseconds. * * @returns a timestamp in milliseconds. */ - inline long long timestampInMilliseconds() - { - return std::chrono::system_clock::now().time_since_epoch().count() / 1000; - } - - class Timer : public ecs::Resource - { - friend class TimerSystem; - - public: - Timer(uint32_t interval) - : interval_(interval) - , timestamp_(timestampInMilliseconds()) + inline long long timestampInMilliseconds() { + return std::chrono::system_clock::now().time_since_epoch().count() / 1000; } - public: - /** - * @returns the timestamp in milliseconds. - */ - long long timestamp() + class Timer : public ecs::Resource { - return timestamp_; - } + friend class TimerSystem; + + public: + Timer(uint32_t interval) + : interval_(interval) + , timestamp_(timestampInMilliseconds()) + { + } - private: - uint32_t interval_; - long long timestamp_; - }; + public: + /** + * @returns the timestamp in milliseconds. + */ + long long timestamp() + { + return timestamp_; + } - class TimerSystem : public ecs::System - { - public: - using ecs::System::System; + private: + uint32_t interval_; + long long timestamp_; + }; - public: - const std::string name() const override + class TimerSystem : public ecs::System { - return "TimerSystem"; - } - void onExecute() override - { - auto timer = getResource(); - if (timer) + public: + using ecs::System::System; + + public: + const std::string name() const override { - auto nowTimestamp = timestampInMilliseconds(); - auto duration = nowTimestamp - timer->timestamp(); - if (duration >= timer->interval_) - timer->timestamp_ = nowTimestamp; + return "TimerSystem"; } - } - }; -} + void onExecute() override + { + auto timer = getResource(); + if (timer) + { + auto nowTimestamp = timestampInMilliseconds(); + auto duration = nowTimestamp - timer->timestamp(); + if (duration >= timer->interval_) + timer->timestamp_ = nowTimestamp; + } + } + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/transform.hpp b/src/client/builtin_scene/transform.hpp index 5b0de4964..b995f2788 100644 --- a/src/client/builtin_scene/transform.hpp +++ b/src/client/builtin_scene/transform.hpp @@ -5,73 +5,75 @@ #include #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - /** + namespace builtin_scene + { + /** * This component is a representation of a transformation in space. */ - class Transform final : public ecs::Component - { - using ecs::Component::Component; + class Transform final : public ecs::Component + { + using ecs::Component::Component; - public: - /** + public: + /** * The identity transform. */ - static const Transform Identity() - { - return Transform(); - } - /** + static const Transform Identity() + { + return Transform(); + } + /** * Creates a new transform from the given translation. * * @param translation The translation. * @returns The new transform. */ - static inline Transform FromTranslation(math::Vec3 translation) - { - Transform transform; - transform.translation_ = translation; - assert(transform.isDirty()); - return transform; - } - /** + static inline Transform FromTranslation(math::Vec3 translation) + { + Transform transform; + transform.translation_ = translation; + assert(transform.isDirty()); + return transform; + } + /** * Creates a new transform from the given rotation. * * @param rotation The rotation. * @returns The new transform. */ - static inline Transform FromRotation(math::Quat rotation) - { - Transform transform; - transform.rotation_ = rotation; - assert(transform.isDirty()); - return transform; - } - /** + static inline Transform FromRotation(math::Quat rotation) + { + Transform transform; + transform.rotation_ = rotation; + assert(transform.isDirty()); + return transform; + } + /** * Creates a new transform from the given scale. * * @param scale The scale. * @returns The new transform. */ - static inline Transform FromScale(math::Vec3 scale) - { - Transform transform; - transform.scale_ = scale; - assert(transform.isDirty()); - return transform; - } - /** + static inline Transform FromScale(math::Vec3 scale) + { + Transform transform; + transform.scale_ = scale; + assert(transform.isDirty()); + return transform; + } + /** * Creates a new transform from the given scale size, which is the same for x, y, and z. * * @param scale The scale. * @returns The new transform. */ - static inline Transform FromScale(float scaleSize) - { - return FromScale(math::Vec3(scaleSize, scaleSize, scaleSize)); - } - /** + static inline Transform FromScale(float scaleSize) + { + return FromScale(math::Vec3(scaleSize, scaleSize, scaleSize)); + } + /** * Creates a new transform from the given translation(x, y, z). * * @param x The x component of the translation. @@ -79,179 +81,179 @@ namespace builtin_scene * @param z The z component of the translation. * @returns The new transform. */ - static inline Transform FromXYZ(float x, float y, float z) - { - return FromTranslation(math::Vec3(x, y, z)); - } + static inline Transform FromXYZ(float x, float y, float z) + { + return FromTranslation(math::Vec3(x, y, z)); + } - public: - /** + public: + /** * @returns If the transform is dirty, meaning it needs to be updated. */ - inline bool isDirty() const - { - if (postTransform_ != nullptr && postTransform_->isDirty()) - return true; - return isDirty_; - } - /** + inline bool isDirty() const + { + if (postTransform_ != nullptr && postTransform_->isDirty()) + return true; + return isDirty_; + } + /** * Mark the transform as dirty or clean, meaning it needs to be updated. */ - inline void setDirty(bool b = true) const - { - isDirty_ = b; - } - /** + inline void setDirty(bool b = true) const + { + isDirty_ = b; + } + /** * The translation of the transform. */ - inline math::Vec3 translation() const - { - return translation_; - } - /** + inline math::Vec3 translation() const + { + return translation_; + } + /** * The rotation of the transform. */ - inline math::Quat rotation() const - { - return rotation_; - } - /** + inline math::Quat rotation() const + { + return rotation_; + } + /** * The scale of the transform. */ - inline math::Vec3 scale() const - { - return scale_; - } - /** + inline math::Vec3 scale() const + { + return scale_; + } + /** * Get the matrix representation of the transform without clearing the dirty flag. * * @returns The matrix representation of the transform. */ - inline const glm::mat4 &matrix() const - { - if (isDirty_ == true) + inline const glm::mat4 &matrix() const { - lastMatrix_ = glm::translate(glm::mat4(1.0f), translation_) * - glm::mat4_cast(rotation_) * - glm::scale(glm::mat4(1.0f), scale_); - setDirty(false); + if (isDirty_ == true) + { + lastMatrix_ = glm::translate(glm::mat4(1.0f), translation_) * + glm::mat4_cast(rotation_) * + glm::scale(glm::mat4(1.0f), scale_); + setDirty(false); + } + return lastMatrix_; } - return lastMatrix_; - } - /** + /** * Get the matrix representation of the transform with the post transform and clear the dirty flag. */ - inline glm::mat4 matrixWithPostTransform() const - { - glm::mat4 mat = matrix(); - if (postTransform_ != nullptr) - mat = mat * postTransform_->matrix(); - return mat; - } - /** + inline glm::mat4 matrixWithPostTransform() const + { + glm::mat4 mat = matrix(); + if (postTransform_ != nullptr) + mat = mat * postTransform_->matrix(); + return mat; + } + /** * Set the matrix representation of the transform. * * @param mat The matrix representation of the transform. */ - inline void setMatrix(glm::mat4 mat) - { - if (mat == lastMatrix_) - return; - lastMatrix_ = mat; - translation_ = math::Vec3(mat[3]); - rotation_ = math::Quat(mat); - scale_ = math::Vec3(mat[0][0], mat[1][1], mat[2][2]); - setDirty(false); - } - /** + inline void setMatrix(glm::mat4 mat) + { + if (mat == lastMatrix_) + return; + lastMatrix_ = mat; + translation_ = math::Vec3(mat[3]); + rotation_ = math::Quat(mat); + scale_ = math::Vec3(mat[0][0], mat[1][1], mat[2][2]); + setDirty(false); + } + /** * Set the translation. * * @param translation The new translation. */ - inline void setTranslation(math::Vec3 translation) - { - if (translation_ != translation) + inline void setTranslation(math::Vec3 translation) { - translation_ = translation; - setDirty(); + if (translation_ != translation) + { + translation_ = translation; + setDirty(); + } } - } - /** + /** * Set the translation (x, y, z). * * @param x The x component of the translation. * @param y The y component of the translation. * @param z The z component of the translation. */ - inline void setTranslation(float x, float y, float z) - { - setTranslation(math::Vec3(x, y, z)); - } - /** + inline void setTranslation(float x, float y, float z) + { + setTranslation(math::Vec3(x, y, z)); + } + /** * Set the x component of the translation. * * @param x The x component of the translation. */ - inline void setX(float x) - { - setTranslation(x, translation_.y, translation_.z); - } - /** + inline void setX(float x) + { + setTranslation(x, translation_.y, translation_.z); + } + /** * Set the y component of the translation. * * @param y The y component of the translation. */ - inline void setY(float y) - { - setTranslation(translation_.x, y, translation_.z); - } - /** + inline void setY(float y) + { + setTranslation(translation_.x, y, translation_.z); + } + /** * Set the z component of the translation. * * @param z The z component of the translation. */ - inline void setZ(float z) - { - setTranslation(translation_.x, translation_.y, z); - } - /** + inline void setZ(float z) + { + setTranslation(translation_.x, translation_.y, z); + } + /** * Set the rotation. * * @param rotation The new rotation. */ - inline void setRotation(math::Quat rotation) - { - if (rotation_ != rotation) + inline void setRotation(math::Quat rotation) { - rotation_ = rotation; - setDirty(); + if (rotation_ != rotation) + { + rotation_ = rotation; + setDirty(); + } } - } - /** + /** * Set the scale. * * @param scale The new scale. */ - inline void setScale(math::Vec3 scale) - { - if (scale_ != scale) + inline void setScale(math::Vec3 scale) { - scale_ = scale; - setDirty(); + if (scale_ != scale) + { + scale_ = scale; + setDirty(); + } } - } - /** + /** * Get a new transform with a given translation. * * @param translation The new translation. * @returns The new transform. */ - inline Transform withTranslation(math::Vec3 translation) - { - translation_ = translation; - return *this; - } - /** + inline Transform withTranslation(math::Vec3 translation) + { + translation_ = translation; + return *this; + } + /** * Get a new transform with a given translation (x, y, z). * * @param x The x component of the translation. @@ -259,33 +261,33 @@ namespace builtin_scene * @param z The z component of the translation. * @returns The new transform. */ - inline Transform withTranslation(float x, float y, float z) - { - return withTranslation(math::Vec3(x, y, z)); - } - /** + inline Transform withTranslation(float x, float y, float z) + { + return withTranslation(math::Vec3(x, y, z)); + } + /** * Get a new transform with a given rotation. * * @param rotation The new rotation. * @returns The new transform. */ - inline Transform withRotation(math::Quat rotation) - { - rotation_ = rotation; - return *this; - } - /** + inline Transform withRotation(math::Quat rotation) + { + rotation_ = rotation; + return *this; + } + /** * Get a new transform with a given scale. * * @param scale The new scale. * @returns The new transform. */ - inline Transform withScale(math::Vec3 scale) - { - scale_ = scale; - return *this; - } - /** + inline Transform withScale(math::Vec3 scale) + { + scale_ = scale; + return *this; + } + /** * Get a readonly `glm::matrix` reference to represent the accumulated matrix. * * The __Accumulated Matrix__ in `Transform` is to store the accumulated transformation matrix in the hierarchy, for example, when @@ -294,81 +296,82 @@ namespace builtin_scene * * @returns The accumulated matrix. */ - inline const glm::mat4 &accumulatedMatrix() const - { - return accumulatedMatrix_; - } - /** + inline const glm::mat4 &accumulatedMatrix() const + { + return accumulatedMatrix_; + } + /** * Set the accumulated matrix. * * @param mat The accumulated matrix. * @see accumulatedMatrix() to learn more about the accumulated matrix. */ - inline void setAccumulatedMatrix(glm::mat4 mat) - { - accumulatedMatrix_ = mat; - } - /** + inline void setAccumulatedMatrix(glm::mat4 mat) + { + accumulatedMatrix_ = mat; + } + /** * @returns If the post transform is initialized. */ - inline bool hasPostTransform() const - { - return postTransform_ != nullptr; - } - /** + inline bool hasPostTransform() const + { + return postTransform_ != nullptr; + } + /** * @returns The post transform reference */ - const Transform &postTransformRef() const - { - return *postTransform_; - } - /** + const Transform &postTransformRef() const + { + return *postTransform_; + } + /** * Get the post transform reference to update, and initialize if it is not initialized. * * @returns The post transform reference. */ - Transform &getOrInitPostTransform() - { - if (!hasPostTransform()) - postTransform_ = std::make_shared(); - return *postTransform_; - } - /** + Transform &getOrInitPostTransform() + { + if (!hasPostTransform()) + postTransform_ = std::make_shared(); + return *postTransform_; + } + /** * @returns The last computed matrix to upload to the GPU. */ - const glm::mat4 &lastComputedMatrix() const - { - return lastMatrix_; - } - /** + const glm::mat4 &lastComputedMatrix() const + { + return lastMatrix_; + } + /** * Set the computed matrix, this could be used by the renderer to cache the computed matrix for rendering. * * @param mat The computed matrix. */ - void setComputedMatrix(glm::mat4 mat) - { - computedMatrix_ = mat; - } + void setComputedMatrix(glm::mat4 mat) + { + computedMatrix_ = mat; + } - public: - friend std::ostream &operator<<(std::ostream &os, const Transform &transform) - { - os << "Transform("; - os << "translation=" << transform.translation_ << ", "; - os << "rotation=" << transform.rotation_ << ", "; - os << "scale=" << transform.scale_; - os << ")"; - return os; - } + public: + friend std::ostream &operator<<(std::ostream &os, const Transform &transform) + { + os << "Transform("; + os << "translation=" << transform.translation_ << ", "; + os << "rotation=" << transform.rotation_ << ", "; + os << "scale=" << transform.scale_; + os << ")"; + return os; + } - private: - math::Vec3 translation_ = math::Vec3::Identity(); - math::Quat rotation_ = math::Quat::Identity(); - math::Vec3 scale_ = math::Vec3::One(); - mutable bool isDirty_ = true; - mutable glm::mat4 lastMatrix_ = glm::mat4(1.0f); - mutable glm::mat4 accumulatedMatrix_ = glm::mat4(1.0f); - std::optional computedMatrix_ = std::nullopt; // The latest computed matrix to be used for rendering. - std::shared_ptr postTransform_ = nullptr; // The transform to apply after this transform. - }; -} + private: + math::Vec3 translation_ = math::Vec3::Identity(); + math::Quat rotation_ = math::Quat::Identity(); + math::Vec3 scale_ = math::Vec3::One(); + mutable bool isDirty_ = true; + mutable glm::mat4 lastMatrix_ = glm::mat4(1.0f); + mutable glm::mat4 accumulatedMatrix_ = glm::mat4(1.0f); + std::optional computedMatrix_ = std::nullopt; // The latest computed matrix to be used for rendering. + std::shared_ptr postTransform_ = nullptr; // The transform to apply after this transform. + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/web_content.cpp b/src/client/builtin_scene/web_content.cpp index a443dce15..279b705b8 100644 --- a/src/client/builtin_scene/web_content.cpp +++ b/src/client/builtin_scene/web_content.cpp @@ -6,324 +6,327 @@ #include "./web_content.hpp" -namespace builtin_scene +namespace endor { - using namespace std; + namespace builtin_scene + { + using namespace std; - WebContentTextStyle::WebContentTextStyle() - : color(SK_ColorBLACK) + WebContentTextStyle::WebContentTextStyle() + : color(SK_ColorBLACK) #ifdef TR_CLIENT_WEB_CONTENT_DEBUG_TEXT - , backgroundColor(SK_ColorGRAY) + , backgroundColor(SK_ColorGRAY) #else - , backgroundColor(SK_ColorTRANSPARENT) + , backgroundColor(SK_ColorTRANSPARENT) #endif - , decoration(0) - , decorationThickness(0.0f) - , decorationColor(SK_ColorBLACK) - , fontStyle({SkFontStyle::kUpright_Slant, SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width}) + , decoration(0) + , decorationThickness(0.0f) + , decorationColor(SK_ColorBLACK) + , fontStyle({SkFontStyle::kUpright_Slant, SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width}) #ifdef __APPLE__ - , fontFamilies({SkString("PingFang SC"), SkString("sans-serif")}) + , fontFamilies({SkString("PingFang SC"), SkString("sans-serif")}) #elif __ANDROID__ - , fontFamilies({SkString("Noto Sans"), SkString("sans-serif")}) + , fontFamilies({SkString("Noto Sans"), SkString("sans-serif")}) #else - , fontFamilies({SkString("sans-serif")}) + , fontFamilies({SkString("sans-serif")}) #endif - , fontSize(20.0f) - , letterSpacing(std::nullopt) - , wordSpacing(std::nullopt) - { - } - - WebContentStyle::WebContentStyle() - : disableHinting(false) - , maxLines(0) - , textAlign(skia::textlayout::TextAlign::kLeft) - , textDirection(skia::textlayout::TextDirection::kLtr) - , textHeightBehavior(skia::textlayout::TextHeightBehavior::kAll) - , textStyle() - , lineHeight(1.2f) - , useFixedLineHeight(false) - , halfLeading(true) - , leading(0.0f) - , strutEnabled(true) - , forceStrutHeight(true) - , applyRoundingHack(false) - { - } - - WebContent::WebContent(std::string name, float initialWidth, float initialHeight, int layer) - : name_(name) - , layer_(layer) - , last_fragment_(std::nullopt) - , content_style_() - , background_color_(1.0f, 1.0f, 1.0f, 0.0f) - , device_pixel_ratio_(client_cssom::DevicePixelRatio) - , texture_pad_(2) - { - resetSkSurface(initialWidth, initialHeight); - } - - // Compute the size in pixels by the given size and device pixel ratio. This function also rounds the size to the - // nearest integer to avoid the floating point precision issue. - inline int computeSize(float size, float devicePixelRatio, int pad) - { - return static_cast(round(size * devicePixelRatio)) + pad * 2; - } - - bool WebContent::resetSkSurface(float w, float h) - { - assert(device_pixel_ratio_ > 0 && "The device pixel ratio must be valid."); - - if (TR_UNLIKELY(w <= 0 || h <= 0)) // Skip if size is invalid. - return false; - if (!needsResize(w, h)) // Skip if size is unchanged. - return false; - - // TODO: use Skia Genesh(GPU) to increase the performance. - SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(computeSize(w, device_pixel_ratio_, texture_pad_), - computeSize(h, device_pixel_ratio_, texture_pad_), - SkColorSpace::MakeSRGB()); - if (surface_ != nullptr) + , fontSize(20.0f) + , letterSpacing(std::nullopt) + , wordSpacing(std::nullopt) { - auto newSurface = surface_->makeSurface(imageInfo); - surface_.reset(); - surface_ = newSurface; } - else + + WebContentStyle::WebContentStyle() + : disableHinting(false) + , maxLines(0) + , textAlign(skia::textlayout::TextAlign::kLeft) + , textDirection(skia::textlayout::TextDirection::kLtr) + , textHeightBehavior(skia::textlayout::TextHeightBehavior::kAll) + , textStyle() + , lineHeight(1.2f) + , useFixedLineHeight(false) + , halfLeading(true) + , leading(0.0f) + , strutEnabled(true) + , forceStrutHeight(true) + , applyRoundingHack(false) { - surface_ = SkSurfaces::Raster(imageInfo); } - assert(surface_ != nullptr && "The new surface must be valid."); - assert(surface_->width() == imageInfo.width() && - surface_->height() == imageInfo.height() && - "The surface size must be valid."); - - setContentDirty(true); - setSurfaceDirty(true); - return true; - } - - SkCanvas *WebContent::canvas() const - { - if (TR_UNLIKELY(surface_ == nullptr)) - return nullptr; - - SkCanvas *canvas = surface_->getCanvas(); - SkMatrix transform = SkMatrix::Translate(texture_pad_, texture_pad_) - .preScale(device_pixel_ratio_, device_pixel_ratio_); - canvas->setMatrix(transform); - return canvas; - } + WebContent::WebContent(std::string name, float initialWidth, float initialHeight, int layer) + : name_(name) + , layer_(layer) + , last_fragment_(std::nullopt) + , content_style_() + , background_color_(1.0f, 1.0f, 1.0f, 0.0f) + , device_pixel_ratio_(client_cssom::DevicePixelRatio) + , texture_pad_(2) + { + resetSkSurface(initialWidth, initialHeight); + } - void WebContent::setStyle(const client_cssom::ComputedStyle &style, std::shared_ptr parent) - { - style_ = style; + // Compute the size in pixels by the given size and device pixel ratio. This function also rounds the size to the + // nearest integer to avoid the floating point precision issue. + inline int computeSize(float size, float devicePixelRatio, int pad) + { + return static_cast(round(size * devicePixelRatio)) + pad * 2; + } - // Update the text style + bool WebContent::resetSkSurface(float w, float h) { - if (style_.hasProperty("font-family")) + assert(device_pixel_ratio_ > 0 && "The device pixel ratio must be valid."); + + if (TR_UNLIKELY(w <= 0 || h <= 0)) // Skip if size is invalid. + return false; + if (!needsResize(w, h)) // Skip if size is unchanged. + return false; + + // TODO: use Skia Genesh(GPU) to increase the performance. + SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(computeSize(w, device_pixel_ratio_, texture_pad_), + computeSize(h, device_pixel_ratio_, texture_pad_), + SkColorSpace::MakeSRGB()); + if (surface_ != nullptr) { - auto &fonts = style_.fonts(); - if (fonts.size() >= 1) - { - vector skFonts; - for (auto &font : fonts) - skFonts.push_back(SkString(font)); - content_style_.textStyle.fontFamilies = skFonts; - } + auto newSurface = surface_->makeSurface(imageInfo); + surface_.reset(); + surface_ = newSurface; } - if (style_.hasProperty("font-size")) + else { - const auto &fontSize = style_.fontSize(); - content_style_.textStyle.fontSize = fontSize.computedSize().px(); + surface_ = SkSurfaces::Raster(imageInfo); } - if (style_.hasProperty("font-weight")) - { - auto fontWeight = style_.fontWeight(); - content_style_.textStyle.fontStyle.weight = SkFontStyle::Weight(fontWeight.value()); - } - if (style_.hasProperty("font-style")) - content_style_.textStyle.fontStyle.slant = style_.fontStyle(); + + assert(surface_ != nullptr && "The new surface must be valid."); + assert(surface_->width() == imageInfo.width() && + surface_->height() == imageInfo.height() && + "The surface size must be valid."); + + setContentDirty(true); + setSurfaceDirty(true); + return true; } - // Update Paragraph styles + SkCanvas *WebContent::canvas() const { - if (style_.hasProperty("color")) - { - // TODO(yorkie): support current color. - auto current_color = SK_ColorBLACK; - content_style_.textStyle.color = style_.color().resolveToAbsoluteColor(current_color); - } - if (style_.hasProperty("text-align")) - content_style_.textAlign = style_.textAlign(); - if (style_.hasProperty("direction")) - content_style_.textDirection = style_.textDirection(); + if (TR_UNLIKELY(surface_ == nullptr)) + return nullptr; + + SkCanvas *canvas = surface_->getCanvas(); + SkMatrix transform = SkMatrix::Translate(texture_pad_, texture_pad_) + .preScale(device_pixel_ratio_, device_pixel_ratio_); + canvas->setMatrix(transform); + return canvas; + } - // Line height - if (style_.hasProperty("line-height")) + void WebContent::setStyle(const client_cssom::ComputedStyle &style, std::shared_ptr parent) + { + style_ = style; + + // Update the text style { - const auto &lineHeight = style_.lineHeight(); - if (lineHeight.isLength()) + if (style_.hasProperty("font-family")) { - content_style_.useFixedLineHeight = true; - content_style_.lineHeight = lineHeight.getLength().px(); + auto &fonts = style_.fonts(); + if (fonts.size() >= 1) + { + vector skFonts; + for (auto &font : fonts) + skFonts.push_back(SkString(font)); + content_style_.textStyle.fontFamilies = skFonts; + } } - else if (lineHeight.isNumber()) + if (style_.hasProperty("font-size")) { - content_style_.useFixedLineHeight = false; - content_style_.lineHeight = lineHeight.getNumber().value; + const auto &fontSize = style_.fontSize(); + content_style_.textStyle.fontSize = fontSize.computedSize().px(); } - else + if (style_.hasProperty("font-weight")) { - content_style_.useFixedLineHeight = false; - content_style_.lineHeight = 1.2f; + auto fontWeight = style_.fontWeight(); + content_style_.textStyle.fontStyle.weight = SkFontStyle::Weight(fontWeight.value()); } + if (style_.hasProperty("font-style")) + content_style_.textStyle.fontStyle.slant = style_.fontStyle(); } - } - // Mark the content as dirty if setting a new style. - setContentDirty(true); - } + // Update Paragraph styles + { + if (style_.hasProperty("color")) + { + // TODO(yorkie): support current color. + auto current_color = SK_ColorBLACK; + content_style_.textStyle.color = style_.color().resolveToAbsoluteColor(current_color); + } + if (style_.hasProperty("text-align")) + content_style_.textAlign = style_.textAlign(); + if (style_.hasProperty("direction")) + content_style_.textDirection = style_.textDirection(); - // TODO(yorkie): consider the change of the device pixel ratio. - bool WebContent::needsResize(float w, float h) const - { - assert(w > 0 && h > 0); - if (TR_UNLIKELY(surface_ == nullptr)) - return true; + // Line height + if (style_.hasProperty("line-height")) + { + const auto &lineHeight = style_.lineHeight(); + if (lineHeight.isLength()) + { + content_style_.useFixedLineHeight = true; + content_style_.lineHeight = lineHeight.getLength().px(); + } + else if (lineHeight.isNumber()) + { + content_style_.useFixedLineHeight = false; + content_style_.lineHeight = lineHeight.getNumber().value; + } + else + { + content_style_.useFixedLineHeight = false; + content_style_.lineHeight = 1.2f; + } + } + } - return surface_->width() != computeSize(w, device_pixel_ratio_, texture_pad_) || - surface_->height() != computeSize(h, device_pixel_ratio_, texture_pad_); - } + // Mark the content as dirty if setting a new style. + setContentDirty(true); + } - shared_ptr WebContent::resizeOrInitTexture(TextureAtlas &textureAtlas) - { - if (!is_texture_using_) + // TODO(yorkie): consider the change of the device pixel ratio. + bool WebContent::needsResize(float w, float h) const { - // Remove the texture from atlas if it's not used. - if (texture_ != nullptr) + assert(w > 0 && h > 0); + if (TR_UNLIKELY(surface_ == nullptr)) + return true; + + return surface_->width() != computeSize(w, device_pixel_ratio_, texture_pad_) || + surface_->height() != computeSize(h, device_pixel_ratio_, texture_pad_); + } + + shared_ptr WebContent::resizeOrInitTexture(TextureAtlas &textureAtlas) + { + if (!is_texture_using_) { - textureAtlas.removeTexture(*texture_); - texture_ = nullptr; + // Remove the texture from atlas if it's not used. + if (texture_ != nullptr) + { + textureAtlas.removeTexture(*texture_); + texture_ = nullptr; + } + return nullptr; } - return nullptr; - } - float w = physicalWidth(); - float h = physicalHeight(); + float w = physicalWidth(); + float h = physicalHeight(); + + if (texture_ == nullptr) + texture_ = textureAtlas.addTexture(w, h, true); + else + texture_ = textureAtlas.resizeTexture(texture_, w, h, true); - if (texture_ == nullptr) - texture_ = textureAtlas.addTexture(w, h, true); - else - texture_ = textureAtlas.resizeTexture(texture_, w, h, true); + if (texture_ == nullptr) [[unlikely]] + { + cerr << "Failed to resize or initialize the texture for WebContent: " << name_ << endl + << " expected size: " << w << "x" << h << endl + << " device pixel ratio: " << device_pixel_ratio_ << endl; + assert(false && "Failed to resize or initialize the texture."); + } + return texture_; + } - if (texture_ == nullptr) [[unlikely]] + glm::vec2 WebContent::measureText(const std::string &text, float max_width) const { - cerr << "Failed to resize or initialize the texture for WebContent: " << name_ << endl - << " expected size: " << w << "x" << h << endl - << " device pixel ratio: " << device_pixel_ratio_ << endl; - assert(false && "Failed to resize or initialize the texture."); + auto paragraph_style = paragraphStyle(); + auto paragraph_builder = skia::textlayout::ParagraphBuilder::make( + paragraph_style, TrClientContextPerProcess::Get()->getFontCacheManager()); + paragraph_builder->pushStyle(paragraph_style.getTextStyle()); + paragraph_builder->addText(text.c_str(), text.size()); + paragraph_builder->pop(); + + auto paragraph = paragraph_builder->Build(); + paragraph->layout(max_width > 0 + ? max_width + 1.0f // Add a small margin to avoid rounding issues + : numeric_limits::infinity()); + + // Use longest line width and height as the constraint space. + return glm::vec2(paragraph->getLongestLine() / device_pixel_ratio_, + paragraph->getHeight() / device_pixel_ratio_); } - return texture_; - } - glm::vec2 WebContent::measureText(const std::string &text, float max_width) const - { - auto paragraph_style = paragraphStyle(); - auto paragraph_builder = skia::textlayout::ParagraphBuilder::make( - paragraph_style, TrClientContextPerProcess::Get()->getFontCacheManager()); - paragraph_builder->pushStyle(paragraph_style.getTextStyle()); - paragraph_builder->addText(text.c_str(), text.size()); - paragraph_builder->pop(); - - auto paragraph = paragraph_builder->Build(); - paragraph->layout(max_width > 0 - ? max_width + 1.0f // Add a small margin to avoid rounding issues - : numeric_limits::infinity()); - - // Use longest line width and height as the constraint space. - return glm::vec2(paragraph->getLongestLine() / device_pixel_ratio_, - paragraph->getHeight() / device_pixel_ratio_); - } + skia::textlayout::TextStyle WebContent::textStyle() const + { + const WebContentTextStyle &sourceTextStyle = content_style_.textStyle; + skia::textlayout::TextStyle newTextStyle; - skia::textlayout::TextStyle WebContent::textStyle() const - { - const WebContentTextStyle &sourceTextStyle = content_style_.textStyle; - skia::textlayout::TextStyle newTextStyle; + SkPaint foregroundPaint; + { + foregroundPaint.setAntiAlias(true); + foregroundPaint.setColor(sourceTextStyle.color); + if (sourceTextStyle.foregroundColor.has_value()) + foregroundPaint.setColor(sourceTextStyle.foregroundColor.value()); + newTextStyle.setForegroundPaint(foregroundPaint); + } - SkPaint foregroundPaint; - { - foregroundPaint.setAntiAlias(true); - foregroundPaint.setColor(sourceTextStyle.color); - if (sourceTextStyle.foregroundColor.has_value()) - foregroundPaint.setColor(sourceTextStyle.foregroundColor.value()); - newTextStyle.setForegroundPaint(foregroundPaint); + if (sourceTextStyle.backgroundColor.has_value()) + { + SkPaint backgroundPaint; + backgroundPaint.setAntiAlias(true); + backgroundPaint.setColor(sourceTextStyle.backgroundColor.value()); + newTextStyle.setBackgroundColor(backgroundPaint); + } + + newTextStyle.setFontSize(sourceTextStyle.fontSize); + if (sourceTextStyle.letterSpacing.has_value()) + newTextStyle.setLetterSpacing(sourceTextStyle.letterSpacing.value()); + if (sourceTextStyle.wordSpacing.has_value()) + newTextStyle.setWordSpacing(sourceTextStyle.wordSpacing.value()); + + newTextStyle.setHalfLeading(content_style_.halfLeading); + newTextStyle.setFontFamilies(sourceTextStyle.fontFamilies); + newTextStyle.setFontStyle(SkFontStyle(sourceTextStyle.fontStyle.weight, + sourceTextStyle.fontStyle.width, + sourceTextStyle.fontStyle.slant)); + return newTextStyle; } - if (sourceTextStyle.backgroundColor.has_value()) + skia::textlayout::StrutStyle WebContent::structStyle() const { - SkPaint backgroundPaint; - backgroundPaint.setAntiAlias(true); - backgroundPaint.setColor(sourceTextStyle.backgroundColor.value()); - newTextStyle.setBackgroundColor(backgroundPaint); + skia::textlayout::StrutStyle newStrutStyle; + newStrutStyle.setFontFamilies(content_style_.textStyle.fontFamilies); + + SkFontStyle fontStyle = SkFontStyle(content_style_.textStyle.fontStyle.weight, + content_style_.textStyle.fontStyle.width, + content_style_.textStyle.fontStyle.slant); + newStrutStyle.setFontStyle(fontStyle); + + auto &textStyle = content_style_.textStyle; + newStrutStyle.setFontSize(textStyle.fontSize); + newStrutStyle.setHalfLeading(content_style_.halfLeading); + + // Reconfigure the font size based on the line height settings. + if (content_style_.useFixedLineHeight) + newStrutStyle.setFontSize(content_style_.lineHeight); + else + newStrutStyle.setFontSize(textStyle.fontSize * content_style_.lineHeight); + + newStrutStyle.setStrutEnabled(true); + newStrutStyle.setForceStrutHeight(true); + return newStrutStyle; } - newTextStyle.setFontSize(sourceTextStyle.fontSize); - if (sourceTextStyle.letterSpacing.has_value()) - newTextStyle.setLetterSpacing(sourceTextStyle.letterSpacing.value()); - if (sourceTextStyle.wordSpacing.has_value()) - newTextStyle.setWordSpacing(sourceTextStyle.wordSpacing.value()); - - newTextStyle.setHalfLeading(content_style_.halfLeading); - newTextStyle.setFontFamilies(sourceTextStyle.fontFamilies); - newTextStyle.setFontStyle(SkFontStyle(sourceTextStyle.fontStyle.weight, - sourceTextStyle.fontStyle.width, - sourceTextStyle.fontStyle.slant)); - return newTextStyle; - } - - skia::textlayout::StrutStyle WebContent::structStyle() const - { - skia::textlayout::StrutStyle newStrutStyle; - newStrutStyle.setFontFamilies(content_style_.textStyle.fontFamilies); - - SkFontStyle fontStyle = SkFontStyle(content_style_.textStyle.fontStyle.weight, - content_style_.textStyle.fontStyle.width, - content_style_.textStyle.fontStyle.slant); - newStrutStyle.setFontStyle(fontStyle); - - auto &textStyle = content_style_.textStyle; - newStrutStyle.setFontSize(textStyle.fontSize); - newStrutStyle.setHalfLeading(content_style_.halfLeading); - - // Reconfigure the font size based on the line height settings. - if (content_style_.useFixedLineHeight) - newStrutStyle.setFontSize(content_style_.lineHeight); - else - newStrutStyle.setFontSize(textStyle.fontSize * content_style_.lineHeight); - - newStrutStyle.setStrutEnabled(true); - newStrutStyle.setForceStrutHeight(true); - return newStrutStyle; - } - - skia::textlayout::ParagraphStyle WebContent::paragraphStyle() const - { - skia::textlayout::ParagraphStyle newParagraphStyle; - if (content_style_.disableHinting) - newParagraphStyle.turnHintingOff(); + skia::textlayout::ParagraphStyle WebContent::paragraphStyle() const + { + skia::textlayout::ParagraphStyle newParagraphStyle; + if (content_style_.disableHinting) + newParagraphStyle.turnHintingOff(); - newParagraphStyle.setTextAlign(content_style_.textAlign); - newParagraphStyle.setTextDirection(content_style_.textDirection); - newParagraphStyle.setTextStyle(textStyle()); - newParagraphStyle.setStrutStyle(structStyle()); + newParagraphStyle.setTextAlign(content_style_.textAlign); + newParagraphStyle.setTextDirection(content_style_.textDirection); + newParagraphStyle.setTextStyle(textStyle()); + newParagraphStyle.setStrutStyle(structStyle()); - if (content_style_.maxLines > 0) - newParagraphStyle.setMaxLines(content_style_.maxLines); + if (content_style_.maxLines > 0) + newParagraphStyle.setMaxLines(content_style_.maxLines); - newParagraphStyle.setApplyRoundingHack(content_style_.applyRoundingHack); - newParagraphStyle.setTextHeightBehavior(content_style_.textHeightBehavior); - return newParagraphStyle; + newParagraphStyle.setApplyRoundingHack(content_style_.applyRoundingHack); + newParagraphStyle.setTextHeightBehavior(content_style_.textHeightBehavior); + return newParagraphStyle; + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/web_content.hpp b/src/client/builtin_scene/web_content.hpp index b3489bc21..c0e99d26c 100644 --- a/src/client/builtin_scene/web_content.hpp +++ b/src/client/builtin_scene/web_content.hpp @@ -18,94 +18,96 @@ #include "./texture_altas.hpp" #include "./text/sdf/generator.hpp" -namespace builtin_scene +namespace endor { - namespace web_renderer + namespace builtin_scene { - class InitSystem; - class RenderBaseSystem; - class RenderBackgroundSystem; - class RenderImageSystem; - class RenderTextSystem; - class UpdateTextureSystem; - } + namespace web_renderer + { + class InitSystem; + class RenderBaseSystem; + class RenderBackgroundSystem; + class RenderImageSystem; + class RenderTextSystem; + class UpdateTextureSystem; + } - struct WebContentFontStyle - { - SkFontStyle::Slant slant; - SkFontStyle::Weight weight; - SkFontStyle::Width width; - }; + struct WebContentFontStyle + { + SkFontStyle::Slant slant; + SkFontStyle::Weight weight; + SkFontStyle::Width width; + }; - class WebContentTextStyle - { - public: - WebContentTextStyle(); - - public: - SkColor color; - std::optional foregroundColor; - std::optional backgroundColor; - uint8_t decoration; - SkScalar decorationThickness; - SkColor decorationColor; - WebContentFontStyle fontStyle; - std::vector fontFamilies; - SkScalar fontSize; - std::optional letterSpacing; - std::optional wordSpacing; - - friend std::ostream &operator<<(std::ostream &os, const WebContentTextStyle &style) + class WebContentTextStyle { - std::string foreground_display = style.foregroundColor.has_value() - ? std::to_string(style.foregroundColor.value()) - : "null"; - std::string background_display = style.backgroundColor.has_value() - ? std::to_string(style.backgroundColor.value()) - : "null"; - - os << "WebContentTextStyle { " << std::endl - << " color: " << style.color << std::endl - << " foreground: " << foreground_display << std::endl - << " background: " << background_display << std::endl - << " decoration: " << static_cast(style.decoration) << std::endl - << " font-size: " << style.fontSize << std::endl - << " font-style: " << style.fontStyle.slant << std::endl - << "}"; - return os; - } - }; + public: + WebContentTextStyle(); - class WebContentStyle - { - public: - WebContentStyle(); - - public: - bool disableHinting; - size_t maxLines; - skia::textlayout::TextAlign textAlign; - skia::textlayout::TextDirection textDirection; - skia::textlayout::TextHeightBehavior textHeightBehavior; - WebContentTextStyle textStyle; - SkScalar lineHeight; - bool useFixedLineHeight; - bool halfLeading; - SkScalar leading; - bool strutEnabled; - bool forceStrutHeight; - bool applyRoundingHack; - }; - - class WebContent : public ecs::Component - { - friend class web_renderer::RenderBackgroundSystem; - friend class web_renderer::RenderImageSystem; - friend class web_renderer::RenderTextSystem; - friend class web_renderer::UpdateTextureSystem; + public: + SkColor color; + std::optional foregroundColor; + std::optional backgroundColor; + uint8_t decoration; + SkScalar decorationThickness; + SkColor decorationColor; + WebContentFontStyle fontStyle; + std::vector fontFamilies; + SkScalar fontSize; + std::optional letterSpacing; + std::optional wordSpacing; + + friend std::ostream &operator<<(std::ostream &os, const WebContentTextStyle &style) + { + std::string foreground_display = style.foregroundColor.has_value() + ? std::to_string(style.foregroundColor.value()) + : "null"; + std::string background_display = style.backgroundColor.has_value() + ? std::to_string(style.backgroundColor.value()) + : "null"; + + os << "WebContentTextStyle { " << std::endl + << " color: " << style.color << std::endl + << " foreground: " << foreground_display << std::endl + << " background: " << background_display << std::endl + << " decoration: " << static_cast(style.decoration) << std::endl + << " font-size: " << style.fontSize << std::endl + << " font-style: " << style.fontStyle.slant << std::endl + << "}"; + return os; + } + }; - public: - /** + class WebContentStyle + { + public: + WebContentStyle(); + + public: + bool disableHinting; + size_t maxLines; + skia::textlayout::TextAlign textAlign; + skia::textlayout::TextDirection textDirection; + skia::textlayout::TextHeightBehavior textHeightBehavior; + WebContentTextStyle textStyle; + SkScalar lineHeight; + bool useFixedLineHeight; + bool halfLeading; + SkScalar leading; + bool strutEnabled; + bool forceStrutHeight; + bool applyRoundingHack; + }; + + class WebContent : public ecs::Component + { + friend class web_renderer::RenderBackgroundSystem; + friend class web_renderer::RenderImageSystem; + friend class web_renderer::RenderTextSystem; + friend class web_renderer::UpdateTextureSystem; + + public: + /** * Construct a new `WebContent` object for rendering the classic web content. * * @param name The content name. @@ -113,172 +115,172 @@ namespace builtin_scene * @param initialHeight The initial height of the content. * @param layer The layer number based on scrollable container hierarchy. */ - WebContent(std::string name, float initialWidth, float initialHeight, int layer = 0); + WebContent(std::string name, float initialWidth, float initialHeight, int layer = 0); - public: - /** + public: + /** * The content name. */ - inline const std::string &name() const - { - return name_; - } + inline const std::string &name() const + { + return name_; + } - /** + /** * Get the current layer number based on scrollable container hierarchy. */ - inline int layer() const - { - return layer_; - } + inline int layer() const + { + return layer_; + } - /** + /** * Set the layer number. */ - inline void setLayer(int layer) - { - layer_ = layer; - } + inline void setLayer(int layer) + { + layer_ = layer; + } - inline bool isScrollableContainer() const - { - return is_scrollable_container_; - } - inline void setIsScrollableContainer(bool value) - { - is_scrollable_container_ = value; - } - inline ecs::EntityId belongsToScrollableContainer() const - { - return belongs_to_scrollable_container_; - } - inline void setBelongsToScrollableContainer(ecs::EntityId container) - { - belongs_to_scrollable_container_ = container; - } + inline bool isScrollableContainer() const + { + return is_scrollable_container_; + } + inline void setIsScrollableContainer(bool value) + { + is_scrollable_container_ = value; + } + inline ecs::EntityId belongsToScrollableContainer() const + { + return belongs_to_scrollable_container_; + } + inline void setBelongsToScrollableContainer(ecs::EntityId container) + { + belongs_to_scrollable_container_ = container; + } - // Returns if the surface is valid. - bool resetSkSurface(float width, float height); - SkCanvas *canvas() const; + // Returns if the surface is valid. + bool resetSkSurface(float width, float height); + SkCanvas *canvas() const; - inline client_cssom::ComputedStyle &style() - { - return style_; - } - inline const client_cssom::ComputedStyle &style() const - { - return style_; - } - void setStyle(const client_cssom::ComputedStyle &style, std::shared_ptr parent = nullptr); + inline client_cssom::ComputedStyle &style() + { + return style_; + } + inline const client_cssom::ComputedStyle &style() const + { + return style_; + } + void setStyle(const client_cssom::ComputedStyle &style, std::shared_ptr parent = nullptr); - inline const std::optional &fragment() const - { - return last_fragment_; - } - inline void setFragment(const client_layout::Fragment &fragment) - { - last_fragment_ = fragment; - } + inline const std::optional &fragment() const + { + return last_fragment_; + } + inline void setFragment(const client_layout::Fragment &fragment) + { + last_fragment_ = fragment; + } - inline float physicalWidth() const - { - return surface_ == nullptr ? 0.0f : surface_->width(); - } - inline float physicalHeight() const - { - return surface_ == nullptr ? 0.0f : surface_->height(); - } - inline float logicalWidth() const - { - return physicalWidth() / device_pixel_ratio_; - } - inline float logicalHeight() const - { - return physicalHeight() / device_pixel_ratio_; - } + inline float physicalWidth() const + { + return surface_ == nullptr ? 0.0f : surface_->width(); + } + inline float physicalHeight() const + { + return surface_ == nullptr ? 0.0f : surface_->height(); + } + inline float logicalWidth() const + { + return physicalWidth() / device_pixel_ratio_; + } + inline float logicalHeight() const + { + return physicalHeight() / device_pixel_ratio_; + } - // Check if the surface needs to be resized. - bool needsResize(float w, float h) const; + // Check if the surface needs to be resized. + bool needsResize(float w, float h) const; - inline glm::vec4 backgroundColor() const - { - return background_color_; - } - inline void setBackgroundColor(float r, float g, float b, float a) - { - background_color_ = glm::vec4(r, g, b, a); - } - inline void setBackgroundColor(const SkColor4f color) - { - background_color_ = glm::vec4(color.fR, color.fG, color.fB, color.fA); - } - inline glm::vec4 borderRadius() const - { - return border_radius_; - } - inline void setBorderRadius(float topLeft, - float topRight, - float bottomRight, - float bottomLeft) - { - border_radius_ = glm::vec4(topLeft, topRight, bottomRight, bottomLeft); - } - inline void resetBorderRadius() - { - border_radius_ = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - } + inline glm::vec4 backgroundColor() const + { + return background_color_; + } + inline void setBackgroundColor(float r, float g, float b, float a) + { + background_color_ = glm::vec4(r, g, b, a); + } + inline void setBackgroundColor(const SkColor4f color) + { + background_color_ = glm::vec4(color.fR, color.fG, color.fB, color.fA); + } + inline glm::vec4 borderRadius() const + { + return border_radius_; + } + inline void setBorderRadius(float topLeft, + float topRight, + float bottomRight, + float bottomLeft) + { + border_radius_ = glm::vec4(topLeft, topRight, bottomRight, bottomLeft); + } + inline void resetBorderRadius() + { + border_radius_ = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); + } - inline std::shared_ptr textureRect() const - { - return texture_; - } - inline const Texture &textureRectRef() const - { - return *texture_; - } + inline std::shared_ptr textureRect() const + { + return texture_; + } + inline const Texture &textureRectRef() const + { + return *texture_; + } - // Spatial image support - inline bool isSpatialized() const - { - return is_spatialized_; - } - inline void setSpatialized(bool v) - { - is_spatialized_ = v; - } + // Spatial image support + inline bool isSpatialized() const + { + return is_spatialized_; + } + inline void setSpatialized(bool v) + { + is_spatialized_ = v; + } - // Returns the pad in pixels for the texture, the pad is used to avoid the texture bleeding issue. - inline int texturePad() const - { - return texture_pad_; - } + // Returns the pad in pixels for the texture, the pad is used to avoid the texture bleeding issue. + inline int texturePad() const + { + return texture_pad_; + } - /** + /** * Init or resize the texture. * * @param textureAtlas The texture atlas to create or resize the texture. * @returns The texture or `nullptr` if the texture is not used. */ - std::shared_ptr resizeOrInitTexture(TextureAtlas &textureAtlas); + std::shared_ptr resizeOrInitTexture(TextureAtlas &textureAtlas); - /** + /** * Measure the size of the given text with current text style. * * @param text The text to measure. * @param max_width The maximum width of the text, if the text exceeds the width, it will be wrapped. * @returns The size of the text in logical pixels. */ - glm::vec2 measureText(const std::string &text, float max_width) const; + glm::vec2 measureText(const std::string &text, float max_width) const; - inline void setEnabled(bool enabled) - { - enabled_ = enabled; - } - inline bool isEnabled() const - { - return enabled_; - } - /** + inline void setEnabled(bool enabled) + { + enabled_ = enabled; + } + inline bool isEnabled() const + { + return enabled_; + } + /** * Set the web content to use the texture or not. * * To reduce the texture memory usage, we will leverage simple rendering in GPU directly in some cases, such as: background @@ -286,384 +288,385 @@ namespace builtin_scene * * @param value Whether to use the texture. */ - inline void setTextureUsing(bool value) - { - if (is_texture_using_ != value) - is_texture_using_ = value; - } + inline void setTextureUsing(bool value) + { + if (is_texture_using_ != value) + is_texture_using_ = value; + } - /** + /** * Set the texture is a signed distance field (SDF) texture or not. */ - inline void setIsSDFTexture(bool value) - { - if (is_sdf_texture_ != value) - is_sdf_texture_ = value; - } + inline void setIsSDFTexture(bool value) + { + if (is_sdf_texture_ != value) + is_sdf_texture_ = value; + } - /** + /** * @returns Whether the content uses SDF texture rendering. */ - inline bool isSDFTexture() const - { - return is_sdf_texture_; - } + inline bool isSDFTexture() const + { + return is_sdf_texture_; + } - // Web content must be transparent objects. - inline bool isOpaque() const - { - return false; - } - inline bool isTransparent() const - { - return true; - } + // Web content must be transparent objects. + inline bool isOpaque() const + { + return false; + } + inline bool isTransparent() const + { + return true; + } - inline bool isContentDirty() const - { - return is_content_dirty_; - } - inline void setContentDirty(bool dirty) - { - is_content_dirty_ = dirty; - } - inline bool isSurfaceDirty() const - { - return is_surface_dirty_; - } - inline void setSurfaceDirty(bool dirty) - { - is_surface_dirty_ = dirty; - } + inline bool isContentDirty() const + { + return is_content_dirty_; + } + inline void setContentDirty(bool dirty) + { + is_content_dirty_ = dirty; + } + inline bool isSurfaceDirty() const + { + return is_surface_dirty_; + } + inline void setSurfaceDirty(bool dirty) + { + is_surface_dirty_ = dirty; + } - /** + /** * Get the rounded rectangle representing the border geometry. * * @returns The SkRRect containing border radius information. */ - inline const SkRRect &roundedRect() const - { - return rounded_rect_; - } - - public: - skia::textlayout::TextStyle textStyle() const; - skia::textlayout::StrutStyle structStyle() const; - skia::textlayout::ParagraphStyle paragraphStyle() const; - - private: - sk_sp surface_; - std::string name_; - - int layer_; - bool is_scrollable_container_; - ecs::EntityId belongs_to_scrollable_container_; - - client_cssom::ComputedStyle style_; - std::optional last_fragment_; - WebContentStyle content_style_; - SkRRect rounded_rect_; - glm::vec4 background_color_; - glm::vec4 border_radius_; - - std::shared_ptr texture_; - float device_pixel_ratio_ = 1.0f; - int texture_pad_ = 2; - bool enabled_ = true; - bool is_texture_using_ = false; - bool is_visible_ = true; - bool is_spatialized_ = false; - bool is_sdf_texture_ = false; - - // The flag to indicate if the content is dirty and needs to be rendered to the surface. - bool is_content_dirty_ = true; - // The flag to indicate if the surface is dirty and needs to be update to the GPU texture. - bool is_surface_dirty_ = true; - }; - - class WebContentContext : public ecs::Resource - { - friend class web_renderer::InitSystem; + inline const SkRRect &roundedRect() const + { + return rounded_rect_; + } - public: - ecs::EntityId instancedMeshEntity() const - { - return instancedMeshEntity_; - } + public: + skia::textlayout::TextStyle textStyle() const; + skia::textlayout::StrutStyle structStyle() const; + skia::textlayout::ParagraphStyle paragraphStyle() const; - private: - ecs::EntityId instancedMeshEntity_; - }; + private: + sk_sp surface_; + std::string name_; + + int layer_; + bool is_scrollable_container_; + ecs::EntityId belongs_to_scrollable_container_; + + client_cssom::ComputedStyle style_; + std::optional last_fragment_; + WebContentStyle content_style_; + SkRRect rounded_rect_; + glm::vec4 background_color_; + glm::vec4 border_radius_; + + std::shared_ptr texture_; + float device_pixel_ratio_ = 1.0f; + int texture_pad_ = 2; + bool enabled_ = true; + bool is_texture_using_ = false; + bool is_visible_ = true; + bool is_spatialized_ = false; + bool is_sdf_texture_ = false; + + // The flag to indicate if the content is dirty and needs to be rendered to the surface. + bool is_content_dirty_ = true; + // The flag to indicate if the surface is dirty and needs to be update to the GPU texture. + bool is_surface_dirty_ = true; + }; - /** - * Systems for rendering Web content: background, boxes, text, etc. - */ - namespace web_renderer - { - class InitSystem final : public ecs::System + class WebContentContext : public ecs::Resource { - using ecs::System::System; + friend class web_renderer::InitSystem; public: - const std::string name() const override + ecs::EntityId instancedMeshEntity() const { - return "web_render.InitSystem"; + return instancedMeshEntity_; } - void onExecute() override; + + private: + ecs::EntityId instancedMeshEntity_; }; - class RenderBaseSystem : public ecs::System + /** + * Systems for rendering Web content: background, boxes, text, etc. + */ + namespace web_renderer { - using ecs::System::System; + class InitSystem final : public ecs::System + { + using ecs::System::System; - protected: - /** + public: + const std::string name() const override + { + return "web_render.InitSystem"; + } + void onExecute() override; + }; + + class RenderBaseSystem : public ecs::System + { + using ecs::System::System; + + protected: + /** * Get the instanced mesh component. * * @tparam ComponentType The type of the component. * @returns The instanced mesh component. */ - template - std::shared_ptr getInstancedMeshComponent() - { - if (webContentCtx_ == nullptr) - webContentCtx_ = getResource(); - assert(webContentCtx_ != nullptr && "The WebContentContext must be valid."); + template + std::shared_ptr getInstancedMeshComponent() + { + if (webContentCtx_ == nullptr) + webContentCtx_ = getResource(); + assert(webContentCtx_ != nullptr && "The WebContentContext must be valid."); - auto entity = webContentCtx_->instancedMeshEntity(); - return getComponent(entity); - } - /** + auto entity = webContentCtx_->instancedMeshEntity(); + return getComponent(entity); + } + /** * Get the object reference to the instanced mesh's component, it will expect the component to be valid. * * @tparam ComponentType The type of the component. * @returns The object reference to the instanced mesh's component. */ - template - ComponentType &getInstancedMeshComponentChecked() - { - auto component = getInstancedMeshComponent(); - assert(component != nullptr && "The instanced mesh component must be valid."); - return *component; - } + template + ComponentType &getInstancedMeshComponentChecked() + { + auto component = getInstancedMeshComponent(); + assert(component != nullptr && "The instanced mesh component must be valid."); + return *component; + } - private: - std::shared_ptr webContentCtx_; - }; + private: + std::shared_ptr webContentCtx_; + }; - class RenderContentBaseSystem : public RenderBaseSystem - { - using RenderBaseSystem::RenderBaseSystem; + class RenderContentBaseSystem : public RenderBaseSystem + { + using RenderBaseSystem::RenderBaseSystem; - public: - void onExecute() override final; + public: + void onExecute() override final; - protected: - /** + protected: + /** * Render the content for the given entity. * * @param entity The entity ID to render. * @param content The WebContent to render. * @returns Whether the surface is written successfully. */ - virtual bool render(ecs::EntityId entity, WebContent &content) = 0; - }; + virtual bool render(ecs::EntityId entity, WebContent &content) = 0; + }; - /** + /** * Render the background. * * - background * - border * - radius */ - class RenderBackgroundSystem final : public RenderContentBaseSystem - { - using RenderContentBaseSystem::RenderContentBaseSystem; - - public: - const std::string name() const override + class RenderBackgroundSystem final : public RenderContentBaseSystem { - return "web_render.RenderBackgroundSystem"; - } + using RenderContentBaseSystem::RenderContentBaseSystem; - private: - bool render(ecs::EntityId entity, WebContent &content) override; - - private: - // The clipping area for the background, it can be a path or a rounded rectangle. - class ClippingArea : public std::variant - { public: - ClippingArea() = default; - ClippingArea(const SkPath &path) - : std::variant(path) - { - } - ClippingArea(const SkRRect &rrect) - : std::variant(rrect) + const std::string name() const override { + return "web_render.RenderBackgroundSystem"; } - inline bool isEmpty() const - { - return std::holds_alternative(*this); - } - inline bool isPath() const - { - return std::holds_alternative(*this); - } - inline bool isRRect() const - { - return std::holds_alternative(*this); - } + private: + bool render(ecs::EntityId entity, WebContent &content) override; - inline const SkPath &path() const - { - return std::get(*this); - } - inline const SkRRect &roundedRect() const + private: + // The clipping area for the background, it can be a path or a rounded rectangle. + class ClippingArea : public std::variant { - return std::get(*this); - } + public: + ClippingArea() = default; + ClippingArea(const SkPath &path) + : std::variant(path) + { + } + ClippingArea(const SkRRect &rrect) + : std::variant(rrect) + { + } - friend std::ostream &operator<<(std::ostream &os, const ClippingArea &area) - { - if (area.isEmpty()) - os << "ClippingArea()"; - else if (area.isPath()) - os << "ClippingArea(Path)"; - else if (area.isRRect()) + inline bool isEmpty() const { - auto &rrect = area.roundedRect(); - os << "ClippingArea(" << rrect.width() << "," << rrect.height() << ")"; + return std::holds_alternative(*this); + } + inline bool isPath() const + { + return std::holds_alternative(*this); + } + inline bool isRRect() const + { + return std::holds_alternative(*this); } - return os; - } - }; - // Helper methods for drawing and clipping. - SkRRect getBackgroundClippingArea(const SkRRect &, - const client_layout::Fragment &, - const client_cssom::ComputedStyle &); - std::optional createTextPath(const std::string &textContent, const WebContent &); - - // Draw the background for a fragment, returning an optional SkPaint if a fill is drawn. - std::optional drawBackground(SkCanvas *, - SkRRect &originalRRect, - ClippingArea &, - const client_layout::Fragment &, - const client_cssom::ComputedStyle &, - bool &textureRequired); - // Draw the rounded rectangle with the given paint, using the clipping area if provided. - void drawRRect(SkCanvas *, const SkRRect &, const SkPaint &, const ClippingArea &); - // Draw the image in the positioning area with the given paint. - void drawImage(SkCanvas *, - const sk_sp &, - const SkRect &positioningArea, - const SkPaint &, - const client_cssom::ComputedStyle &); - // Draw the image with separate positioning and repeatable areas. - void drawImage(SkCanvas *, - const sk_sp &, - const SkRect &positioningArea, - const SkRect &repeatableArea, - const SkPaint &, - const client_cssom::ComputedStyle &); - }; + inline const SkPath &path() const + { + return std::get(*this); + } + inline const SkRRect &roundedRect() const + { + return std::get(*this); + } - class RenderImageSystem final : public RenderContentBaseSystem - { - using RenderContentBaseSystem::RenderContentBaseSystem; + friend std::ostream &operator<<(std::ostream &os, const ClippingArea &area) + { + if (area.isEmpty()) + os << "ClippingArea()"; + else if (area.isPath()) + os << "ClippingArea(Path)"; + else if (area.isRRect()) + { + auto &rrect = area.roundedRect(); + os << "ClippingArea(" << rrect.width() << "," << rrect.height() << ")"; + } + return os; + } + }; + + // Helper methods for drawing and clipping. + SkRRect getBackgroundClippingArea(const SkRRect &, + const client_layout::Fragment &, + const client_cssom::ComputedStyle &); + std::optional createTextPath(const std::string &textContent, const WebContent &); + + // Draw the background for a fragment, returning an optional SkPaint if a fill is drawn. + std::optional drawBackground(SkCanvas *, + SkRRect &originalRRect, + ClippingArea &, + const client_layout::Fragment &, + const client_cssom::ComputedStyle &, + bool &textureRequired); + // Draw the rounded rectangle with the given paint, using the clipping area if provided. + void drawRRect(SkCanvas *, const SkRRect &, const SkPaint &, const ClippingArea &); + // Draw the image in the positioning area with the given paint. + void drawImage(SkCanvas *, + const sk_sp &, + const SkRect &positioningArea, + const SkPaint &, + const client_cssom::ComputedStyle &); + // Draw the image with separate positioning and repeatable areas. + void drawImage(SkCanvas *, + const sk_sp &, + const SkRect &positioningArea, + const SkRect &repeatableArea, + const SkPaint &, + const client_cssom::ComputedStyle &); + }; - public: - const std::string name() const override + class RenderImageSystem final : public RenderContentBaseSystem { - return "web_render.RenderImageSystem"; - } + using RenderContentBaseSystem::RenderContentBaseSystem; - private: - bool render(ecs::EntityId entity, WebContent &content) override; - }; + public: + const std::string name() const override + { + return "web_render.RenderImageSystem"; + } - /** + private: + bool render(ecs::EntityId entity, WebContent &content) override; + }; + + /** * Render the text. * * - font * - size * - color */ - class RenderTextSystem final : public RenderContentBaseSystem - { - using RenderContentBaseSystem::RenderContentBaseSystem; - - public: - RenderTextSystem(); - - public: - const std::string name() const override + class RenderTextSystem final : public RenderContentBaseSystem { - return "web_render.RenderTextSystem"; - } + using RenderContentBaseSystem::RenderContentBaseSystem; - private: - bool render(ecs::EntityId entity, WebContent &content) override; + public: + RenderTextSystem(); - private: - float getLayoutWidthForText(WebContent &content); - bool generateSignedDistanceOn(SkCanvas *canvas); + public: + const std::string name() const override + { + return "web_render.RenderTextSystem"; + } - private: - TrClientContextPerProcess *clientContext_; - sk_sp fontCollection_; - std::unique_ptr paragraphBuilder_; - text::sdf::SDFGenerator sdfGenerator_; - }; + private: + bool render(ecs::EntityId entity, WebContent &content) override; - class UpdateTextureSystem final : public RenderBaseSystem - { - using RenderBaseSystem::RenderBaseSystem; + private: + float getLayoutWidthForText(WebContent &content); + bool generateSignedDistanceOn(SkCanvas *canvas); - public: - const std::string name() const override + private: + TrClientContextPerProcess *clientContext_; + sk_sp fontCollection_; + std::unique_ptr paragraphBuilder_; + text::sdf::SDFGenerator sdfGenerator_; + }; + + class UpdateTextureSystem final : public RenderBaseSystem { - return "web_render.UpdateTextureSystem"; - } + using RenderBaseSystem::RenderBaseSystem; - public: - void onExecute() override final; - }; - } + public: + const std::string name() const override + { + return "web_render.UpdateTextureSystem"; + } - /** + public: + void onExecute() override final; + }; + } + + /** * The plugin to render Web content spatially. */ - class WebContentPlugin final : public ecs::Plugin - { - public: - using ecs::Plugin::Plugin; - - protected: - void build(ecs::App &app) override + class WebContentPlugin final : public ecs::Plugin { - using namespace ecs; - using namespace web_renderer; + public: + using ecs::Plugin::Plugin; + + protected: + void build(ecs::App &app) override + { + using namespace ecs; + using namespace web_renderer; - app.addResource(Resource::Make()); - app.registerComponent(); + app.addResource(Resource::Make()); + app.registerComponent(); - auto initWebContent = System::Make(); - app.addSystem(SchedulerLabel::kPostStartup, initWebContent); + auto initWebContent = System::Make(); + app.addSystem(SchedulerLabel::kPostStartup, initWebContent); - auto renderBackground = System::Make(); - auto renderImage = System::Make(); - auto renderText = System::Make(); - auto updateTexture = System::Make(); + auto renderBackground = System::Make(); + auto renderImage = System::Make(); + auto renderText = System::Make(); + auto updateTexture = System::Make(); - renderBackground - ->chain(renderImage) - ->chain(renderText) - ->chain(updateTexture); - app.addSystem(SchedulerLabel::kUpdate, renderBackground); - } - }; -} + renderBackground + ->chain(renderImage) + ->chain(renderText) + ->chain(updateTexture); + app.addSystem(SchedulerLabel::kUpdate, renderBackground); + } + }; + } +} // namespace endor diff --git a/src/client/builtin_scene/web_content_renderer.cpp b/src/client/builtin_scene/web_content_renderer.cpp index 857812a3f..5f3950959 100644 --- a/src/client/builtin_scene/web_content_renderer.cpp +++ b/src/client/builtin_scene/web_content_renderer.cpp @@ -36,1202 +36,1235 @@ #include "./image.hpp" #include "./xr.hpp" -namespace builtin_scene::web_renderer +namespace endor { - using namespace std; - using namespace skia::textlayout; - using namespace client_cssom; - using namespace client_cssom::values; - - using BorderEdge = client_cssom::values::generics::BorderEdge; - using BorderCorner = client_cssom::values::generics::BorderCorner; + namespace builtin_scene::web_renderer + { + using namespace std; + using namespace skia::textlayout; + using namespace client_cssom; + using namespace client_cssom::values; + using BorderEdge = client_cssom::values::generics::BorderEdge; + using BorderCorner = client_cssom::values::generics::BorderCorner; - // Helper function to calculate background positioning area based on background-origin - SkRect getBackgroundPositioningArea(const SkRRect &roundedRect, - const client_layout::Fragment &fragment, - const ComputedStyle &style) - { - const SkRect &borderBox = roundedRect.rect(); - if (style.backgroundOrigin().isBorderBox()) + // Helper function to calculate background positioning area based on background-origin + SkRect getBackgroundPositioningArea(const SkRRect &roundedRect, + const client_layout::Fragment &fragment, + const ComputedStyle &style) { + const SkRect &borderBox = roundedRect.rect(); + + if (style.backgroundOrigin().isBorderBox()) + { + return borderBox; + } + else if (style.backgroundOrigin().isPaddingBox()) + { + // For padding-box, subtract border widths + float borderTop = fragment.border().top(); + float borderRight = fragment.border().right(); + float borderBottom = fragment.border().bottom(); + float borderLeft = fragment.border().left(); + + return SkRect::MakeLTRB( + borderBox.fLeft + borderLeft, + borderBox.fTop + borderTop, + borderBox.fRight - borderRight, + borderBox.fBottom - borderBottom); + } + else if (style.backgroundOrigin().isContentBox()) + { + // For content-box, subtract border and padding widths + float borderTop = fragment.border().top(); + float borderRight = fragment.border().right(); + float borderBottom = fragment.border().bottom(); + float borderLeft = fragment.border().left(); + + float paddingTop = fragment.padding().top(); + float paddingRight = fragment.padding().right(); + float paddingBottom = fragment.padding().bottom(); + float paddingLeft = fragment.padding().left(); + + return SkRect::MakeLTRB( + borderBox.fLeft + borderLeft + paddingLeft, + borderBox.fTop + borderTop + paddingTop, + borderBox.fRight - borderRight - paddingRight, + borderBox.fBottom - borderBottom - paddingBottom); + } + + // Default to border-box return borderBox; } - else if (style.backgroundOrigin().isPaddingBox()) - { - // For padding-box, subtract border widths - float borderTop = fragment.border().top(); - float borderRight = fragment.border().right(); - float borderBottom = fragment.border().bottom(); - float borderLeft = fragment.border().left(); - - return SkRect::MakeLTRB( - borderBox.fLeft + borderLeft, - borderBox.fTop + borderTop, - borderBox.fRight - borderRight, - borderBox.fBottom - borderBottom); - } - else if (style.backgroundOrigin().isContentBox()) - { - // For content-box, subtract border and padding widths - float borderTop = fragment.border().top(); - float borderRight = fragment.border().right(); - float borderBottom = fragment.border().bottom(); - float borderLeft = fragment.border().left(); - - float paddingTop = fragment.padding().top(); - float paddingRight = fragment.padding().right(); - float paddingBottom = fragment.padding().bottom(); - float paddingLeft = fragment.padding().left(); - - return SkRect::MakeLTRB( - borderBox.fLeft + borderLeft + paddingLeft, - borderBox.fTop + borderTop + paddingTop, - borderBox.fRight - borderRight - paddingRight, - borderBox.fBottom - borderBottom - paddingBottom); - } - // Default to border-box - return borderBox; - } + // Helper function to calculate background size based on background-size property + SkSize calculateBackgroundSize(const sk_sp &image, + const SkRect &positioningArea, + const ComputedStyle &style) + { + if (!image) + return SkSize::Make(0, 0); - // Helper function to calculate background size based on background-size property - SkSize calculateBackgroundSize(const sk_sp &image, - const SkRect &positioningArea, - const ComputedStyle &style) - { - if (!image) - return SkSize::Make(0, 0); + float intrinsicWidth = static_cast(image->width()); + float intrinsicHeight = static_cast(image->height()); + float areaWidth = positioningArea.width(); + float areaHeight = positioningArea.height(); - float intrinsicWidth = static_cast(image->width()); - float intrinsicHeight = static_cast(image->height()); - float areaWidth = positioningArea.width(); - float areaHeight = positioningArea.height(); + const auto &backgroundSize = style.backgroundSize(); - const auto &backgroundSize = style.backgroundSize(); + if (backgroundSize.isAuto()) + { + // Use intrinsic dimensions + return SkSize::Make(intrinsicWidth, intrinsicHeight); + } + else if (backgroundSize.isCover()) + { + // Scale to cover entire area, maintaining aspect ratio + float scaleX = areaWidth / intrinsicWidth; + float scaleY = areaHeight / intrinsicHeight; + float scale = std::max(scaleX, scaleY); + return SkSize::Make(intrinsicWidth * scale, intrinsicHeight * scale); + } + else if (backgroundSize.isContain()) + { + // Scale to fit within area, maintaining aspect ratio + float scaleX = areaWidth / intrinsicWidth; + float scaleY = areaHeight / intrinsicHeight; + float scale = std::min(scaleX, scaleY); + return SkSize::Make(intrinsicWidth * scale, intrinsicHeight * scale); + } + else if (backgroundSize.isLength()) + { + // Use specified length, maintaining aspect ratio + float length = backgroundSize.getWidth(); + float scale = length / intrinsicWidth; + return SkSize::Make(length, intrinsicHeight * scale); + } + else if (backgroundSize.isPercentage()) + { + // Use percentage of positioning area, maintaining aspect ratio + float percentage = backgroundSize.getWidth() / 100.0f; + float width = areaWidth * percentage; + float scale = width / intrinsicWidth; + return SkSize::Make(width, intrinsicHeight * scale); + } + else if (backgroundSize.isTwoValues()) + { + // Use specified width and height + return SkSize::Make(backgroundSize.getWidth(), backgroundSize.getHeight()); + } - if (backgroundSize.isAuto()) - { - // Use intrinsic dimensions + // Fallback to auto return SkSize::Make(intrinsicWidth, intrinsicHeight); } - else if (backgroundSize.isCover()) - { - // Scale to cover entire area, maintaining aspect ratio - float scaleX = areaWidth / intrinsicWidth; - float scaleY = areaHeight / intrinsicHeight; - float scale = std::max(scaleX, scaleY); - return SkSize::Make(intrinsicWidth * scale, intrinsicHeight * scale); - } - else if (backgroundSize.isContain()) - { - // Scale to fit within area, maintaining aspect ratio - float scaleX = areaWidth / intrinsicWidth; - float scaleY = areaHeight / intrinsicHeight; - float scale = std::min(scaleX, scaleY); - return SkSize::Make(intrinsicWidth * scale, intrinsicHeight * scale); - } - else if (backgroundSize.isLength()) - { - // Use specified length, maintaining aspect ratio - float length = backgroundSize.getWidth(); - float scale = length / intrinsicWidth; - return SkSize::Make(length, intrinsicHeight * scale); - } - else if (backgroundSize.isPercentage()) - { - // Use percentage of positioning area, maintaining aspect ratio - float percentage = backgroundSize.getWidth() / 100.0f; - float width = areaWidth * percentage; - float scale = width / intrinsicWidth; - return SkSize::Make(width, intrinsicHeight * scale); - } - else if (backgroundSize.isTwoValues()) - { - // Use specified width and height - return SkSize::Make(backgroundSize.getWidth(), backgroundSize.getHeight()); - } - - // Fallback to auto - return SkSize::Make(intrinsicWidth, intrinsicHeight); - } - // Helper function to calculate background position based on background-position property - SkPoint calculateBackgroundPosition(const SkSize &imageSize, - const SkRect &positioningArea, - const ComputedStyle &style) - { - const auto &backgroundPosition = style.backgroundPosition(); - float areaWidth = positioningArea.width(); - float areaHeight = positioningArea.height(); - - if (backgroundPosition.isCenter()) - { - // Center both horizontally and vertically - float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; - float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isLeft()) - { - // Left edge horizontally, center vertically - float x = positioningArea.fLeft; - float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isRight()) - { - // Right edge horizontally, center vertically - // Ensure precise alignment to the right edge - float x = positioningArea.fRight - imageSize.width(); - float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isTop()) - { - // Center horizontally, top edge vertically - float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; - float y = positioningArea.fTop; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isBottom()) + // Helper function to calculate background position based on background-position property + SkPoint calculateBackgroundPosition(const SkSize &imageSize, + const SkRect &positioningArea, + const ComputedStyle &style) { - // Center horizontally, bottom edge vertically - // Ensure precise alignment to the bottom edge - float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; - float y = positioningArea.fBottom - imageSize.height(); - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isLength()) - { - // Use specified length for x, center vertically - float x = positioningArea.fLeft + backgroundPosition.getX(); - float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isPercentage()) - { - // Use percentage for x, center vertically - float percentage = backgroundPosition.getX() / 100.0f; - float x = positioningArea.fLeft + (areaWidth - imageSize.width()) * percentage; - float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isTwoValues()) - { - // Use specified x and y values - // Handle potential percentage values that may be parsed as lengths - float xValue = backgroundPosition.getX(); - float yValue = backgroundPosition.getY(); + const auto &backgroundPosition = style.backgroundPosition(); + float areaWidth = positioningArea.width(); + float areaHeight = positioningArea.height(); - // Detect common percentage values that might be incorrectly parsed as lengths: - // Focus on the most problematic cases: 0%, 50%, 100% and quarter values - const float epsilon = 0.001f; - auto isNearValue = [epsilon](float val, float target) + if (backgroundPosition.isCenter()) { - return std::abs(val - target) < epsilon; - }; - - // Check for the most common percentage values that cause alignment issues - auto looksLikePercentage = [&isNearValue](float value) + // Center both horizontally and vertically + float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; + float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; + return SkPoint::Make(x, y); + } + else if (backgroundPosition.isLeft()) { - return (value >= 0.0f && value <= 100.0f) && - (isNearValue(value, 0.0f) || isNearValue(value, 25.0f) || - isNearValue(value, 50.0f) || isNearValue(value, 75.0f) || - isNearValue(value, 100.0f)); - }; - - bool xLooksLikePercentage = looksLikePercentage(xValue); - bool yLooksLikePercentage = looksLikePercentage(yValue); - - float x, y; - - if (xLooksLikePercentage) + // Left edge horizontally, center vertically + float x = positioningArea.fLeft; + float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; + return SkPoint::Make(x, y); + } + else if (backgroundPosition.isRight()) { - // Treat as percentage for horizontal positioning - float xPercentage = xValue / 100.0f; - x = positioningArea.fLeft + xPercentage * (areaWidth - imageSize.width()); + // Right edge horizontally, center vertically + // Ensure precise alignment to the right edge + float x = positioningArea.fRight - imageSize.width(); + float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; + return SkPoint::Make(x, y); } - else + else if (backgroundPosition.isTop()) { - // Treat as length value - x = positioningArea.fLeft + xValue; + // Center horizontally, top edge vertically + float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; + float y = positioningArea.fTop; + return SkPoint::Make(x, y); } - - if (yLooksLikePercentage) + else if (backgroundPosition.isBottom()) { - // Treat as percentage for vertical positioning - float yPercentage = yValue / 100.0f; - y = positioningArea.fTop + yPercentage * (areaHeight - imageSize.height()); + // Center horizontally, bottom edge vertically + // Ensure precise alignment to the bottom edge + float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; + float y = positioningArea.fBottom - imageSize.height(); + return SkPoint::Make(x, y); } - else + else if (backgroundPosition.isLength()) { - // Treat as length value - y = positioningArea.fTop + yValue; + // Use specified length for x, center vertically + float x = positioningArea.fLeft + backgroundPosition.getX(); + float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; + return SkPoint::Make(x, y); } + else if (backgroundPosition.isPercentage()) + { + // Use percentage for x, center vertically + float percentage = backgroundPosition.getX() / 100.0f; + float x = positioningArea.fLeft + (areaWidth - imageSize.width()) * percentage; + float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; + return SkPoint::Make(x, y); + } + else if (backgroundPosition.isTwoValues()) + { + // Use specified x and y values + // Handle potential percentage values that may be parsed as lengths + float xValue = backgroundPosition.getX(); + float yValue = backgroundPosition.getY(); + + // Detect common percentage values that might be incorrectly parsed as lengths: + // Focus on the most problematic cases: 0%, 50%, 100% and quarter values + const float epsilon = 0.001f; + auto isNearValue = [epsilon](float val, float target) + { + return std::abs(val - target) < epsilon; + }; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isThreeValues()) - { - // Handle 3-value syntax: keyword offset keyword - float x, y; - - auto hKeyword = backgroundPosition.getHorizontalKeyword(); - auto vKeyword = backgroundPosition.getVerticalKeyword(); - float hOffset = backgroundPosition.getHorizontalOffset(); - float vOffset = backgroundPosition.getVerticalOffset(); - - using Keyword = client_cssom::values::generics::BackgroundPositionKeyword; - - // Calculate horizontal position - if (hKeyword == Keyword::kLeftKeyword) - x = positioningArea.fLeft + hOffset; - else if (hKeyword == Keyword::kRightKeyword) - x = positioningArea.fRight - imageSize.width() - hOffset; - else if (hKeyword == Keyword::kCenterKeyword) - x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f + hOffset; - else - x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; // Default to center - - // Calculate vertical position - if (vKeyword == Keyword::kTopKeyword) - y = positioningArea.fTop + vOffset; - else if (vKeyword == Keyword::kBottomKeyword) - y = positioningArea.fBottom - imageSize.height() - vOffset; - else if (vKeyword == Keyword::kCenterKeyword) - y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f + vOffset; - else - y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; // Default to center + // Check for the most common percentage values that cause alignment issues + auto looksLikePercentage = [&isNearValue](float value) + { + return (value >= 0.0f && value <= 100.0f) && + (isNearValue(value, 0.0f) || isNearValue(value, 25.0f) || + isNearValue(value, 50.0f) || isNearValue(value, 75.0f) || + isNearValue(value, 100.0f)); + }; - return SkPoint::Make(x, y); - } - else if (backgroundPosition.isFourValues()) - { - // Handle 4-value syntax: keyword offset keyword offset - float x, y; - - auto hKeyword = backgroundPosition.getHorizontalKeyword(); - auto vKeyword = backgroundPosition.getVerticalKeyword(); - float hOffset = backgroundPosition.getHorizontalOffset(); - float vOffset = backgroundPosition.getVerticalOffset(); - - using Keyword = client_cssom::values::generics::BackgroundPositionKeyword; - - // Calculate horizontal position - if (hKeyword == Keyword::kLeftKeyword) - x = positioningArea.fLeft + hOffset; - else if (hKeyword == Keyword::kRightKeyword) - x = positioningArea.fRight - imageSize.width() - hOffset; - else if (hKeyword == Keyword::kCenterKeyword) - x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f + hOffset; - else - x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; // Default to center - - // Calculate vertical position - if (vKeyword == Keyword::kTopKeyword) - y = positioningArea.fTop + vOffset; - else if (vKeyword == Keyword::kBottomKeyword) - y = positioningArea.fBottom - imageSize.height() - vOffset; - else if (vKeyword == Keyword::kCenterKeyword) - y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f + vOffset; - else - y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; // Default to center + bool xLooksLikePercentage = looksLikePercentage(xValue); + bool yLooksLikePercentage = looksLikePercentage(yValue); - return SkPoint::Make(x, y); - } + float x, y; - // Fallback to center - float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; - float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; - return SkPoint::Make(x, y); - } + if (xLooksLikePercentage) + { + // Treat as percentage for horizontal positioning + float xPercentage = xValue / 100.0f; + x = positioningArea.fLeft + xPercentage * (areaWidth - imageSize.width()); + } + else + { + // Treat as length value + x = positioningArea.fLeft + xValue; + } - // Helper function to draw background image with repeat pattern + if (yLooksLikePercentage) + { + // Treat as percentage for vertical positioning + float yPercentage = yValue / 100.0f; + y = positioningArea.fTop + yPercentage * (areaHeight - imageSize.height()); + } + else + { + // Treat as length value + y = positioningArea.fTop + yValue; + } - void InitSystem::onExecute() - { - auto webContentCtx = getResource(); - auto meshes = getResource(); - auto materials = getResource(); - assert(webContentCtx != nullptr && - meshes != nullptr && - materials != nullptr); - - // Create mesh and material for web content rendering - auto material = Material::Make(); - auto mesh = MeshBuilder::CreateInstancedMesh("HTMLClassicMeshes", math::Dir3::Forward()); - mesh->enableDepthOnlyPass(); - - // Spawn the instanced mesh entity which will be used for rendering all web content elements. - webContentCtx->instancedMeshEntity_ = spawn(hierarchy::Root(true), - Mesh3d(meshes->add(mesh), false), - MeshMaterial3d(materials->add(material)), - Transform::FromXYZ(0.0f, 0.0f, 0.0f)); - } + return SkPoint::Make(x, y); + } + else if (backgroundPosition.isThreeValues()) + { + // Handle 3-value syntax: keyword offset keyword + float x, y; + + auto hKeyword = backgroundPosition.getHorizontalKeyword(); + auto vKeyword = backgroundPosition.getVerticalKeyword(); + float hOffset = backgroundPosition.getHorizontalOffset(); + float vOffset = backgroundPosition.getVerticalOffset(); + + using Keyword = client_cssom::values::generics::BackgroundPositionKeyword; + + // Calculate horizontal position + if (hKeyword == Keyword::kLeftKeyword) + x = positioningArea.fLeft + hOffset; + else if (hKeyword == Keyword::kRightKeyword) + x = positioningArea.fRight - imageSize.width() - hOffset; + else if (hKeyword == Keyword::kCenterKeyword) + x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f + hOffset; + else + x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; // Default to center + + // Calculate vertical position + if (vKeyword == Keyword::kTopKeyword) + y = positioningArea.fTop + vOffset; + else if (vKeyword == Keyword::kBottomKeyword) + y = positioningArea.fBottom - imageSize.height() - vOffset; + else if (vKeyword == Keyword::kCenterKeyword) + y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f + vOffset; + else + y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; // Default to center - void RenderContentBaseSystem::onExecute() - { - auto selectDirtyContent = [](const WebContent &content) -> bool - { - // Only select content that has a canvas and is dirty - return content.canvas() != nullptr && content.isContentDirty(); - }; - auto list = queryEntitiesWithComponent(selectDirtyContent); - if (list.size() == 0) - return; - - vector> futures; - futures.reserve(list.size()); - - // Lambda to render a single WebContent entity - auto renderContent = [this](ecs::EntityId entity, WebContent &content) - { - if (render(entity, content)) - content.setSurfaceDirty(true); - }; + return SkPoint::Make(x, y); + } + else if (backgroundPosition.isFourValues()) + { + // Handle 4-value syntax: keyword offset keyword offset + float x, y; + + auto hKeyword = backgroundPosition.getHorizontalKeyword(); + auto vKeyword = backgroundPosition.getVerticalKeyword(); + float hOffset = backgroundPosition.getHorizontalOffset(); + float vOffset = backgroundPosition.getVerticalOffset(); + + using Keyword = client_cssom::values::generics::BackgroundPositionKeyword; + + // Calculate horizontal position + if (hKeyword == Keyword::kLeftKeyword) + x = positioningArea.fLeft + hOffset; + else if (hKeyword == Keyword::kRightKeyword) + x = positioningArea.fRight - imageSize.width() - hOffset; + else if (hKeyword == Keyword::kCenterKeyword) + x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f + hOffset; + else + x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; // Default to center + + // Calculate vertical position + if (vKeyword == Keyword::kTopKeyword) + y = positioningArea.fTop + vOffset; + else if (vKeyword == Keyword::kBottomKeyword) + y = positioningArea.fBottom - imageSize.height() - vOffset; + else if (vKeyword == Keyword::kCenterKeyword) + y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f + vOffset; + else + y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; // Default to center - // Schedule rendering tasks for each dirty WebContent entity - for (auto &item : list) - { - ecs::EntityId entity = item.first; - WebContent &content = *item.second; - futures.emplace_back(async(launch::async, [&renderContent, entity, &content]() - { renderContent(entity, content); })); - } + return SkPoint::Make(x, y); + } - // Wait for all rendering tasks to complete - for (auto &future : futures) - future.wait(); - } + // Fallback to center + float x = positioningArea.fLeft + (areaWidth - imageSize.width()) / 2.0f; + float y = positioningArea.fTop + (areaHeight - imageSize.height()) / 2.0f; + return SkPoint::Make(x, y); + } - // Create a gradient shader based on the computed image and rounded rectangle. - sk_sp createGradientShader(const computed::Image &, const SkRRect &); + // Helper function to draw background image with repeat pattern - // Helper method to convert LengthPercentage to position (0.0 to 1.0) - float lengthPercentageToPosition(const computed::LengthPercentage &lengthPercentage, float totalLength) - { - if (lengthPercentage.isPercentage()) + void InitSystem::onExecute() { - return lengthPercentage.getPercentage().value(); - } - else if (lengthPercentage.isLength()) - { - // Convert length to position relative to total length - float lengthPx = lengthPercentage.getLength().px(); - return totalLength > 0 ? std::clamp(lengthPx / totalLength, 0.0f, 1.0f) : 0.0f; + auto webContentCtx = getResource(); + auto meshes = getResource(); + auto materials = getResource(); + assert(webContentCtx != nullptr && + meshes != nullptr && + materials != nullptr); + + // Create mesh and material for web content rendering + auto material = Material::Make(); + auto mesh = MeshBuilder::CreateInstancedMesh("HTMLClassicMeshes", math::Dir3::Forward()); + mesh->enableDepthOnlyPass(); + + // Spawn the instanced mesh entity which will be used for rendering all web content elements. + webContentCtx->instancedMeshEntity_ = spawn(hierarchy::Root(true), + Mesh3d(meshes->add(mesh), false), + MeshMaterial3d(materials->add(material)), + Transform::FromXYZ(0.0f, 0.0f, 0.0f)); } - else + + void RenderContentBaseSystem::onExecute() { - return 0.0f; // Fallback for calc or other types + auto selectDirtyContent = [](const WebContent &content) -> bool + { + // Only select content that has a canvas and is dirty + return content.canvas() != nullptr && content.isContentDirty(); + }; + auto list = queryEntitiesWithComponent(selectDirtyContent); + if (list.size() == 0) + return; + + vector> futures; + futures.reserve(list.size()); + + // Lambda to render a single WebContent entity + auto renderContent = [this](ecs::EntityId entity, WebContent &content) + { + if (render(entity, content)) + content.setSurfaceDirty(true); + }; + + // Schedule rendering tasks for each dirty WebContent entity + for (auto &item : list) + { + ecs::EntityId entity = item.first; + WebContent &content = *item.second; + futures.emplace_back(async(launch::async, [&renderContent, entity, &content]() + { renderContent(entity, content); })); + } + + // Wait for all rendering tasks to complete + for (auto &future : futures) + future.wait(); } - } - // Extract colors and positions from gradient items - void extractGradientStops(const vector &items, - float totalLength, - vector &colors, - vector &positions) - { - colors.clear(); - positions.clear(); + // Create a gradient shader based on the computed image and rounded rectangle. + sk_sp createGradientShader(const computed::Image &, const SkRRect &); - float lastHintPosition = 0.0f; - for (const auto &item : items) + // Helper method to convert LengthPercentage to position (0.0 to 1.0) + float lengthPercentageToPosition(const computed::LengthPercentage &lengthPercentage, float totalLength) { - if (item.type == computed::GradientItem::kSimpleColorStop) + if (lengthPercentage.isPercentage()) { - const auto &colorStop = get(item.value); - colors.push_back(SkColor4f::FromColor(colorStop.color.resolveToAbsoluteColor())); - - // For simple color stops, distribute positions evenly if not already set - if (positions.empty()) - positions.push_back(0.0f); - else if (positions.size() == colors.size() - 1) - positions.push_back(1.0f); - else - positions.push_back((float)positions.size() / (colors.size() - 1)); + return lengthPercentage.getPercentage().value(); } - else if (item.type == computed::GradientItem::kComplexColorStop) + else if (lengthPercentage.isLength()) { - const auto &colorStop = get(item.value); - colors.push_back(SkColor4f::FromColor(colorStop.color.resolveToAbsoluteColor())); - - // Convert length_percentage to position (0.0 to 1.0) - float position = lengthPercentageToPosition(colorStop.length_percentage, totalLength); - positions.push_back(position); + // Convert length to position relative to total length + float lengthPx = lengthPercentage.getLength().px(); + return totalLength > 0 ? std::clamp(lengthPx / totalLength, 0.0f, 1.0f) : 0.0f; } - else if (item.type == computed::GradientItem::kInterpolationHint) + else { - const auto &hint = get(item.value); - - // InterpolationHint affects the transition between the previous and next color stops - // Store the hint position to potentially adjust gradient transitions - lastHintPosition = lengthPercentageToPosition(hint.length_percentage, totalLength); - - // Note: Skia doesn't directly support interpolation hints, but we store the position - // for potential future enhancement of gradient interpolation + return 0.0f; // Fallback for calc or other types } } - // Handle fallback cases - if (colors.empty()) - { - colors.push_back(SkColor4f::FromColor(SK_ColorTRANSPARENT)); - colors.push_back(SkColor4f::FromColor(SK_ColorTRANSPARENT)); - positions.push_back(0.0f); - positions.push_back(1.0f); - } - else if (colors.size() == 1) - { - colors.push_back(colors[0]); - positions.push_back(0.0f); - positions.push_back(1.0f); - } - else if (positions.size() != colors.size()) + // Extract colors and positions from gradient items + void extractGradientStops(const vector &items, + float totalLength, + vector &colors, + vector &positions) { + colors.clear(); positions.clear(); - for (size_t i = 0; i < colors.size(); ++i) - { - positions.push_back(colors.size() == 1 ? 0.0f : (float)i / (colors.size() - 1)); - } - } - } - // Create a linear gradient shader - sk_sp createLinearGradientShader(const computed::Gradient::LinearGradient *linearGradient, - const SkRRect &originalRRect, - SkTileMode tileMode) - { - // Calculate gradient direction points based on LineDirection - SkPoint pts[2]; - const SkRect &rect = originalRRect.rect(); + float lastHintPosition = 0.0f; + for (const auto &item : items) + { + if (item.type == computed::GradientItem::kSimpleColorStop) + { + const auto &colorStop = get(item.value); + colors.push_back(SkColor4f::FromColor(colorStop.color.resolveToAbsoluteColor())); + + // For simple color stops, distribute positions evenly if not already set + if (positions.empty()) + positions.push_back(0.0f); + else if (positions.size() == colors.size() - 1) + positions.push_back(1.0f); + else + positions.push_back((float)positions.size() / (colors.size() - 1)); + } + else if (item.type == computed::GradientItem::kComplexColorStop) + { + const auto &colorStop = get(item.value); + colors.push_back(SkColor4f::FromColor(colorStop.color.resolveToAbsoluteColor())); - switch (linearGradient->direction) - { - case generics::LineDirection::kToRight: - pts[0] = SkPoint::Make(rect.fLeft, rect.centerY()); - pts[1] = SkPoint::Make(rect.fRight, rect.centerY()); - break; - case generics::LineDirection::kToLeft: - pts[0] = SkPoint::Make(rect.fRight, rect.centerY()); - pts[1] = SkPoint::Make(rect.fLeft, rect.centerY()); - break; - case generics::LineDirection::kToBottom: - pts[0] = SkPoint::Make(rect.centerX(), rect.fTop); - pts[1] = SkPoint::Make(rect.centerX(), rect.fBottom); - break; - case generics::LineDirection::kToTop: - pts[0] = SkPoint::Make(rect.centerX(), rect.fBottom); - pts[1] = SkPoint::Make(rect.centerX(), rect.fTop); - break; - case generics::LineDirection::kToBottomRight: - pts[0] = SkPoint::Make(rect.fLeft, rect.fTop); - pts[1] = SkPoint::Make(rect.fRight, rect.fBottom); - break; - case generics::LineDirection::kToBottomLeft: - pts[0] = SkPoint::Make(rect.fRight, rect.fTop); - pts[1] = SkPoint::Make(rect.fLeft, rect.fBottom); - break; - case generics::LineDirection::kToTopRight: - pts[0] = SkPoint::Make(rect.fLeft, rect.fBottom); - pts[1] = SkPoint::Make(rect.fRight, rect.fTop); - break; - case generics::LineDirection::kToTopLeft: - pts[0] = SkPoint::Make(rect.fRight, rect.fBottom); - pts[1] = SkPoint::Make(rect.fLeft, rect.fTop); - break; - default: - // Default to left-to-right - pts[0] = SkPoint::Make(rect.fLeft, rect.centerY()); - pts[1] = SkPoint::Make(rect.fRight, rect.centerY()); - break; - } + // Convert length_percentage to position (0.0 to 1.0) + float position = lengthPercentageToPosition(colorStop.length_percentage, totalLength); + positions.push_back(position); + } + else if (item.type == computed::GradientItem::kInterpolationHint) + { + const auto &hint = get(item.value); - // Calculate the total length for position conversion - float totalLength = sqrt(pow(pts[1].fX - pts[0].fX, 2) + pow(pts[1].fY - pts[0].fY, 2)); + // InterpolationHint affects the transition between the previous and next color stops + // Store the hint position to potentially adjust gradient transitions + lastHintPosition = lengthPercentageToPosition(hint.length_percentage, totalLength); - // Extract colors and positions from gradient items - vector colors; - vector positions; - extractGradientStops(linearGradient->items, totalLength, colors, positions); - - return SkGradientShader::MakeLinear(pts, - colors.data(), - SkColorSpace::MakeSRGB(), - positions.data(), - colors.size(), - tileMode); - } + // Note: Skia doesn't directly support interpolation hints, but we store the position + // for potential future enhancement of gradient interpolation + } + } - // Create a radial gradient shader - sk_sp createRadialGradientShader(const computed::Gradient::RadialGradient *radialGradient, - const SkRRect &originalRRect, - SkTileMode tileMode) - { - const SkRect &rect = originalRRect.rect(); - SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY()); + // Handle fallback cases + if (colors.empty()) + { + colors.push_back(SkColor4f::FromColor(SK_ColorTRANSPARENT)); + colors.push_back(SkColor4f::FromColor(SK_ColorTRANSPARENT)); + positions.push_back(0.0f); + positions.push_back(1.0f); + } + else if (colors.size() == 1) + { + colors.push_back(colors[0]); + positions.push_back(0.0f); + positions.push_back(1.0f); + } + else if (positions.size() != colors.size()) + { + positions.clear(); + for (size_t i = 0; i < colors.size(); ++i) + { + positions.push_back(colors.size() == 1 ? 0.0f : (float)i / (colors.size() - 1)); + } + } + } - // Calculate radius based on size and shape - SkScalar radius; - switch (radialGradient->size) + // Create a linear gradient shader + sk_sp createLinearGradientShader(const computed::Gradient::LinearGradient *linearGradient, + const SkRRect &originalRRect, + SkTileMode tileMode) { - case generics::RadialGradientSize::kClosestSide: - radius = min(rect.width(), rect.height()) / 2.0f; - break; - case generics::RadialGradientSize::kFarthestSide: - radius = max(rect.width(), rect.height()) / 2.0f; - break; - case generics::RadialGradientSize::kClosestCorner: - radius = sqrt(pow(min(rect.width(), rect.height()) / 2.0f, 2) * 2); - break; - case generics::RadialGradientSize::kFarthestCorner: - default: - radius = sqrt(pow(rect.width() / 2.0f, 2) + pow(rect.height() / 2.0f, 2)); - break; - } + // Calculate gradient direction points based on LineDirection + SkPoint pts[2]; + const SkRect &rect = originalRRect.rect(); - // Extract colors and positions from gradient items - vector colors; - vector positions; - extractGradientStops(radialGradient->items, radius, colors, positions); - - return SkGradientShader::MakeRadial(center, - radius, - colors.data(), - SkColorSpace::MakeSRGB(), - positions.data(), - colors.size(), - tileMode); - } + switch (linearGradient->direction) + { + case generics::LineDirection::kToRight: + pts[0] = SkPoint::Make(rect.fLeft, rect.centerY()); + pts[1] = SkPoint::Make(rect.fRight, rect.centerY()); + break; + case generics::LineDirection::kToLeft: + pts[0] = SkPoint::Make(rect.fRight, rect.centerY()); + pts[1] = SkPoint::Make(rect.fLeft, rect.centerY()); + break; + case generics::LineDirection::kToBottom: + pts[0] = SkPoint::Make(rect.centerX(), rect.fTop); + pts[1] = SkPoint::Make(rect.centerX(), rect.fBottom); + break; + case generics::LineDirection::kToTop: + pts[0] = SkPoint::Make(rect.centerX(), rect.fBottom); + pts[1] = SkPoint::Make(rect.centerX(), rect.fTop); + break; + case generics::LineDirection::kToBottomRight: + pts[0] = SkPoint::Make(rect.fLeft, rect.fTop); + pts[1] = SkPoint::Make(rect.fRight, rect.fBottom); + break; + case generics::LineDirection::kToBottomLeft: + pts[0] = SkPoint::Make(rect.fRight, rect.fTop); + pts[1] = SkPoint::Make(rect.fLeft, rect.fBottom); + break; + case generics::LineDirection::kToTopRight: + pts[0] = SkPoint::Make(rect.fLeft, rect.fBottom); + pts[1] = SkPoint::Make(rect.fRight, rect.fTop); + break; + case generics::LineDirection::kToTopLeft: + pts[0] = SkPoint::Make(rect.fRight, rect.fBottom); + pts[1] = SkPoint::Make(rect.fLeft, rect.fTop); + break; + default: + // Default to left-to-right + pts[0] = SkPoint::Make(rect.fLeft, rect.centerY()); + pts[1] = SkPoint::Make(rect.fRight, rect.centerY()); + break; + } - // Create a gradient shader from computed image value - sk_sp createGradientShader(const computed::Image &image, const SkRRect &originalRRect) - { - if (!image.isGradient()) - return nullptr; + // Calculate the total length for position conversion + float totalLength = sqrt(pow(pts[1].fX - pts[0].fX, 2) + pow(pts[1].fY - pts[0].fY, 2)); - SkTileMode tileMode = image.isGradientRepeating() ? SkTileMode::kRepeat : SkTileMode::kClamp; + // Extract colors and positions from gradient items + vector colors; + vector positions; + extractGradientStops(linearGradient->items, totalLength, colors, positions); - // Handle linear gradient - const auto *linearGradient = image.getLinearGradient(); - if (linearGradient) - { - return createLinearGradientShader(linearGradient, originalRRect, tileMode); + return SkGradientShader::MakeLinear(pts, + colors.data(), + SkColorSpace::MakeSRGB(), + positions.data(), + colors.size(), + tileMode); } - // Handle radial gradient - const auto *radialGradient = image.getRadialGradient(); - if (radialGradient) + // Create a radial gradient shader + sk_sp createRadialGradientShader(const computed::Gradient::RadialGradient *radialGradient, + const SkRRect &originalRRect, + SkTileMode tileMode) { - return createRadialGradientShader(radialGradient, originalRRect, tileMode); - } + const SkRect &rect = originalRRect.rect(); + SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY()); - // TODO: Implement conic gradient support - return nullptr; - } + // Calculate radius based on size and shape + SkScalar radius; + switch (radialGradient->size) + { + case generics::RadialGradientSize::kClosestSide: + radius = min(rect.width(), rect.height()) / 2.0f; + break; + case generics::RadialGradientSize::kFarthestSide: + radius = max(rect.width(), rect.height()) / 2.0f; + break; + case generics::RadialGradientSize::kClosestCorner: + radius = sqrt(pow(min(rect.width(), rect.height()) / 2.0f, 2) * 2); + break; + case generics::RadialGradientSize::kFarthestCorner: + default: + radius = sqrt(pow(rect.width() / 2.0f, 2) + pow(rect.height() / 2.0f, 2)); + break; + } - // Compute the radius for a specific corner of the rounded rectangle. - optional computeRoundedRectRadius(const SkRect &rect, - const client_cssom::ComputedStyle &style, - const BorderCorner &corner) - { - string name = client_cssom::values::generics::to_string(corner); - if (!style.hasProperty(name)) - return nullopt; + // Extract colors and positions from gradient items + vector colors; + vector positions; + extractGradientStops(radialGradient->items, radius, colors, positions); - const auto &cornerRadius = style.borderRadius()[corner].lengthPercentage(); - if (cornerRadius.isPercentage()) - { - const auto &percentage = cornerRadius.toPercentage(); - return SkVector({percentage->computeWithBase(rect.width()), - percentage->computeWithBase(rect.height())}); - } - else if (cornerRadius.isLength()) - { - auto computedPixels = cornerRadius.getLength().px(); - return SkVector({computedPixels, computedPixels}); + return SkGradientShader::MakeRadial(center, + radius, + colors.data(), + SkColorSpace::MakeSRGB(), + positions.data(), + colors.size(), + tileMode); } - else - { - return nullopt; - } - } - bool shouldDrawRoundedRect(SkRRect &roundedRect, SkRect &rect, const client_cssom::ComputedStyle &style) - { - // Set the radius for all four corners. - auto borderTopLeftRadius = computeRoundedRectRadius(rect, style, BorderCorner::kTopLeft); - auto borderTopRightRadius = computeRoundedRectRadius(rect, style, BorderCorner::kTopRight); - auto borderBottomRightRadius = computeRoundedRectRadius(rect, style, BorderCorner::kBottomRight); - auto borderBottomLeftRadius = computeRoundedRectRadius(rect, style, BorderCorner::kBottomLeft); - - // Fast check for all zero radii. - if (borderTopLeftRadius == nullopt && - borderTopRightRadius == nullopt && - borderBottomRightRadius == nullopt && - borderBottomLeftRadius == nullopt) + // Create a gradient shader from computed image value + sk_sp createGradientShader(const computed::Image &image, const SkRRect &originalRRect) { - roundedRect.setRect(rect); - return false; - } + if (!image.isGradient()) + return nullptr; - static SkVector defaultRadius = {0.0f, 0.0f}; - SkVector radii[4] = { - borderTopLeftRadius.value_or(defaultRadius), - borderTopRightRadius.value_or(defaultRadius), - borderBottomRightRadius.value_or(defaultRadius), - borderBottomLeftRadius.value_or(defaultRadius)}; - roundedRect.setRectRadii(rect, radii); + SkTileMode tileMode = image.isGradientRepeating() ? SkTileMode::kRepeat : SkTileMode::kClamp; - // Check if the radii are all zero. - for (int i = 0; i < 4; i++) - { - if (radii[i].x() != 0.0f || radii[i].y() != 0.0f) - return true; - } + // Handle linear gradient + const auto *linearGradient = image.getLinearGradient(); + if (linearGradient) + { + return createLinearGradientShader(linearGradient, originalRRect, tileMode); + } - // All corners have zero radius, so no need to draw rounded rectangle. - return false; - } + // Handle radial gradient + const auto *radialGradient = image.getRadialGradient(); + if (radialGradient) + { + return createRadialGradientShader(radialGradient, originalRRect, tileMode); + } - void setBorderPaintEffect(SkPaint &paint, - client_cssom::values::computed::BorderSideStyle borderStyle, - float strokeWidth) - { - if (borderStyle.isNoneOrHidden()) - { - paint.setStrokeWidth(0); - paint.setPathEffect(nullptr); - return; + // TODO: Implement conic gradient support + return nullptr; } - paint.setStrokeWidth(strokeWidth); - if (borderStyle.isDashed()) - { - const SkScalar intervals[] = {10, 5}; - paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0)); - } - else if (borderStyle.isDotted()) + // Compute the radius for a specific corner of the rounded rectangle. + optional computeRoundedRectRadius(const SkRect &rect, + const client_cssom::ComputedStyle &style, + const BorderCorner &corner) { - const SkScalar intervals[] = {2, 5}; - paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0)); - } - else if (borderStyle.isSolid()) - { - paint.setPathEffect(nullptr); + string name = client_cssom::values::generics::to_string(corner); + if (!style.hasProperty(name)) + return nullopt; + + const auto &cornerRadius = style.borderRadius()[corner].lengthPercentage(); + if (cornerRadius.isPercentage()) + { + const auto &percentage = cornerRadius.toPercentage(); + return SkVector({percentage->computeWithBase(rect.width()), + percentage->computeWithBase(rect.height())}); + } + else if (cornerRadius.isLength()) + { + auto computedPixels = cornerRadius.getLength().px(); + return SkVector({computedPixels, computedPixels}); + } + else + { + return nullopt; + } } - } - // Should draw the border edge, and return the computed border width. - inline bool shouldDrawBorderEdge(const client_cssom::ComputedStyle &style, - const BorderEdge edge, - float &computedBorderWidth) - { - const auto &edgeWidth = style.borderWidth()[edge]; - const auto &edgeStyle = style.borderStyle()[edge]; + bool shouldDrawRoundedRect(SkRRect &roundedRect, SkRect &rect, const client_cssom::ComputedStyle &style) + { + // Set the radius for all four corners. + auto borderTopLeftRadius = computeRoundedRectRadius(rect, style, BorderCorner::kTopLeft); + auto borderTopRightRadius = computeRoundedRectRadius(rect, style, BorderCorner::kTopRight); + auto borderBottomRightRadius = computeRoundedRectRadius(rect, style, BorderCorner::kBottomRight); + auto borderBottomLeftRadius = computeRoundedRectRadius(rect, style, BorderCorner::kBottomLeft); - // Fast check for border style and width. - if (edgeStyle.isNoneOrHidden() || edgeWidth.isZero()) - return false; + // Fast check for all zero radii. + if (borderTopLeftRadius == nullopt && + borderTopRightRadius == nullopt && + borderBottomRightRadius == nullopt && + borderBottomLeftRadius == nullopt) + { + roundedRect.setRect(rect); + return false; + } - computedBorderWidth = edgeWidth.value; - return computedBorderWidth > 0.0f; - } + static SkVector defaultRadius = {0.0f, 0.0f}; + SkVector radii[4] = { + borderTopLeftRadius.value_or(defaultRadius), + borderTopRightRadius.value_or(defaultRadius), + borderBottomRightRadius.value_or(defaultRadius), + borderBottomLeftRadius.value_or(defaultRadius)}; + roundedRect.setRectRadii(rect, radii); - bool drawBorders(SkCanvas *canvas, - SkRRect &roundedRect, - const client_layout::Fragment &fragment, - const client_cssom::ComputedStyle &style) - { - bool hasBorders = false; - SkPaint paint; - paint.setStyle(SkPaint::kStroke_Style); - paint.setAntiAlias(true); + // Check if the radii are all zero. + for (int i = 0; i < 4; i++) + { + if (radii[i].x() != 0.0f || radii[i].y() != 0.0f) + return true; + } - float computedBorderWidth = 0.0f; - if (shouldDrawBorderEdge(style, BorderEdge::kTop, computedBorderWidth)) - { - const auto &edgeColor = style.borderColor()[BorderEdge::kTop]; - float halfBorderWidth = computedBorderWidth / 2.0f; - - paint.setColor(edgeColor.resolveToAbsoluteColor()); - setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kTop], halfBorderWidth * 2); - - SkPath path; - const SkRect &rect = roundedRect.rect(); - // Draw the left top corner - path.arcTo(SkRect::MakeXYWH(rect.fLeft + halfBorderWidth, - rect.fTop + halfBorderWidth, - roundedRect.radii(SkRRect::kUpperLeft_Corner).x() * 2, - roundedRect.radii(SkRRect::kUpperLeft_Corner).y() * 2), - 180.0f, - 90.0f, - false); - path.lineTo(rect.fRight - roundedRect.radii(SkRRect::kUpperRight_Corner).x() - halfBorderWidth, - rect.fTop + halfBorderWidth); - path.arcTo(SkRect::MakeXYWH(rect.fRight - roundedRect.radii(SkRRect::kUpperRight_Corner).x() * 2 - halfBorderWidth, - rect.fTop + halfBorderWidth, - roundedRect.radii(SkRRect::kUpperRight_Corner).x() * 2, - roundedRect.radii(SkRRect::kUpperRight_Corner).y() * 2), - 270.0f, - 90.0f, - false); - canvas->drawPath(path, paint); - hasBorders = true; - } - if (shouldDrawBorderEdge(style, BorderEdge::kRight, computedBorderWidth)) - { - const auto &edgeColor = style.borderColor()[BorderEdge::kRight]; - float halfBorderWidth = computedBorderWidth / 2.0f; - - paint.setColor(edgeColor.resolveToAbsoluteColor()); - setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kRight], halfBorderWidth * 2); - - SkPath path; - const SkRect &rect = roundedRect.rect(); - path.moveTo(rect.fRight - halfBorderWidth, - rect.fTop + roundedRect.radii(SkRRect::kUpperRight_Corner).y() + halfBorderWidth); - path.lineTo(rect.fRight - halfBorderWidth, - rect.fBottom - roundedRect.radii(SkRRect::kLowerRight_Corner).y() - halfBorderWidth); - canvas->drawPath(path, paint); - hasBorders = true; - } - if (shouldDrawBorderEdge(style, BorderEdge::kBottom, computedBorderWidth)) - { - const auto &edgeColor = style.borderColor()[BorderEdge::kBottom]; - float halfBorderWidth = computedBorderWidth / 2.0f; - - paint.setColor(edgeColor.resolveToAbsoluteColor()); - setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kBottom], halfBorderWidth * 2); - - SkPath path; - const SkRect &rect = roundedRect.rect(); - path.arcTo(SkRect::MakeXYWH(rect.fRight - roundedRect.radii(SkRRect::kLowerRight_Corner).x() * 2 - halfBorderWidth, - rect.fBottom - roundedRect.radii(SkRRect::kLowerRight_Corner).y() * 2 - halfBorderWidth, - roundedRect.radii(SkRRect::kLowerRight_Corner).x() * 2, - roundedRect.radii(SkRRect::kLowerRight_Corner).y() * 2), - 0.0f, - 90.0f, - false); - path.lineTo(rect.fLeft + roundedRect.radii(SkRRect::kLowerLeft_Corner).x() + halfBorderWidth, - rect.fBottom - halfBorderWidth); - path.arcTo(SkRect::MakeXYWH(rect.fLeft + halfBorderWidth, - rect.fBottom - roundedRect.radii(SkRRect::kLowerLeft_Corner).y() * 2 - halfBorderWidth, - roundedRect.radii(SkRRect::kLowerLeft_Corner).x() * 2, - roundedRect.radii(SkRRect::kLowerLeft_Corner).y() * 2), - 90.0f, - 90.0f, - false); - canvas->drawPath(path, paint); - hasBorders = true; + // All corners have zero radius, so no need to draw rounded rectangle. + return false; } - if (shouldDrawBorderEdge(style, BorderEdge::kLeft, computedBorderWidth)) + + void setBorderPaintEffect(SkPaint &paint, + client_cssom::values::computed::BorderSideStyle borderStyle, + float strokeWidth) { - const auto &edgeColor = style.borderColor()[BorderEdge::kLeft]; - float halfBorderWidth = computedBorderWidth / 2.0f; - - paint.setColor(edgeColor.resolveToAbsoluteColor()); - setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kLeft], halfBorderWidth * 2); - - SkPath path; - const SkRect &rect = roundedRect.rect(); - path.moveTo(rect.fLeft + halfBorderWidth, - rect.fBottom - roundedRect.radii(SkRRect::kLowerLeft_Corner).y() - halfBorderWidth); - path.lineTo(rect.fLeft + halfBorderWidth, - rect.fTop + roundedRect.radii(SkRRect::kUpperLeft_Corner).y() + halfBorderWidth); - canvas->drawPath(path, paint); - hasBorders = true; - } - return hasBorders; - } + if (borderStyle.isNoneOrHidden()) + { + paint.setStrokeWidth(0); + paint.setPathEffect(nullptr); + return; + } - bool RenderBackgroundSystem::render(ecs::EntityId entity, WebContent &content) - { - const ComputedStyle &style = content.style(); - const auto &fragment = content.fragment(); - if (!fragment.has_value()) // No layout, no rendering. - return false; + paint.setStrokeWidth(strokeWidth); + if (borderStyle.isDashed()) + { + const SkScalar intervals[] = {10, 5}; + paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0)); + } + else if (borderStyle.isDotted()) + { + const SkScalar intervals[] = {2, 5}; + paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0)); + } + else if (borderStyle.isSolid()) + { + paint.setPathEffect(nullptr); + } + } - auto canvas = content.canvas(); - canvas->clear(SK_ColorTRANSPARENT); + // Should draw the border edge, and return the computed border width. + inline bool shouldDrawBorderEdge(const client_cssom::ComputedStyle &style, + const BorderEdge edge, + float &computedBorderWidth) + { + const auto &edgeWidth = style.borderWidth()[edge]; + const auto &edgeStyle = style.borderStyle()[edge]; - float top = 0.0f; - float left = 0.0f; - float width = fragment->contentWidth(); - float height = fragment->contentHeight(); + // Fast check for border style and width. + if (edgeStyle.isNoneOrHidden() || edgeWidth.isZero()) + return false; - SkRect rect = SkRect::MakeXYWH(left, top, width, height); - SkRRect &roundedRect = content.rounded_rect_; - bool drawRoundedRect = shouldDrawRoundedRect(roundedRect, rect, style); - if (drawRoundedRect) - { - // TODO(yorkie): support radius for xy - content.setBorderRadius(roundedRect.radii(SkRRect::kUpperLeft_Corner).x(), - roundedRect.radii(SkRRect::kUpperRight_Corner).x(), - roundedRect.radii(SkRRect::kLowerRight_Corner).x(), - roundedRect.radii(SkRRect::kLowerLeft_Corner).x()); - } - else - { - content.resetBorderRadius(); + computedBorderWidth = edgeWidth.value; + return computedBorderWidth > 0.0f; } - ClippingArea clipInfo; - if (style.backgroundClip().isText()) + bool drawBorders(SkCanvas *canvas, + SkRRect &roundedRect, + const client_layout::Fragment &fragment, + const client_cssom::ComputedStyle &style) { - // Get the text content from the children of this entity. - string textContent; - auto childrenComponent = getComponentChecked(entity); - for (const auto &childEntity : childrenComponent.children()) + bool hasBorders = false; + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + + float computedBorderWidth = 0.0f; + if (shouldDrawBorderEdge(style, BorderEdge::kTop, computedBorderWidth)) { - auto textComponent = getComponent(childEntity); - if (textComponent != nullptr) - textContent += textComponent->content; + const auto &edgeColor = style.borderColor()[BorderEdge::kTop]; + float halfBorderWidth = computedBorderWidth / 2.0f; + + paint.setColor(edgeColor.resolveToAbsoluteColor()); + setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kTop], halfBorderWidth * 2); + + SkPath path; + const SkRect &rect = roundedRect.rect(); + // Draw the left top corner + path.arcTo(SkRect::MakeXYWH(rect.fLeft + halfBorderWidth, + rect.fTop + halfBorderWidth, + roundedRect.radii(SkRRect::kUpperLeft_Corner).x() * 2, + roundedRect.radii(SkRRect::kUpperLeft_Corner).y() * 2), + 180.0f, + 90.0f, + false); + path.lineTo(rect.fRight - roundedRect.radii(SkRRect::kUpperRight_Corner).x() - halfBorderWidth, + rect.fTop + halfBorderWidth); + path.arcTo(SkRect::MakeXYWH(rect.fRight - roundedRect.radii(SkRRect::kUpperRight_Corner).x() * 2 - halfBorderWidth, + rect.fTop + halfBorderWidth, + roundedRect.radii(SkRRect::kUpperRight_Corner).x() * 2, + roundedRect.radii(SkRRect::kUpperRight_Corner).y() * 2), + 270.0f, + 90.0f, + false); + canvas->drawPath(path, paint); + hasBorders = true; } - - auto textPath = createTextPath(textContent, content); - if (textPath.has_value()) - clipInfo = ClippingArea(textPath.value()); - else - clipInfo = ClippingArea(SkRRect::MakeEmpty()); // No text path, use empty clipping area. - } - else if (!style.backgroundClip().isBorderBox()) - { - SkRRect clippingArea = getBackgroundClippingArea(roundedRect, fragment.value(), style); - clipInfo = ClippingArea(clippingArea); + if (shouldDrawBorderEdge(style, BorderEdge::kRight, computedBorderWidth)) + { + const auto &edgeColor = style.borderColor()[BorderEdge::kRight]; + float halfBorderWidth = computedBorderWidth / 2.0f; + + paint.setColor(edgeColor.resolveToAbsoluteColor()); + setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kRight], halfBorderWidth * 2); + + SkPath path; + const SkRect &rect = roundedRect.rect(); + path.moveTo(rect.fRight - halfBorderWidth, + rect.fTop + roundedRect.radii(SkRRect::kUpperRight_Corner).y() + halfBorderWidth); + path.lineTo(rect.fRight - halfBorderWidth, + rect.fBottom - roundedRect.radii(SkRRect::kLowerRight_Corner).y() - halfBorderWidth); + canvas->drawPath(path, paint); + hasBorders = true; + } + if (shouldDrawBorderEdge(style, BorderEdge::kBottom, computedBorderWidth)) + { + const auto &edgeColor = style.borderColor()[BorderEdge::kBottom]; + float halfBorderWidth = computedBorderWidth / 2.0f; + + paint.setColor(edgeColor.resolveToAbsoluteColor()); + setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kBottom], halfBorderWidth * 2); + + SkPath path; + const SkRect &rect = roundedRect.rect(); + path.arcTo(SkRect::MakeXYWH(rect.fRight - roundedRect.radii(SkRRect::kLowerRight_Corner).x() * 2 - halfBorderWidth, + rect.fBottom - roundedRect.radii(SkRRect::kLowerRight_Corner).y() * 2 - halfBorderWidth, + roundedRect.radii(SkRRect::kLowerRight_Corner).x() * 2, + roundedRect.radii(SkRRect::kLowerRight_Corner).y() * 2), + 0.0f, + 90.0f, + false); + path.lineTo(rect.fLeft + roundedRect.radii(SkRRect::kLowerLeft_Corner).x() + halfBorderWidth, + rect.fBottom - halfBorderWidth); + path.arcTo(SkRect::MakeXYWH(rect.fLeft + halfBorderWidth, + rect.fBottom - roundedRect.radii(SkRRect::kLowerLeft_Corner).y() * 2 - halfBorderWidth, + roundedRect.radii(SkRRect::kLowerLeft_Corner).x() * 2, + roundedRect.radii(SkRRect::kLowerLeft_Corner).y() * 2), + 90.0f, + 90.0f, + false); + canvas->drawPath(path, paint); + hasBorders = true; + } + if (shouldDrawBorderEdge(style, BorderEdge::kLeft, computedBorderWidth)) + { + const auto &edgeColor = style.borderColor()[BorderEdge::kLeft]; + float halfBorderWidth = computedBorderWidth / 2.0f; + + paint.setColor(edgeColor.resolveToAbsoluteColor()); + setBorderPaintEffect(paint, style.borderStyle()[BorderEdge::kLeft], halfBorderWidth * 2); + + SkPath path; + const SkRect &rect = roundedRect.rect(); + path.moveTo(rect.fLeft + halfBorderWidth, + rect.fBottom - roundedRect.radii(SkRRect::kLowerLeft_Corner).y() - halfBorderWidth); + path.lineTo(rect.fLeft + halfBorderWidth, + rect.fTop + roundedRect.radii(SkRRect::kUpperLeft_Corner).y() + halfBorderWidth); + canvas->drawPath(path, paint); + hasBorders = true; + } + return hasBorders; } - if (style.hasBackgroundColor()) + bool RenderBackgroundSystem::render(ecs::EntityId entity, WebContent &content) { - auto color = style.backgroundColor().resolveToAbsoluteColor(); - content.setBackgroundColor(SkColor4f::FromColor(color)); - } + const ComputedStyle &style = content.style(); + const auto &fragment = content.fragment(); + if (!fragment.has_value()) // No layout, no rendering. + return false; - bool textureRequired = false; - drawBackground(canvas, - roundedRect, - clipInfo, - fragment.value(), - style, - textureRequired); - content.setTextureUsing(textureRequired); - return true; - } + auto canvas = content.canvas(); + canvas->clear(SK_ColorTRANSPARENT); - SkRRect RenderBackgroundSystem::getBackgroundClippingArea(const SkRRect &roundedRect, - const client_layout::Fragment &fragment, - const ComputedStyle &style) - { - const SkRect &borderBox = roundedRect.rect(); - SkRRect clippingArea = roundedRect; // Start with border-box + float top = 0.0f; + float left = 0.0f; + float width = fragment->contentWidth(); + float height = fragment->contentHeight(); - if (style.backgroundClip().isPaddingBox()) - { - // For padding-box, subtract border widths - float borderTop = fragment.border().top(); - float borderRight = fragment.border().right(); - float borderBottom = fragment.border().bottom(); - float borderLeft = fragment.border().left(); - - SkRect paddingRect = SkRect::MakeLTRB( - borderBox.fLeft + borderLeft, - borderBox.fTop + borderTop, - borderBox.fRight - borderRight, - borderBox.fBottom - borderBottom); - - // Adjust radii for the padding box - SkVector radii[4]; - for (int i = 0; i < 4; i++) + SkRect rect = SkRect::MakeXYWH(left, top, width, height); + SkRRect &roundedRect = content.rounded_rect_; + bool drawRoundedRect = shouldDrawRoundedRect(roundedRect, rect, style); + if (drawRoundedRect) { - SkVector originalRadius = roundedRect.radii(static_cast(i)); - // Reduce radii by border width (but don't go below 0) - float borderReduction = (i == 0 || i == 3) ? borderLeft : borderRight; // Simplified - radii[i] = SkVector{std::max(0.0f, originalRadius.x() - borderReduction), - std::max(0.0f, originalRadius.y() - borderReduction)}; + // TODO(yorkie): support radius for xy + content.setBorderRadius(roundedRect.radii(SkRRect::kUpperLeft_Corner).x(), + roundedRect.radii(SkRRect::kUpperRight_Corner).x(), + roundedRect.radii(SkRRect::kLowerRight_Corner).x(), + roundedRect.radii(SkRRect::kLowerLeft_Corner).x()); } - clippingArea.setRectRadii(paddingRect, radii); - } - else if (style.backgroundClip().isContentBox()) - { - // For content-box, subtract border and padding widths - float borderTop = fragment.border().top(); - float borderRight = fragment.border().right(); - float borderBottom = fragment.border().bottom(); - float borderLeft = fragment.border().left(); - - float paddingTop = fragment.padding().top(); - float paddingRight = fragment.padding().right(); - float paddingBottom = fragment.padding().bottom(); - float paddingLeft = fragment.padding().left(); - - SkRect contentRect = SkRect::MakeLTRB( - borderBox.fLeft + borderLeft + paddingLeft, - borderBox.fTop + borderTop + paddingTop, - borderBox.fRight - borderRight - paddingRight, - borderBox.fBottom - borderBottom - paddingBottom); - - // Adjust radii for the content box - SkVector radii[4]; - for (int i = 0; i < 4; i++) + else { - SkVector originalRadius = roundedRect.radii(static_cast(i)); - // Reduce radii by border and padding width (but don't go below 0) - float totalReduction = (i == 0 || i == 3) ? (borderLeft + paddingLeft) : (borderRight + paddingRight); // Simplified - radii[i] = SkVector{std::max(0.0f, originalRadius.x() - totalReduction), - std::max(0.0f, originalRadius.y() - totalReduction)}; + content.resetBorderRadius(); } - clippingArea.setRectRadii(contentRect, radii); - } - // For border-box (default) and text, return the original rounded rect - return clippingArea; - } + ClippingArea clipInfo; + if (style.backgroundClip().isText()) + { + // Get the text content from the children of this entity. + string textContent; + auto childrenComponent = getComponentChecked(entity); + for (const auto &childEntity : childrenComponent.children()) + { + auto textComponent = getComponent(childEntity); + if (textComponent != nullptr) + textContent += textComponent->content; + } - optional RenderBackgroundSystem::createTextPath(const std::string &textContent, - const WebContent &content) - { - if (textContent.empty()) - return nullopt; - - // Try to get the font and text style for path creation - const auto &fragment = content.fragment(); - if (!fragment.has_value()) - return nullopt; - - SkPath textPath; - auto clientContext = TrClientContextPerProcess::Get(); - - // Create paragraph to get text layout - sk_sp fontCollection = clientContext->getFontCacheManager(); - auto paragraphStyle = content.paragraphStyle(); - auto paragraphBuilder = ParagraphBuilder::make(paragraphStyle, fontCollection); - paragraphBuilder->pushStyle(paragraphStyle.getTextStyle()); - paragraphBuilder->addText(textContent.c_str(), textContent.size()); - paragraphBuilder->pop(); - - float layoutWidth = round(content.fragment()->contentWidth()) + 1.0f; - auto paragraph = paragraphBuilder->Build(); - paragraph->layout(layoutWidth); - - // Calculate the text offset within the fragment - // Text should be positioned in the content area (inside border and padding) - const auto &borderBox = content.fragment()->border(); - const auto &paddingBox = content.fragment()->padding(); - float offsetX = borderBox.left() + paddingBox.left(); - float offsetY = borderBox.top() + paddingBox.top(); - - // Use the paragraph visitor to extract glyph paths with proper font handling - // This approach correctly handles mixed CJK/English text by using the fonts - // that the paragraph system has already resolved for each glyph - auto addPath = [&textPath, offsetX, offsetY](int lineNumber, const Paragraph::VisitorInfo *info) - { - if (info == nullptr) + auto textPath = createTextPath(textContent, content); + if (textPath.has_value()) + clipInfo = ClippingArea(textPath.value()); + else + clipInfo = ClippingArea(SkRRect::MakeEmpty()); // No text path, use empty clipping area. + } + else if (!style.backgroundClip().isBorderBox()) { - // End of line marker, nothing to do - return; + SkRRect clippingArea = getBackgroundClippingArea(roundedRect, fragment.value(), style); + clipInfo = ClippingArea(clippingArea); } - // Extract glyph paths from the properly resolved fonts - for (int i = 0; i < info->count; ++i) + if (style.hasBackgroundColor()) { - SkPath glyphPath; - if (info->font.getPath(info->glyphs[i], &glyphPath)) + auto color = style.backgroundColor().resolveToAbsoluteColor(); + content.setBackgroundColor(SkColor4f::FromColor(color)); + } + + bool textureRequired = false; + drawBackground(canvas, + roundedRect, + clipInfo, + fragment.value(), + style, + textureRequired); + content.setTextureUsing(textureRequired); + return true; + } + + SkRRect RenderBackgroundSystem::getBackgroundClippingArea(const SkRRect &roundedRect, + const client_layout::Fragment &fragment, + const ComputedStyle &style) + { + const SkRect &borderBox = roundedRect.rect(); + SkRRect clippingArea = roundedRect; // Start with border-box + + if (style.backgroundClip().isPaddingBox()) + { + // For padding-box, subtract border widths + float borderTop = fragment.border().top(); + float borderRight = fragment.border().right(); + float borderBottom = fragment.border().bottom(); + float borderLeft = fragment.border().left(); + + SkRect paddingRect = SkRect::MakeLTRB( + borderBox.fLeft + borderLeft, + borderBox.fTop + borderTop, + borderBox.fRight - borderRight, + borderBox.fBottom - borderBottom); + + // Adjust radii for the padding box + SkVector radii[4]; + for (int i = 0; i < 4; i++) + { + SkVector originalRadius = roundedRect.radii(static_cast(i)); + // Reduce radii by border width (but don't go below 0) + float borderReduction = (i == 0 || i == 3) ? borderLeft : borderRight; // Simplified + radii[i] = SkVector{std::max(0.0f, originalRadius.x() - borderReduction), + std::max(0.0f, originalRadius.y() - borderReduction)}; + } + clippingArea.setRectRadii(paddingRect, radii); + } + else if (style.backgroundClip().isContentBox()) + { + // For content-box, subtract border and padding widths + float borderTop = fragment.border().top(); + float borderRight = fragment.border().right(); + float borderBottom = fragment.border().bottom(); + float borderLeft = fragment.border().left(); + + float paddingTop = fragment.padding().top(); + float paddingRight = fragment.padding().right(); + float paddingBottom = fragment.padding().bottom(); + float paddingLeft = fragment.padding().left(); + + SkRect contentRect = SkRect::MakeLTRB( + borderBox.fLeft + borderLeft + paddingLeft, + borderBox.fTop + borderTop + paddingTop, + borderBox.fRight - borderRight - paddingRight, + borderBox.fBottom - borderBottom - paddingBottom); + + // Adjust radii for the content box + SkVector radii[4]; + for (int i = 0; i < 4; i++) { - // Calculate the absolute position including the text offset within the fragment - SkMatrix transform = SkMatrix::Translate( - info->origin.x() + info->positions[i].x() + offsetX, - info->origin.y() + info->positions[i].y() + offsetY); - textPath.addPath(glyphPath, transform); + SkVector originalRadius = roundedRect.radii(static_cast(i)); + // Reduce radii by border and padding width (but don't go below 0) + float totalReduction = (i == 0 || i == 3) ? (borderLeft + paddingLeft) : (borderRight + paddingRight); // Simplified + radii[i] = SkVector{std::max(0.0f, originalRadius.x() - totalReduction), + std::max(0.0f, originalRadius.y() - totalReduction)}; } + clippingArea.setRectRadii(contentRect, radii); } - }; - paragraph->visit(addPath); - return textPath.isEmpty() - ? nullopt - : optional(textPath); - } + // For border-box (default) and text, return the original rounded rect + return clippingArea; + } - // Draw the background for a fragment, returning an optional SkPaint if a fill is drawn. - optional RenderBackgroundSystem::drawBackground(SkCanvas *canvas, - SkRRect &originalRRect, - ClippingArea &clipInfo, - const client_layout::Fragment &fragment, - const client_cssom::ComputedStyle &style, - bool &textureRequired) - { - optional fillPaint = nullopt; + optional RenderBackgroundSystem::createTextPath(const std::string &textContent, + const WebContent &content) + { + if (textContent.empty()) + return nullopt; - // Mark the texture as not required by default. - textureRequired = false; + // Try to get the font and text style for path creation + const auto &fragment = content.fragment(); + if (!fragment.has_value()) + return nullopt; - // If we have a clip path or rounded rect, we need to use texture. - if (!clipInfo.isEmpty()) - textureRequired = true; + SkPath textPath; + auto clientContext = TrClientContextPerProcess::Get(); - SkRRect roundedRect; - roundedRect.setRect(originalRRect.rect()); + // Create paragraph to get text layout + sk_sp fontCollection = clientContext->getFontCacheManager(); + auto paragraphStyle = content.paragraphStyle(); + auto paragraphBuilder = ParagraphBuilder::make(paragraphStyle, fontCollection); + paragraphBuilder->pushStyle(paragraphStyle.getTextStyle()); + paragraphBuilder->addText(textContent.c_str(), textContent.size()); + paragraphBuilder->pop(); - if (style.hasBackgroundColor()) - { - auto color = style.backgroundColor().resolveToAbsoluteColor(); - - fillPaint = make_optional(); - fillPaint->setColor(color); - fillPaint->setAntiAlias(true); - fillPaint->setStyle(SkPaint::kFill_Style); - drawRRect(canvas, - roundedRect, - fillPaint.value(), - clipInfo); + float layoutWidth = round(content.fragment()->contentWidth()) + 1.0f; + auto paragraph = paragraphBuilder->Build(); + paragraph->layout(layoutWidth); + + // Calculate the text offset within the fragment + // Text should be positioned in the content area (inside border and padding) + const auto &borderBox = content.fragment()->border(); + const auto &paddingBox = content.fragment()->padding(); + float offsetX = borderBox.left() + paddingBox.left(); + float offsetY = borderBox.top() + paddingBox.top(); + + // Use the paragraph visitor to extract glyph paths with proper font handling + // This approach correctly handles mixed CJK/English text by using the fonts + // that the paragraph system has already resolved for each glyph + auto addPath = [&textPath, offsetX, offsetY](int lineNumber, const Paragraph::VisitorInfo *info) + { + if (info == nullptr) + { + // End of line marker, nothing to do + return; + } + + // Extract glyph paths from the properly resolved fonts + for (int i = 0; i < info->count; ++i) + { + SkPath glyphPath; + if (info->font.getPath(info->glyphs[i], &glyphPath)) + { + // Calculate the absolute position including the text offset within the fragment + SkMatrix transform = SkMatrix::Translate( + info->origin.x() + info->positions[i].x() + offsetX, + info->origin.y() + info->positions[i].y() + offsetY); + textPath.addPath(glyphPath, transform); + } + } + }; + paragraph->visit(addPath); + + return textPath.isEmpty() + ? nullopt + : optional(textPath); } - if (style.hasBackgroundImage()) + // Draw the background for a fragment, returning an optional SkPaint if a fill is drawn. + optional RenderBackgroundSystem::drawBackground(SkCanvas *canvas, + SkRRect &originalRRect, + ClippingArea &clipInfo, + const client_layout::Fragment &fragment, + const client_cssom::ComputedStyle &style, + bool &textureRequired) { - // Init the fill paint if it hasn't been set yet. - if (!fillPaint.has_value()) - fillPaint = make_optional(); + optional fillPaint = nullopt; + + // Mark the texture as not required by default. + textureRequired = false; - // Reset the fill paint properties. - fillPaint->setColor(SK_ColorBLACK); - fillPaint->setAntiAlias(true); - fillPaint->setStyle(SkPaint::kFill_Style); + // If we have a clip path or rounded rect, we need to use texture. + if (!clipInfo.isEmpty()) + textureRequired = true; - // Set the blend mode for the paint if the background blend mode is not normal. - if (!style.backgroundBlendMode().isNormal()) - fillPaint->setBlendMode(style.backgroundBlendMode()); + SkRRect roundedRect; + roundedRect.setRect(originalRRect.rect()); - const auto &image = style.backgroundImage(); - if (image.isUrl()) + if (style.hasBackgroundColor()) { - if (image.isUrlImageLoaded()) - { - SkBitmap bitmap; - // TODO(yorkie): support decoding this async? - if (canvas::ImageCodec::Decode(image.getUrlImageData(), - nullptr, - bitmap, - image.getUrl())) - { - canvas->save(); - { - // Handle the clipping area if specified. - if (clipInfo.isPath()) - canvas->clipPath(clipInfo.path(), true); - else if (clipInfo.isRRect()) - canvas->clipRRect(clipInfo.roundedRect(), true); + auto color = style.backgroundColor().resolveToAbsoluteColor(); + + fillPaint = make_optional(); + fillPaint->setColor(color); + fillPaint->setAntiAlias(true); + fillPaint->setStyle(SkPaint::kFill_Style); + drawRRect(canvas, + roundedRect, + fillPaint.value(), + clipInfo); + } + + if (style.hasBackgroundImage()) + { + // Init the fill paint if it hasn't been set yet. + if (!fillPaint.has_value()) + fillPaint = make_optional(); - // Get the background positioning area based on background-origin - SkRect positioningArea = getBackgroundPositioningArea(roundedRect, fragment, style); + // Reset the fill paint properties. + fillPaint->setColor(SK_ColorBLACK); + fillPaint->setAntiAlias(true); + fillPaint->setStyle(SkPaint::kFill_Style); - // Get the repeatable area based on background-clip (same as clipping area) - SkRRect repeatableRRect = getBackgroundClippingArea(roundedRect, fragment, style); - SkRect repeatableArea = repeatableRRect.rect(); + // Set the blend mode for the paint if the background blend mode is not normal. + if (!style.backgroundBlendMode().isNormal()) + fillPaint->setBlendMode(style.backgroundBlendMode()); - drawImage(canvas, bitmap.asImage(), positioningArea, repeatableArea, fillPaint.value(), style); + const auto &image = style.backgroundImage(); + if (image.isUrl()) + { + if (image.isUrlImageLoaded()) + { + SkBitmap bitmap; + // TODO(yorkie): support decoding this async? + if (canvas::ImageCodec::Decode(image.getUrlImageData(), + nullptr, + bitmap, + image.getUrl())) + { + canvas->save(); + { + // Handle the clipping area if specified. + if (clipInfo.isPath()) + canvas->clipPath(clipInfo.path(), true); + else if (clipInfo.isRRect()) + canvas->clipRRect(clipInfo.roundedRect(), true); + + // Get the background positioning area based on background-origin + SkRect positioningArea = getBackgroundPositioningArea(roundedRect, fragment, style); + + // Get the repeatable area based on background-clip (same as clipping area) + SkRRect repeatableRRect = getBackgroundClippingArea(roundedRect, fragment, style); + SkRect repeatableArea = repeatableRRect.rect(); + + drawImage(canvas, bitmap.asImage(), positioningArea, repeatableArea, fillPaint.value(), style); + } + canvas->restore(); + textureRequired = true; } - canvas->restore(); - textureRequired = true; + } + else + { + // NOTE(yorkie): If the image is not loaded yet, just wait for the image to be loaded. } } - else + else if (image.isGradient()) { - // NOTE(yorkie): If the image is not loaded yet, just wait for the image to be loaded. - } - } - else if (image.isGradient()) - { - // Create gradient shader using the new helper method - sk_sp shader = createGradientShader(image, originalRRect); + // Create gradient shader using the new helper method + sk_sp shader = createGradientShader(image, originalRRect); - // Apply the shader if successfully created - if (shader) - { - fillPaint->setShader(shader); - drawRRect(canvas, - roundedRect, - fillPaint.value(), - clipInfo); - textureRequired = true; + // Apply the shader if successfully created + if (shader) + { + fillPaint->setShader(shader); + drawRRect(canvas, + roundedRect, + fillPaint.value(), + clipInfo); + textureRequired = true; + } } } + return fillPaint; } - return fillPaint; - } - void RenderBackgroundSystem::drawRRect(SkCanvas *canvas, - const SkRRect &roundedRect, - const SkPaint &paint, - const ClippingArea &clipInfo) - { - if (clipInfo.isEmpty()) + void RenderBackgroundSystem::drawRRect(SkCanvas *canvas, + const SkRRect &roundedRect, + const SkPaint &paint, + const ClippingArea &clipInfo) { - canvas->drawRRect(roundedRect, paint); + if (clipInfo.isEmpty()) + { + canvas->drawRRect(roundedRect, paint); + } + else + { + canvas->save(); + if (clipInfo.isPath()) + canvas->clipPath(clipInfo.path(), true); + else if (clipInfo.isRRect()) + canvas->clipRRect(clipInfo.roundedRect(), true); + canvas->drawRRect(roundedRect, paint); + canvas->restore(); + } } - else + + void RenderBackgroundSystem::drawImage(SkCanvas *canvas, + const sk_sp &image, + const SkRect &positioningArea, + const SkPaint &paint, + const ComputedStyle &style) { - canvas->save(); - if (clipInfo.isPath()) - canvas->clipPath(clipInfo.path(), true); - else if (clipInfo.isRRect()) - canvas->clipRRect(clipInfo.roundedRect(), true); - canvas->drawRRect(roundedRect, paint); - canvas->restore(); + // Use positioning area as repeatable area for backward compatibility + drawImage(canvas, image, positioningArea, positioningArea, paint, style); } - } - - void RenderBackgroundSystem::drawImage(SkCanvas *canvas, - const sk_sp &image, - const SkRect &positioningArea, - const SkPaint &paint, - const ComputedStyle &style) - { - // Use positioning area as repeatable area for backward compatibility - drawImage(canvas, image, positioningArea, positioningArea, paint, style); - } - void RenderBackgroundSystem::drawImage(SkCanvas *canvas, - const sk_sp &image, - const SkRect &positioningArea, - const SkRect &repeatableArea, - const SkPaint &paint, - const ComputedStyle &style) - { - if (!image) - return; + void RenderBackgroundSystem::drawImage(SkCanvas *canvas, + const sk_sp &image, + const SkRect &positioningArea, + const SkRect &repeatableArea, + const SkPaint &paint, + const ComputedStyle &style) + { + if (!image) + return; - // Calculate the background size based on background-size property - SkSize imageSize = calculateBackgroundSize(image, positioningArea, style); - float imageWidth = imageSize.width(); - float imageHeight = imageSize.height(); + // Calculate the background size based on background-size property + SkSize imageSize = calculateBackgroundSize(image, positioningArea, style); + float imageWidth = imageSize.width(); + float imageHeight = imageSize.height(); - // Calculate the background position based on background-position property - SkPoint imagePosition = calculateBackgroundPosition(imageSize, positioningArea, style); + // Calculate the background position based on background-position property + SkPoint imagePosition = calculateBackgroundPosition(imageSize, positioningArea, style); - if (style.backgroundRepeat().isRepeat()) - { - // Repeat both horizontally and vertically - // Start from the calculated position and tile in both directions - float startX = imagePosition.x(); - float startY = imagePosition.y(); + if (style.backgroundRepeat().isRepeat()) + { + // Repeat both horizontally and vertically + // Start from the calculated position and tile in both directions + float startX = imagePosition.x(); + float startY = imagePosition.y(); + + // Adjust start position to ensure full coverage of repeatable area + while (startX > repeatableArea.fLeft) + startX -= imageWidth; + while (startY > repeatableArea.fTop) + startY -= imageHeight; + + for (float y = startY; y < repeatableArea.fBottom; y += imageHeight) + { + for (float x = startX; x < repeatableArea.fRight; x += imageWidth) + { + SkRect destRect = SkRect::MakeXYWH(x, y, imageWidth, imageHeight); + // Clip to repeatable area + if (destRect.intersect(repeatableArea)) + { + // Calculate source rect proportionally + float srcLeft = (destRect.fLeft - x) / imageWidth * image->width(); + float srcTop = (destRect.fTop - y) / imageHeight * image->height(); + float srcRight = srcLeft + (destRect.width() / imageWidth * image->width()); + float srcBottom = srcTop + (destRect.height() / imageHeight * image->height()); + + SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); + canvas->drawImageRect(image, + srcRect, + destRect, + SkSamplingOptions(), + &paint, + SkCanvas::kStrict_SrcRectConstraint); + } + } + } + } + else if (style.backgroundRepeat().isRepeatX()) + { + // Repeat only horizontally + float startX = imagePosition.x(); + float y = imagePosition.y(); - // Adjust start position to ensure full coverage of repeatable area - while (startX > repeatableArea.fLeft) - startX -= imageWidth; - while (startY > repeatableArea.fTop) - startY -= imageHeight; + // Adjust start position to ensure full coverage of repeatable area + while (startX > repeatableArea.fLeft) + startX -= imageWidth; - for (float y = startY; y < repeatableArea.fBottom; y += imageHeight) - { for (float x = startX; x < repeatableArea.fRight; x += imageWidth) { SkRect destRect = SkRect::MakeXYWH(x, y, imageWidth, imageHeight); - // Clip to repeatable area if (destRect.intersect(repeatableArea)) { - // Calculate source rect proportionally float srcLeft = (destRect.fLeft - x) / imageWidth * image->width(); float srcTop = (destRect.fTop - y) / imageHeight * image->height(); float srcRight = srcLeft + (destRect.width() / imageWidth * image->width()); @@ -1247,26 +1280,49 @@ namespace builtin_scene::web_renderer } } } - } - else if (style.backgroundRepeat().isRepeatX()) - { - // Repeat only horizontally - float startX = imagePosition.x(); - float y = imagePosition.y(); + else if (style.backgroundRepeat().isRepeatY()) + { + // Repeat only vertically + float x = imagePosition.x(); + float startY = imagePosition.y(); - // Adjust start position to ensure full coverage of repeatable area - while (startX > repeatableArea.fLeft) - startX -= imageWidth; + // Adjust start position to ensure full coverage of repeatable area + while (startY > repeatableArea.fTop) + startY -= imageHeight; - for (float x = startX; x < repeatableArea.fRight; x += imageWidth) + for (float y = startY; y < repeatableArea.fBottom; y += imageHeight) + { + SkRect destRect = SkRect::MakeXYWH(x, y, imageWidth, imageHeight); + if (destRect.intersect(repeatableArea)) + { + float srcLeft = (destRect.fLeft - x) / imageWidth * image->width(); + float srcTop = (destRect.fTop - y) / imageHeight * image->height(); + float srcRight = srcLeft + (destRect.width() / imageWidth * image->width()); + float srcBottom = srcTop + (destRect.height() / imageHeight * image->height()); + + SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); + canvas->drawImageRect(image, + srcRect, + destRect, + SkSamplingOptions(), + &paint, + SkCanvas::kStrict_SrcRectConstraint); + } + } + } + else if (style.backgroundRepeat().isNoRepeat()) { - SkRect destRect = SkRect::MakeXYWH(x, y, imageWidth, imageHeight); + // No repeat - draw once at the calculated position and size + SkRect destRect = SkRect::MakeXYWH(imagePosition.x(), imagePosition.y(), imageWidth, imageHeight); if (destRect.intersect(repeatableArea)) { - float srcLeft = (destRect.fLeft - x) / imageWidth * image->width(); - float srcTop = (destRect.fTop - y) / imageHeight * image->height(); - float srcRight = srcLeft + (destRect.width() / imageWidth * image->width()); - float srcBottom = srcTop + (destRect.height() / imageHeight * image->height()); + // Calculate source rect proportionally + float scaleX = imageWidth / image->width(); + float scaleY = imageHeight / image->height(); + float srcLeft = (destRect.fLeft - imagePosition.x()) / scaleX; + float srcTop = (destRect.fTop - imagePosition.y()) / scaleY; + float srcRight = srcLeft + destRect.width() / scaleX; + float srcBottom = srcTop + destRect.height() / scaleY; SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); canvas->drawImageRect(image, @@ -1277,219 +1333,166 @@ namespace builtin_scene::web_renderer SkCanvas::kStrict_SrcRectConstraint); } } - } - else if (style.backgroundRepeat().isRepeatY()) - { - // Repeat only vertically - float x = imagePosition.x(); - float startY = imagePosition.y(); - - // Adjust start position to ensure full coverage of repeatable area - while (startY > repeatableArea.fTop) - startY -= imageHeight; - - for (float y = startY; y < repeatableArea.fBottom; y += imageHeight) + else { - SkRect destRect = SkRect::MakeXYWH(x, y, imageWidth, imageHeight); + // Default to no repeat for unsupported values (space, round) + SkRect destRect = SkRect::MakeXYWH(imagePosition.x(), imagePosition.y(), imageWidth, imageHeight); if (destRect.intersect(repeatableArea)) { - float srcLeft = (destRect.fLeft - x) / imageWidth * image->width(); - float srcTop = (destRect.fTop - y) / imageHeight * image->height(); - float srcRight = srcLeft + (destRect.width() / imageWidth * image->width()); - float srcBottom = srcTop + (destRect.height() / imageHeight * image->height()); - - SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); - canvas->drawImageRect(image, - srcRect, - destRect, - SkSamplingOptions(), - &paint, - SkCanvas::kStrict_SrcRectConstraint); + canvas->drawImageRect(image, destRect, SkSamplingOptions(), &paint); } } } - else if (style.backgroundRepeat().isNoRepeat()) + + bool RenderImageSystem::render(ecs::EntityId entity, WebContent &content) { - // No repeat - draw once at the calculated position and size - SkRect destRect = SkRect::MakeXYWH(imagePosition.x(), imagePosition.y(), imageWidth, imageHeight); - if (destRect.intersect(repeatableArea)) - { - // Calculate source rect proportionally - float scaleX = imageWidth / image->width(); - float scaleY = imageHeight / image->height(); - float srcLeft = (destRect.fLeft - imagePosition.x()) / scaleX; - float srcTop = (destRect.fTop - imagePosition.y()) / scaleY; - float srcRight = srcLeft + destRect.width() / scaleX; - float srcBottom = srcTop + destRect.height() / scaleY; - - SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); - canvas->drawImageRect(image, - srcRect, - destRect, - SkSamplingOptions(), - &paint, - SkCanvas::kStrict_SrcRectConstraint); + auto imageComponent = getComponent(entity); + if (imageComponent == nullptr || + !imageComponent->hasImageData()) + return false; + + // Disable using texture if the image is not visible. + if (!imageComponent->visible()) + { + content.setTextureUsing(false); + return true; } - } - else - { - // Default to no repeat for unsupported values (space, round) - SkRect destRect = SkRect::MakeXYWH(imagePosition.x(), imagePosition.y(), imageWidth, imageHeight); - if (destRect.intersect(repeatableArea)) + + sk_sp skImage = imageComponent->image(); + if (skImage == nullptr) { - canvas->drawImageRect(image, destRect, SkSamplingOptions(), &paint); + // Disable using texture if the image is failed to load. + content.setTextureUsing(false); + return true; } - } - } - bool RenderImageSystem::render(ecs::EntityId entity, WebContent &content) - { - auto imageComponent = getComponent(entity); - if (imageComponent == nullptr || - !imageComponent->hasImageData()) - return false; + SkCanvas *canvas = content.canvas(); + canvas->save(); + { + SkRRect &roundedRect = content.rounded_rect_; + canvas->clipRRect(roundedRect, true); - // Disable using texture if the image is not visible. - if (!imageComponent->visible()) - { - content.setTextureUsing(false); - return true; - } + SkPaint imagePaint; + imagePaint.setAntiAlias(true); + imagePaint.setStyle(SkPaint::kFill_Style); - sk_sp skImage = imageComponent->image(); - if (skImage == nullptr) - { - // Disable using texture if the image is failed to load. - content.setTextureUsing(false); + SkRect srcRect = SkRect::MakeWH(skImage->width(), skImage->height()); + SkRect dstRect = SkRect::MakeWH(content.logicalWidth(), content.logicalHeight()); + canvas->drawImageRect(skImage, + srcRect, + dstRect, + SkSamplingOptions(), + &imagePaint, + SkCanvas::kStrict_SrcRectConstraint); + } + canvas->restore(); + content.setTextureUsing(true); return true; } - SkCanvas *canvas = content.canvas(); - canvas->save(); + RenderTextSystem::RenderTextSystem() + : RenderContentBaseSystem() + , clientContext_(TrClientContextPerProcess::Get()) + , fontCollection_(clientContext_->getFontCacheManager()) + , paragraphBuilder_(nullptr) + , sdfGenerator_(text::sdf::SDFParams(6, 0.25f)) { - SkRRect &roundedRect = content.rounded_rect_; - canvas->clipRRect(roundedRect, true); - - SkPaint imagePaint; - imagePaint.setAntiAlias(true); - imagePaint.setStyle(SkPaint::kFill_Style); - - SkRect srcRect = SkRect::MakeWH(skImage->width(), skImage->height()); - SkRect dstRect = SkRect::MakeWH(content.logicalWidth(), content.logicalHeight()); - canvas->drawImageRect(skImage, - srcRect, - dstRect, - SkSamplingOptions(), - &imagePaint, - SkCanvas::kStrict_SrcRectConstraint); } - canvas->restore(); - content.setTextureUsing(true); - return true; - } - RenderTextSystem::RenderTextSystem() - : RenderContentBaseSystem() - , clientContext_(TrClientContextPerProcess::Get()) - , fontCollection_(clientContext_->getFontCacheManager()) - , paragraphBuilder_(nullptr) - , sdfGenerator_(text::sdf::SDFParams(6, 0.25f)) - { - } + bool RenderTextSystem::render(ecs::EntityId entity, WebContent &content) + { + auto textComponent = getComponent(entity); + if (textComponent == nullptr) [[unlikely]] + return false; - bool RenderTextSystem::render(ecs::EntityId entity, WebContent &content) - { - auto textComponent = getComponent(entity); - if (textComponent == nullptr) [[unlikely]] - return false; + string &text = textComponent->content; + SkCanvas *canvas = content.canvas(); + if (canvas == nullptr) [[unlikely]] + return false; - string &text = textComponent->content; - SkCanvas *canvas = content.canvas(); - if (canvas == nullptr) [[unlikely]] - return false; + // 1. Render text normally using the original paragraph rendering + { + auto paragraphStyle = content.paragraphStyle(); + auto paragraphBuilder = ParagraphBuilder::make(paragraphStyle, fontCollection_); + paragraphBuilder->pushStyle(paragraphStyle.getTextStyle()); + paragraphBuilder->addText(text.c_str(), text.size()); + paragraphBuilder->pop(); + + auto layoutWidth = round(getLayoutWidthForText(content)) + 1.0f; + auto paragraph = paragraphBuilder->Build(); + paragraph->layout(layoutWidth); + paragraph->paint(canvas, 0.0f, 0.0f); + } - // 1. Render text normally using the original paragraph rendering - { - auto paragraphStyle = content.paragraphStyle(); - auto paragraphBuilder = ParagraphBuilder::make(paragraphStyle, fontCollection_); - paragraphBuilder->pushStyle(paragraphStyle.getTextStyle()); - paragraphBuilder->addText(text.c_str(), text.size()); - paragraphBuilder->pop(); + // 2. generate SDF texture from the painted canvas for anti-aliasing + { + auto usingSdf = generateSignedDistanceOn(canvas); + content.setIsSDFTexture(usingSdf); + } - auto layoutWidth = round(getLayoutWidthForText(content)) + 1.0f; - auto paragraph = paragraphBuilder->Build(); - paragraph->layout(layoutWidth); - paragraph->paint(canvas, 0.0f, 0.0f); + // 3. Mark the content as using texture + content.setTextureUsing(true); + return true; } - // 2. generate SDF texture from the painted canvas for anti-aliasing + float RenderTextSystem::getLayoutWidthForText(WebContent &content) { - auto usingSdf = generateSignedDistanceOn(canvas); - content.setIsSDFTexture(usingSdf); + const auto &fragment = content.fragment(); + return fragment->contentWidth(); } - // 3. Mark the content as using texture - content.setTextureUsing(true); - return true; - } - - float RenderTextSystem::getLayoutWidthForText(WebContent &content) - { - const auto &fragment = content.fragment(); - return fragment->contentWidth(); - } - - bool RenderTextSystem::generateSignedDistanceOn(SkCanvas *canvas) - { - // Get the surface from the canvas to extract pixel data - auto surface = canvas->getSurface(); - if (!surface) - return false; + bool RenderTextSystem::generateSignedDistanceOn(SkCanvas *canvas) + { + // Get the surface from the canvas to extract pixel data + auto surface = canvas->getSurface(); + if (!surface) + return false; - // Get writable pixel data - SkPixmap pixmap; - if (!surface->peekPixels(&pixmap)) - return false; + // Get writable pixel data + SkPixmap pixmap; + if (!surface->peekPixels(&pixmap)) + return false; - unsigned char *pixels = (unsigned char *)pixmap.writable_addr(); - if (!pixels) - return false; + unsigned char *pixels = (unsigned char *)pixmap.writable_addr(); + if (!pixels) + return false; - int width = pixmap.width(); - int height = pixmap.height(); + int width = pixmap.width(); + int height = pixmap.height(); - // Use `SDFGenerator` to generate from the pixel data to the alpha channel. - bool success = sdfGenerator_.generateOnPixels(pixels, width, height); - return success; - } + // Use `SDFGenerator` to generate from the pixel data to the alpha channel. + bool success = sdfGenerator_.generateOnPixels(pixels, width, height); + return success; + } - void UpdateTextureSystem::onExecute() - { - auto material3d = getInstancedMeshComponent(); - assert(material3d != nullptr); + void UpdateTextureSystem::onExecute() + { + auto material3d = getInstancedMeshComponent(); + assert(material3d != nullptr); - auto webContentInstancedMaterial = material3d->material(); - if (webContentInstancedMaterial == nullptr) [[unlikely]] - return; + auto webContentInstancedMaterial = material3d->material(); + if (webContentInstancedMaterial == nullptr) [[unlikely]] + return; - auto selectContents = [](const WebContent &content) -> bool - { - return content.canvas() != nullptr && content.isSurfaceDirty(); - }; - auto list = queryEntitiesWithComponent(selectContents); - if (list.size() > 0) - { - for (auto &item : list) + auto selectContents = [](const WebContent &content) -> bool { - auto content = item.second; - // Use the same texture update method for both text and image content - auto status = webContentInstancedMaterial->updateTexture(*content); - if (status != materials::WebContentInstancedMaterial::TextureUpdateStatus::kFailed) + return content.canvas() != nullptr && content.isSurfaceDirty(); + }; + auto list = queryEntitiesWithComponent(selectContents); + if (list.size() > 0) + { + for (auto &item : list) { - content->setContentDirty(false); - content->setSurfaceDirty(false); + auto content = item.second; + // Use the same texture update method for both text and image content + auto status = webContentInstancedMaterial->updateTexture(*content); + if (status != materials::WebContentInstancedMaterial::TextureUpdateStatus::kFailed) + { + content->setContentDirty(false); + content->setSurfaceDirty(false); + } } } } } -} +} // namespace endor diff --git a/src/client/builtin_scene/xr.cpp b/src/client/builtin_scene/xr.cpp index f3efa7aa6..1823c2d4f 100644 --- a/src/client/builtin_scene/xr.cpp +++ b/src/client/builtin_scene/xr.cpp @@ -9,133 +9,136 @@ #include "./scene.hpp" #include "./xr.hpp" -namespace builtin_scene +namespace endor { - using namespace std; - using namespace client_xr; - - WebXRExperience::WebXRExperience() - : ecs::Resource() - , client_(TrClientContextPerProcess::Get()->getXRDeviceClient()) - , session_(nullptr) - , reference_space_(nullptr) - , current_frame_(nullptr) - , current_time_(0) + namespace builtin_scene { - assert(client_ != nullptr && "WebXRExperience: XRDeviceClient is null"); - } - - WebXRExperience::~WebXRExperience() - { - if (session_ != nullptr) + using namespace std; + using namespace client_xr; + + WebXRExperience::WebXRExperience() + : ecs::Resource() + , client_(TrClientContextPerProcess::Get()->getXRDeviceClient()) + , session_(nullptr) + , reference_space_(nullptr) + , current_frame_(nullptr) + , current_time_(0) { - session_->removeEventListener(dom::DOMEventType::XRSessionSelectStart); - session_->removeEventListener(dom::DOMEventType::XRSessionSelectEnd); - session_->end(); - session_ = nullptr; + assert(client_ != nullptr && "WebXRExperience: XRDeviceClient is null"); } - } - bool WebXRExperience::multiviewRequired() const - { - assert(session_ != nullptr && "requestSession() must be called before."); - return client_->getDeviceInit().multiviewRequired(); - } - - shared_ptr WebXRExperience::requestSession() - { - assert(session_ == nullptr && "WebXRExperience: Session already created"); - session_ = client_->getXRSystem()->requestSession(); - - // Add event listeners for actions - auto callback = [this](dom::DOMEventType type, shared_ptr event) + WebXRExperience::~WebXRExperience() { - if (type == dom::DOMEventType::XRSessionSelectStart) - { - if (select_start_handler_ == nullptr) - return; - select_start_handler_(*static_pointer_cast(event)); - } - else if (type == dom::DOMEventType::XRSessionSelectEnd) + if (session_ != nullptr) { - if (select_end_handler_ == nullptr) - return; - select_end_handler_(*static_pointer_cast(event)); + session_->removeEventListener(dom::DOMEventType::XRSessionSelectStart); + session_->removeEventListener(dom::DOMEventType::XRSessionSelectEnd); + session_->end(); + session_ = nullptr; } - }; - session_->addEventListener(dom::DOMEventType::XRSessionSelectStart, callback); - session_->addEventListener(dom::DOMEventType::XRSessionSelectEnd, callback); - return session_; - } + } - optional WebXRExperience::selectRayForHitTesting() - { - if (!sessionRef().inputSources.has_value()) - return nullopt; + bool WebXRExperience::multiviewRequired() const + { + assert(session_ != nullptr && "requestSession() must be called before."); + return client_->getDeviceInit().multiviewRequired(); + } - const auto &inputSources = sessionRef().inputSources.value(); - for (const auto &inputSource : inputSources) + shared_ptr WebXRExperience::requestSession() { - auto mode = inputSource->targetRayMode(); - if (mode == XRTargetRayMode::TrackedPointer || mode == XRTargetRayMode::Screen) + assert(session_ == nullptr && "WebXRExperience: Session already created"); + session_ = client_->getXRSystem()->requestSession(); + + // Add event listeners for actions + auto callback = [this](dom::DOMEventType type, shared_ptr event) { - shared_ptr raySpace = inputSource->targetRaySpace(); - XRRigidTransform rayTransform = current_frame_->getPose(raySpace, reference_space_)->transform; + if (type == dom::DOMEventType::XRSessionSelectStart) + { + if (select_start_handler_ == nullptr) + return; + select_start_handler_(*static_pointer_cast(event)); + } + else if (type == dom::DOMEventType::XRSessionSelectEnd) + { + if (select_end_handler_ == nullptr) + return; + select_end_handler_(*static_pointer_cast(event)); + } + }; + session_->addEventListener(dom::DOMEventType::XRSessionSelectStart, callback); + session_->addEventListener(dom::DOMEventType::XRSessionSelectEnd, callback); + return session_; + } + + optional WebXRExperience::selectRayForHitTesting() + { + if (!sessionRef().inputSources.has_value()) + return nullopt; - glm::mat4 rayBaseMatrix = rayTransform.matrix(); - glm::vec3 origin = glm::vec3(rayBaseMatrix[3]); - glm::vec3 dir = glm::normalize(glm::vec3(rayBaseMatrix * glm::vec4(0.f, 0.f, -1.f, 0.f))); - return make_optional(collision::TrRay(origin, dir, 100.f)); + const auto &inputSources = sessionRef().inputSources.value(); + for (const auto &inputSource : inputSources) + { + auto mode = inputSource->targetRayMode(); + if (mode == XRTargetRayMode::TrackedPointer || mode == XRTargetRayMode::Screen) + { + shared_ptr raySpace = inputSource->targetRaySpace(); + XRRigidTransform rayTransform = current_frame_->getPose(raySpace, reference_space_)->transform; + + glm::mat4 rayBaseMatrix = rayTransform.matrix(); + glm::vec3 origin = glm::vec3(rayBaseMatrix[3]); + glm::vec3 dir = glm::normalize(glm::vec3(rayBaseMatrix * glm::vec4(0.f, 0.f, -1.f, 0.f))); + return make_optional(collision::TrRay(origin, dir, 100.f)); + } } + return nullopt; } - return nullopt; - } - - void WebXRExperienceStartupSystem::onExecute() - { - } - void WebXRExperienceUpdateSystem::onExecute() - { - auto xrExperience = getResource(); - assert(xrExperience != nullptr); + void WebXRExperienceStartupSystem::onExecute() + { + } - auto xrFrame = xrExperience->currentFrame(); - auto referenceSpace = xrExperience->referenceSpace(); - if (xrFrame == nullptr || referenceSpace == nullptr) - return; + void WebXRExperienceUpdateSystem::onExecute() + { + auto xrExperience = getResource(); + assert(xrExperience != nullptr); - auto viewerPose = xrFrame->getViewerPose(referenceSpace); - if (viewerPose == nullptr) - return; - } + auto xrFrame = xrExperience->currentFrame(); + auto referenceSpace = xrExperience->referenceSpace(); + if (xrFrame == nullptr || referenceSpace == nullptr) + return; - void WebXRCollisionBoxSystem::onExecute() - { - auto scene = getApplication(); - assert(scene != nullptr); + auto viewerPose = xrFrame->getViewerPose(referenceSpace); + if (viewerPose == nullptr) + return; + } - auto xrExperience = getResource(); - if (xrExperience != nullptr) + void WebXRCollisionBoxSystem::onExecute() { - float width = client_cssom::pixelToMeter(scene->volumeSize().width()); - float height = client_cssom::pixelToMeter(scene->volumeSize().height()); - glm::vec3 min(-width / 2, -height / 2, -0.001f); - glm::vec3 max(width / 2, height / 2, 0.001f); - xrExperience->sessionRef().updateCollisionBox(min, max); + auto scene = getApplication(); + assert(scene != nullptr); + + auto xrExperience = getResource(); + if (xrExperience != nullptr) + { + float width = client_cssom::pixelToMeter(scene->volumeSize().width()); + float height = client_cssom::pixelToMeter(scene->volumeSize().height()); + glm::vec3 min(-width / 2, -height / 2, -0.001f); + glm::vec3 max(width / 2, height / 2, 0.001f); + xrExperience->sessionRef().updateCollisionBox(min, max); + } } - } - void WebXRPlugin::build(ecs::App &app) - { - using namespace ecs; + void WebXRPlugin::build(ecs::App &app) + { + using namespace ecs; - // Resources - app.addResource(Resource::Make()); + // Resources + app.addResource(Resource::Make()); - // Systems - app.addSystem(SchedulerLabel::kStartup, System::Make()); - app.addSystem(SchedulerLabel::kPreUpdate, System::Make()); - app.addSystem(SchedulerLabel::kPostUpdate, System::Make()); + // Systems + app.addSystem(SchedulerLabel::kStartup, System::Make()); + app.addSystem(SchedulerLabel::kPreUpdate, System::Make()); + app.addSystem(SchedulerLabel::kPostUpdate, System::Make()); + } } -} +} // namespace endor diff --git a/src/client/builtin_scene/xr.hpp b/src/client/builtin_scene/xr.hpp index 7d89291c1..e2e51f589 100644 --- a/src/client/builtin_scene/xr.hpp +++ b/src/client/builtin_scene/xr.hpp @@ -14,145 +14,148 @@ #include "./ecs.hpp" -namespace builtin_scene +namespace endor { - class WebXRExperience : public ecs::Resource + namespace builtin_scene { - friend class Scene; - - public: - WebXRExperience(); - ~WebXRExperience(); - - public: - std::shared_ptr session() - { - return session_; - } - client_xr::XRSession &sessionRef() - { - return *session_; - } - - std::shared_ptr referenceSpace() - { - return reference_space_; - } - const client_xr::XRReferenceSpace &referenceSpaceRef() - { - return *reference_space_; - } - - std::shared_ptr currentFrame() - { - return current_frame_; - } - const client_xr::XRFrame ¤tFrameRef() - { - return *current_frame_; - } - - uint32_t currentTime() - { - return current_time_; - } - std::shared_ptr viewerPose() - { - if (current_frame_ == nullptr || reference_space_ == nullptr) - return nullptr; - return current_frame_->getViewerPose(reference_space_); - } - - bool multiviewEnabled() const - { - return multiview_enabled_; - } - bool multiviewRequired() const; - - void resetSelectStartHandler(std::function handler) - { - select_start_handler_ = handler; - } - void resetSelectEndHandler(std::function handler) - { - select_end_handler_ = handler; - } - - // Request a session - std::shared_ptr requestSession(); - - // Select a ray for hit testing - std::optional selectRayForHitTesting(); - - private: - inline void updateReferenceSpace(std::shared_ptr space) - { - reference_space_ = space; - } - inline void updateCurrentFrame(uint32_t time, std::shared_ptr frame) - { - current_time_ = time; - current_frame_ = frame; - } - inline void enableMultiview(bool enabled) - { - multiview_enabled_ = enabled; - } - - private: - std::shared_ptr client_; - std::shared_ptr session_; - std::shared_ptr reference_space_; - std::shared_ptr current_frame_; - uint32_t current_time_; - bool multiview_enabled_; - - std::function select_start_handler_; - std::function select_end_handler_; - }; - - class WebXRExperienceStartupSystem : public ecs::System - { - using ecs::System::System; - - public: - const std::string name() const override - { - return "WebXRExperienceStartupSystem"; - } - void onExecute() override; - }; - - class WebXRExperienceUpdateSystem : public ecs::System - { - using ecs::System::System; - - public: - const std::string name() const override - { - return "WebXRExperienceUpdateSystem"; - } - void onExecute() override; - }; - - class WebXRCollisionBoxSystem : public ecs::System - { - using ecs::System::System; - - public: - const std::string name() const override - { - return "WebXRCollisionBoxSystem"; - } - void onExecute() override; - }; - - class WebXRPlugin : public ecs::Plugin - { - public: - using ecs::Plugin::Plugin; - - protected: - void build(ecs::App &) override; - }; -} + class WebXRExperience : public ecs::Resource + { + friend class Scene; + + public: + WebXRExperience(); + ~WebXRExperience(); + + public: + std::shared_ptr session() + { + return session_; + } + client_xr::XRSession &sessionRef() + { + return *session_; + } + + std::shared_ptr referenceSpace() + { + return reference_space_; + } + const client_xr::XRReferenceSpace &referenceSpaceRef() + { + return *reference_space_; + } + + std::shared_ptr currentFrame() + { + return current_frame_; + } + const client_xr::XRFrame ¤tFrameRef() + { + return *current_frame_; + } + + uint32_t currentTime() + { + return current_time_; + } + std::shared_ptr viewerPose() + { + if (current_frame_ == nullptr || reference_space_ == nullptr) + return nullptr; + return current_frame_->getViewerPose(reference_space_); + } + + bool multiviewEnabled() const + { + return multiview_enabled_; + } + bool multiviewRequired() const; + + void resetSelectStartHandler(std::function handler) + { + select_start_handler_ = handler; + } + void resetSelectEndHandler(std::function handler) + { + select_end_handler_ = handler; + } + + // Request a session + std::shared_ptr requestSession(); + + // Select a ray for hit testing + std::optional selectRayForHitTesting(); + + private: + inline void updateReferenceSpace(std::shared_ptr space) + { + reference_space_ = space; + } + inline void updateCurrentFrame(uint32_t time, std::shared_ptr frame) + { + current_time_ = time; + current_frame_ = frame; + } + inline void enableMultiview(bool enabled) + { + multiview_enabled_ = enabled; + } + + private: + std::shared_ptr client_; + std::shared_ptr session_; + std::shared_ptr reference_space_; + std::shared_ptr current_frame_; + uint32_t current_time_; + bool multiview_enabled_; + + std::function select_start_handler_; + std::function select_end_handler_; + }; + + class WebXRExperienceStartupSystem : public ecs::System + { + using ecs::System::System; + + public: + const std::string name() const override + { + return "WebXRExperienceStartupSystem"; + } + void onExecute() override; + }; + + class WebXRExperienceUpdateSystem : public ecs::System + { + using ecs::System::System; + + public: + const std::string name() const override + { + return "WebXRExperienceUpdateSystem"; + } + void onExecute() override; + }; + + class WebXRCollisionBoxSystem : public ecs::System + { + using ecs::System::System; + + public: + const std::string name() const override + { + return "WebXRCollisionBoxSystem"; + } + void onExecute() override; + }; + + class WebXRPlugin : public ecs::Plugin + { + public: + using ecs::Plugin::Plugin; + + protected: + void build(ecs::App &) override; + }; + } +} // namespace endor diff --git a/src/client/canvas/canvas-inl.hpp b/src/client/canvas/canvas-inl.hpp index 4542a8ec4..f0dc90186 100644 --- a/src/client/canvas/canvas-inl.hpp +++ b/src/client/canvas/canvas-inl.hpp @@ -4,50 +4,53 @@ #include "./canvas.hpp" #include "./rendering_context2d.hpp" -namespace canvas +namespace endor { - template - std::shared_ptr> CanvasBase::getContext(RenderingContextType type) + namespace canvas { - if (renderingContext2d == nullptr) + template + std::shared_ptr> CanvasBase::getContext(RenderingContextType type) { - resetSkSurface(); // Reset the SkSurface when a request to get new rendering context. - switch (type) + if (renderingContext2d == nullptr) { - case RenderingContextType::RenderingContext2D: - renderingContext2d = std::make_shared>(getPtr()); - break; - default: - break; + resetSkSurface(); // Reset the SkSurface when a request to get new rendering context. + switch (type) + { + case RenderingContextType::RenderingContext2D: + renderingContext2d = std::make_shared>(getPtr()); + break; + default: + break; + } } + return renderingContext2d; } - return renderingContext2d; - } - template - void CanvasBase::resetSkSurface() - { - // If the width or height is zero, we cannot create a valid Skia surface. - if (widthToSet == 0 || heightToSet == 0) + template + void CanvasBase::resetSkSurface() { - bitmap_->reset(); - skSurface.reset(); - } - else - { - auto imageInfo = SkImageInfo::MakeN32Premul(widthToSet, heightToSet); - if (skSurface != nullptr) + // If the width or height is zero, we cannot create a valid Skia surface. + if (widthToSet == 0 || heightToSet == 0) + { + bitmap_->reset(); skSurface.reset(); + } + else + { + auto imageInfo = SkImageInfo::MakeN32Premul(widthToSet, heightToSet); + if (skSurface != nullptr) + skSurface.reset(); - bitmap_->allocPixels(imageInfo); - skSurface = SkSurfaces::WrapPixels(bitmap_->info(), - bitmap_->getPixels(), - bitmap_->rowBytes()); - assert(skSurface != nullptr && "Failed to create Skia surface for the canvas."); - } + bitmap_->allocPixels(imageInfo); + skSurface = SkSurfaces::WrapPixels(bitmap_->info(), + bitmap_->getPixels(), + bitmap_->rowBytes()); + assert(skSurface != nullptr && "Failed to create Skia surface for the canvas."); + } - // Reset the rendering context if created. - if (renderingContext2d != nullptr) - renderingContext2d->reset(skSurface); + // Reset the rendering context if created. + if (renderingContext2d != nullptr) + renderingContext2d->reset(skSurface); + } } -} +} // namespace endor diff --git a/src/client/canvas/canvas.cpp b/src/client/canvas/canvas.cpp index c3bcda266..082e687b1 100644 --- a/src/client/canvas/canvas.cpp +++ b/src/client/canvas/canvas.cpp @@ -2,21 +2,24 @@ using namespace std; -namespace canvas +namespace endor { - std::string Canvas::toDataURL(const std::string &type, double encoderOptions) + namespace canvas { - return "data:"; - } + std::string Canvas::toDataURL(const std::string &type, double encoderOptions) + { + return "data:"; + } - OffscreenCanvas::OffscreenCanvas(uint32_t width, uint32_t height) - { - widthToSet = width; - heightToSet = height; - resize(); - } + OffscreenCanvas::OffscreenCanvas(uint32_t width, uint32_t height) + { + widthToSet = width; + heightToSet = height; + resize(); + } - void OffscreenCanvas::commit() - { + void OffscreenCanvas::commit() + { + } } -} +} // namespace endor diff --git a/src/client/canvas/canvas.hpp b/src/client/canvas/canvas.hpp index 42c460ff3..a4f2255ac 100644 --- a/src/client/canvas/canvas.hpp +++ b/src/client/canvas/canvas.hpp @@ -12,222 +12,225 @@ #include "./rendering_context.hpp" #include "./image_source.hpp" -namespace canvas +namespace endor { - /** + namespace canvas + { + /** * @class CanvasBase * The `CanvasBase` class is a base class for canvas implementations, providing common functionality for both `Canvas` and `OffscreenCanvas`. * * @tparam T The derived class type (e.g., `Canvas` or `OffscreenCanvas`). */ - template - class CanvasBase : public ImageSource, - public dom::DOMEventTarget, - public std::enable_shared_from_this - { - public: - friend class canvas::RenderingContextBase; - friend class canvas::CanvasRenderingContext2D; + template + class CanvasBase : public ImageSource, + public dom::DOMEventTarget, + public std::enable_shared_from_this + { + public: + friend class canvas::RenderingContextBase; + friend class canvas::CanvasRenderingContext2D; - static constexpr const int DEFAULT_CANVAS_WIDTH = 300; - static constexpr const int DEFAULT_CANVAS_HEIGHT = 150; + static constexpr const int DEFAULT_CANVAS_WIDTH = 300; + static constexpr const int DEFAULT_CANVAS_HEIGHT = 150; - public: - /** + public: + /** * Default constructor for `CanvasBase`. */ - CanvasBase(std::optional width = std::nullopt, - std::optional height = std::nullopt) - : ImageSource() - , dom::DOMEventTarget() - , widthToSet(width.has_value() ? static_cast(width.value()) : DEFAULT_CANVAS_WIDTH) - , heightToSet(height.has_value() ? static_cast(height.value()) : DEFAULT_CANVAS_HEIGHT) - , bitmap_(std::make_shared()) - { - resetSkSurface(); - } - virtual ~CanvasBase() = default; - - public: - /** + CanvasBase(std::optional width = std::nullopt, + std::optional height = std::nullopt) + : ImageSource() + , dom::DOMEventTarget() + , widthToSet(width.has_value() ? static_cast(width.value()) : DEFAULT_CANVAS_WIDTH) + , heightToSet(height.has_value() ? static_cast(height.value()) : DEFAULT_CANVAS_HEIGHT) + , bitmap_(std::make_shared()) + { + resetSkSurface(); + } + virtual ~CanvasBase() = default; + + public: + /** * Creates or gets the rendering context with the specified type. * * @param type The type of the rendering context: 2d, webgl, webgl2. * @returns The rendering context. * @throws std::invalid_argument if the context type is unsupported. */ - std::shared_ptr> getContext(RenderingContextType type); + std::shared_ptr> getContext(RenderingContextType type); - public: - /** + public: + /** * Gets the width of the canvas. * * @returns The width of the canvas in pixels. */ - size_t width() const override final - { - return skSurface == nullptr ? 0 : skSurface->width(); - } + size_t width() const override final + { + return skSurface == nullptr ? 0 : skSurface->width(); + } - /** + /** * Gets the height of the canvas. * * @returns The height of the canvas in pixels. */ - size_t height() const override final - { - return skSurface == nullptr ? 0 : skSurface->height(); - } + size_t height() const override final + { + return skSurface == nullptr ? 0 : skSurface->height(); + } - /** + /** * Reads the pixel data from the canvas into the specified `SkPixmap`. * * @param dst The destination `SkPixmap` to store the pixel data. * @returns True if the pixel data was successfully read, false otherwise. */ - bool readPixels(SkPixmap &dst) const override final - { - if (skSurface == nullptr) - return false; + bool readPixels(SkPixmap &dst) const override final + { + if (skSurface == nullptr) + return false; - // TODO: Read the pixels into the bitmap_ as well. - return skSurface->peekPixels(&dst); - } + // TODO: Read the pixels into the bitmap_ as well. + return skSurface->peekPixels(&dst); + } - /** + /** * Peeks the pixel data from the canvas into the specified `SkPixmap` without copying. * * @param pixmap The destination `SkPixmap` to store the pixel data. * @returns True if the pixel data was successfully peeked, false otherwise. */ - bool peekPixels(SkPixmap *pixmap) const override final - { - if (skSurface == nullptr || pixmap == nullptr) - return false; - return skSurface->peekPixels(pixmap); - } + bool peekPixels(SkPixmap *pixmap) const override final + { + if (skSurface == nullptr || pixmap == nullptr) + return false; + return skSurface->peekPixels(pixmap); + } - /** + /** * This callback is used to listen for pixel updates on the canvas. */ - void setPixelsUpdatedCallback(std::function callback) - { - pixels_updated_callback_ = std::move(callback); - } + void setPixelsUpdatedCallback(std::function callback) + { + pixels_updated_callback_ = std::move(callback); + } - public: - /** + public: + /** * Sets the width of the canvas. * * @param width The new width of the canvas in pixels. */ - void setWidth(uint32_t width) - { - widthToSet = width; - resize(); - } + void setWidth(uint32_t width) + { + widthToSet = width; + resize(); + } - /** + /** * Sets the height of the canvas. * * @param height The new height of the canvas in pixels. */ - void setHeight(uint32_t height) - { - heightToSet = height; - resize(); - } + void setHeight(uint32_t height) + { + heightToSet = height; + resize(); + } - /** + /** * Resizes the canvas to the specified width and height. */ - inline void resize() - { - resetSkSurface(); - } + inline void resize() + { + resetSkSurface(); + } - /** + /** * @returns The constant shared pointer to the SkBitmap that stores the canvas content. */ - inline std::shared_ptr getSkBitmap() const - { - return bitmap_; - } + inline std::shared_ptr getSkBitmap() const + { + return bitmap_; + } - protected: - /** + protected: + /** * Gets a shared pointer to the derived class instance. * * @returns A shared pointer to the derived class instance. */ - std::shared_ptr getPtr() - { - return this->shared_from_this(); - } + std::shared_ptr getPtr() + { + return this->shared_from_this(); + } - /** + /** * Resets the Skia surface to match the current width and height. */ - void resetSkSurface(); + void resetSkSurface(); - protected: - sk_sp skSurface; // The Skia surface for rendering - uint32_t widthToSet; // The width to set for the canvas - uint32_t heightToSet; // The height to set for the canvas + protected: + sk_sp skSurface; // The Skia surface for rendering + uint32_t widthToSet; // The width to set for the canvas + uint32_t heightToSet; // The height to set for the canvas - // Cached rendering contexts - // - // A canvas can have only one instance of a type of rendering context, such as there is only a 2d context, but at - // the same time, developers can get a `2d` context and a `gl` context, both them could draw on this canvas. - std::shared_ptr> renderingContext2d; - // TODO(yorkie): support other types of rendering contexts, such as WebGL, WebGL2, etc. + // Cached rendering contexts + // + // A canvas can have only one instance of a type of rendering context, such as there is only a 2d context, but at + // the same time, developers can get a `2d` context and a `gl` context, both them could draw on this canvas. + std::shared_ptr> renderingContext2d; + // TODO(yorkie): support other types of rendering contexts, such as WebGL, WebGL2, etc. - private: - std::shared_ptr bitmap_; // The bitmap storage for the canvas content - std::function pixels_updated_callback_; - }; + private: + std::shared_ptr bitmap_; // The bitmap storage for the canvas content + std::function pixels_updated_callback_; + }; - /** + /** * @class Canvas * The `Canvas` class represents an HTML canvas element. */ - class Canvas final : public CanvasBase - { - public: - using CanvasBase::CanvasBase; + class Canvas final : public CanvasBase + { + public: + using CanvasBase::CanvasBase; - public: - /** + public: + /** * Converts the canvas content to a data URL. * * @param type The MIME type of the image (e.g., "image/png"). * @param encoderOptions The encoding options for the image. * @returns A data URL containing the canvas content. */ - std::string toDataURL(const std::string &type, double encoderOptions); - }; + std::string toDataURL(const std::string &type, double encoderOptions); + }; - /** + /** * @class OffscreenCanvas * The `OffscreenCanvas` class represents an offscreen canvas element. */ - class OffscreenCanvas final : public CanvasBase - { - public: - /** + class OffscreenCanvas final : public CanvasBase + { + public: + /** * Constructs a new `OffscreenCanvas` with the specified width and height. * * @param width The width of the canvas in pixels. * @param height The height of the canvas in pixels. */ - OffscreenCanvas(uint32_t width, uint32_t height); + OffscreenCanvas(uint32_t width, uint32_t height); - public: - /** + public: + /** * Commits the offscreen canvas content to the main thread. */ - void commit(); - }; -} // namespace canvas + void commit(); + }; + } // namespace canvas +} // namespace endor #include "./canvas-inl.hpp" diff --git a/src/client/canvas/image_bitmap.cpp b/src/client/canvas/image_bitmap.cpp index 89084cd45..585ffea23 100644 --- a/src/client/canvas/image_bitmap.cpp +++ b/src/client/canvas/image_bitmap.cpp @@ -6,88 +6,91 @@ using namespace std; -namespace canvas +namespace endor { - shared_ptr ImageBitmap::CreateImageBitmap(const void *imageData, size_t imageByteLength, float sx, float sy, float sw, float sh) + namespace canvas { - return make_shared(imageData, imageByteLength, sx, sy, sw, sh); - } - - shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr image, - float sx, - float sy, - float sw, - float sh) - { - return make_shared(image, sx, sy, sw, sh); - } - - shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr otherImageBitmap, - float sx, - float sy, - float sw, - float sh) - { - auto image = dynamic_pointer_cast(otherImageBitmap); - return CreateImageBitmap(image, sx, sy, sw, sh); - } - - shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr otherImageData, - float sx, - float sy, - float sw, - float sh) - { - auto image = dynamic_pointer_cast(otherImageData); - return CreateImageBitmap(image, sx, sy, sw, sh); - } + shared_ptr ImageBitmap::CreateImageBitmap(const void *imageData, size_t imageByteLength, float sx, float sy, float sw, float sh) + { + return make_shared(imageData, imageByteLength, sx, sy, sw, sh); + } - shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr imageBlob, - float sx, - float sy, - float sw, - float sh) - { - auto bytes = imageBlob->bytes().get_future().get(); - return CreateImageBitmap(bytes.data(), bytes.size(), sx, sy, sw, sh); - } + shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr image, + float sx, + float sy, + float sw, + float sh) + { + return make_shared(image, sx, sy, sw, sh); + } - ImageBitmap::ImageBitmap(const void *imageData, size_t imageByteLength, float sx, float sy, float sw, float sh) - : skBitmap(make_shared()) - { - auto codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(imageData, imageByteLength)); - if (codec) + shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr otherImageBitmap, + float sx, + float sy, + float sw, + float sh) { - SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); - skBitmap->allocPixels(info); - codec->getPixels(info, skBitmap->getPixels(), skBitmap->rowBytes()); + auto image = dynamic_pointer_cast(otherImageBitmap); + return CreateImageBitmap(image, sx, sy, sw, sh); } - else + + shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr otherImageData, + float sx, + float sy, + float sw, + float sh) { - throw runtime_error("Could not create image codec from data."); + auto image = dynamic_pointer_cast(otherImageData); + return CreateImageBitmap(image, sx, sy, sw, sh); } - } - ImageBitmap::ImageBitmap(shared_ptr image, float sx, float sy, float sw, float sh) - : skBitmap(make_shared()) - { - skBitmap->allocN32Pixels(image->width(), image->height()); + shared_ptr ImageBitmap::CreateImageBitmap(shared_ptr imageBlob, + float sx, + float sy, + float sw, + float sh) + { + auto bytes = imageBlob->bytes().get_future().get(); + return CreateImageBitmap(bytes.data(), bytes.size(), sx, sy, sw, sh); + } - SkPixmap pixmap; - if (!image->readPixels(pixmap)) + ImageBitmap::ImageBitmap(const void *imageData, size_t imageByteLength, float sx, float sy, float sw, float sh) + : skBitmap(make_shared()) { - skBitmap->reset(); - std::cerr << "Failed to read pixels from image source" << std::endl; - return; + auto codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(imageData, imageByteLength)); + if (codec) + { + SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); + skBitmap->allocPixels(info); + codec->getPixels(info, skBitmap->getPixels(), skBitmap->rowBytes()); + } + else + { + throw runtime_error("Could not create image codec from data."); + } } - else + + ImageBitmap::ImageBitmap(shared_ptr image, float sx, float sy, float sw, float sh) + : skBitmap(make_shared()) { - skBitmap->writePixels(pixmap); + skBitmap->allocN32Pixels(image->width(), image->height()); + + SkPixmap pixmap; + if (!image->readPixels(pixmap)) + { + skBitmap->reset(); + std::cerr << "Failed to read pixels from image source" << std::endl; + return; + } + else + { + skBitmap->writePixels(pixmap); + } } - } - void ImageBitmap::close() - { - skBitmap->reset(); + void ImageBitmap::close() + { + skBitmap->reset(); + } } -} +} // namespace endor diff --git a/src/client/canvas/image_bitmap.hpp b/src/client/canvas/image_bitmap.hpp index e28fb3124..0df323cbc 100644 --- a/src/client/canvas/image_bitmap.hpp +++ b/src/client/canvas/image_bitmap.hpp @@ -7,69 +7,72 @@ #include #include -namespace canvas +namespace endor { - class ImageData; - class ImageBitmap : public ImageSource, - public scripting_base::JSObjectHolder + namespace canvas { - public: - static std::shared_ptr CreateImageBitmap(const void *imageData, - size_t imageByteLength, - float sx, - float sy, - float sw, - float sh); - static std::shared_ptr CreateImageBitmap(std::shared_ptr image, - float sx, - float sy, - float sw, - float sh); - static std::shared_ptr CreateImageBitmap(std::shared_ptr otherImageBitmap, - float sx, - float sy, - float sw, - float sh); - static std::shared_ptr CreateImageBitmap(std::shared_ptr otherImageData, - float sx, - float sy, - float sw, - float sh); - static std::shared_ptr CreateImageBitmap(std::shared_ptr imageBitmap, - float sx, - float sy, - float sw, - float sh); + class ImageData; + class ImageBitmap : public ImageSource, + public scripting_base::JSObjectHolder + { + public: + static std::shared_ptr CreateImageBitmap(const void *imageData, + size_t imageByteLength, + float sx, + float sy, + float sw, + float sh); + static std::shared_ptr CreateImageBitmap(std::shared_ptr image, + float sx, + float sy, + float sw, + float sh); + static std::shared_ptr CreateImageBitmap(std::shared_ptr otherImageBitmap, + float sx, + float sy, + float sw, + float sh); + static std::shared_ptr CreateImageBitmap(std::shared_ptr otherImageData, + float sx, + float sy, + float sw, + float sh); + static std::shared_ptr CreateImageBitmap(std::shared_ptr imageBitmap, + float sx, + float sy, + float sw, + float sh); - public: - ImageBitmap(const void *imageData, size_t imageByteLength, float sx, float sy, float sw, float sh); - ImageBitmap(std::shared_ptr image, float sx, float sy, float sw, float sh); + public: + ImageBitmap(const void *imageData, size_t imageByteLength, float sx, float sy, float sw, float sh); + ImageBitmap(std::shared_ptr image, float sx, float sy, float sw, float sh); - public: - size_t width() const override - { - return skBitmap->width(); - } - size_t height() const override - { - return skBitmap->height(); - } - bool readPixels(SkPixmap &dst) const override - { - dst.reset(skBitmap->info(), - skBitmap->getPixels(), - skBitmap->rowBytes()); - return skBitmap->readPixels(dst); - } - bool peekPixels(SkPixmap *pixmap) const override - { - return skBitmap->peekPixels(pixmap); - } + public: + size_t width() const override + { + return skBitmap->width(); + } + size_t height() const override + { + return skBitmap->height(); + } + bool readPixels(SkPixmap &dst) const override + { + dst.reset(skBitmap->info(), + skBitmap->getPixels(), + skBitmap->rowBytes()); + return skBitmap->readPixels(dst); + } + bool peekPixels(SkPixmap *pixmap) const override + { + return skBitmap->peekPixels(pixmap); + } - public: - void close(); + public: + void close(); - private: - std::shared_ptr skBitmap; - }; -} + private: + std::shared_ptr skBitmap; + }; + } +} // namespace endor diff --git a/src/client/canvas/image_codec.cpp b/src/client/canvas/image_codec.cpp index 8915a4a04..a65965706 100644 --- a/src/client/canvas/image_codec.cpp +++ b/src/client/canvas/image_codec.cpp @@ -16,390 +16,393 @@ #include "./image_codec.hpp" -namespace canvas +namespace endor { - using namespace std; - - EncodedImageFormat::EncodedImageFormat(SkEncodedImageFormat sk_format) + namespace canvas { - switch (sk_format) + using namespace std; + + EncodedImageFormat::EncodedImageFormat(SkEncodedImageFormat sk_format) { - case SkEncodedImageFormat::kBMP: - format_ = kBMP; - break; - case SkEncodedImageFormat::kGIF: - format_ = kGIF; - break; - case SkEncodedImageFormat::kICO: - format_ = kICO; - break; - case SkEncodedImageFormat::kJPEG: - format_ = kJPEG; - break; - case SkEncodedImageFormat::kPNG: - format_ = kPNG; - break; - case SkEncodedImageFormat::kWBMP: - format_ = kWBMP; - break; - case SkEncodedImageFormat::kWEBP: - format_ = kWEBP; - break; - case SkEncodedImageFormat::kPKM: - format_ = kPKM; - break; - case SkEncodedImageFormat::kKTX: - format_ = kKTX; - break; - case SkEncodedImageFormat::kASTC: - format_ = kASTC; - break; - case SkEncodedImageFormat::kDNG: - format_ = kDNG; - break; - case SkEncodedImageFormat::kHEIF: - format_ = kHEIF; - break; - case SkEncodedImageFormat::kAVIF: - format_ = kAVIF; - break; - case SkEncodedImageFormat::kJPEGXL: - format_ = kJPEGXL; - break; - default: - format_ = kUnknown; + switch (sk_format) + { + case SkEncodedImageFormat::kBMP: + format_ = kBMP; + break; + case SkEncodedImageFormat::kGIF: + format_ = kGIF; + break; + case SkEncodedImageFormat::kICO: + format_ = kICO; + break; + case SkEncodedImageFormat::kJPEG: + format_ = kJPEG; + break; + case SkEncodedImageFormat::kPNG: + format_ = kPNG; + break; + case SkEncodedImageFormat::kWBMP: + format_ = kWBMP; + break; + case SkEncodedImageFormat::kWEBP: + format_ = kWEBP; + break; + case SkEncodedImageFormat::kPKM: + format_ = kPKM; + break; + case SkEncodedImageFormat::kKTX: + format_ = kKTX; + break; + case SkEncodedImageFormat::kASTC: + format_ = kASTC; + break; + case SkEncodedImageFormat::kDNG: + format_ = kDNG; + break; + case SkEncodedImageFormat::kHEIF: + format_ = kHEIF; + break; + case SkEncodedImageFormat::kAVIF: + format_ = kAVIF; + break; + case SkEncodedImageFormat::kJPEGXL: + format_ = kJPEGXL; + break; + default: + format_ = kUnknown; + } } - } - std::ostream &operator<<(std::ostream &os, const EncodedImageFormat &format) - { - switch (format.format_) + std::ostream &operator<<(std::ostream &os, const EncodedImageFormat &format) { - case EncodedImageFormat::kBMP: - os << "BMP"; - break; - case EncodedImageFormat::kGIF: - os << "GIF"; - break; - case EncodedImageFormat::kICO: - os << "ICO"; - break; - case EncodedImageFormat::kJPEG: - os << "JPEG"; - break; - case EncodedImageFormat::kPNG: - os << "PNG"; - break; - case EncodedImageFormat::kWBMP: - os << "WBMP"; - break; - case EncodedImageFormat::kWEBP: - os << "WEBP"; - break; - case EncodedImageFormat::kPKM: - os << "PKM"; - break; - case EncodedImageFormat::kKTX: - os << "KTX"; - break; - case EncodedImageFormat::kASTC: - os << "ASTC"; - break; - case EncodedImageFormat::kDNG: - os << "DNG"; - break; - case EncodedImageFormat::kHEIF: - os << "HEIF"; - break; - case EncodedImageFormat::kAVIF: - os << "AVIF"; - break; - case EncodedImageFormat::kJPEGXL: - os << "JPEGXL"; - break; - case EncodedImageFormat::kSVG: - os << "SVG"; - break; - default: - os << "Unknown"; + switch (format.format_) + { + case EncodedImageFormat::kBMP: + os << "BMP"; + break; + case EncodedImageFormat::kGIF: + os << "GIF"; + break; + case EncodedImageFormat::kICO: + os << "ICO"; + break; + case EncodedImageFormat::kJPEG: + os << "JPEG"; + break; + case EncodedImageFormat::kPNG: + os << "PNG"; + break; + case EncodedImageFormat::kWBMP: + os << "WBMP"; + break; + case EncodedImageFormat::kWEBP: + os << "WEBP"; + break; + case EncodedImageFormat::kPKM: + os << "PKM"; + break; + case EncodedImageFormat::kKTX: + os << "KTX"; + break; + case EncodedImageFormat::kASTC: + os << "ASTC"; + break; + case EncodedImageFormat::kDNG: + os << "DNG"; + break; + case EncodedImageFormat::kHEIF: + os << "HEIF"; + break; + case EncodedImageFormat::kAVIF: + os << "AVIF"; + break; + case EncodedImageFormat::kJPEGXL: + os << "JPEGXL"; + break; + case EncodedImageFormat::kSVG: + os << "SVG"; + break; + default: + os << "Unknown"; + } + return os; } - return os; - } - namespace SvgDecoder - { - // Helper function to check if data contains SVG content - static bool IsSVG(const vector &data) + namespace SvgDecoder { - if (data.empty()) - return false; + // Helper function to check if data contains SVG content + static bool IsSVG(const vector &data) + { + if (data.empty()) + return false; - // Look for SVG signature in the first few hundred bytes - size_t search_length = min(data.size(), size_t(512)); - string start_data(data.begin(), data.begin() + search_length); + // Look for SVG signature in the first few hundred bytes + size_t search_length = min(data.size(), size_t(512)); + string start_data(data.begin(), data.begin() + search_length); - // Convert to lowercase for case-insensitive search - transform(start_data.begin(), start_data.end(), start_data.begin(), ::tolower); + // Convert to lowercase for case-insensitive search + transform(start_data.begin(), start_data.end(), start_data.begin(), ::tolower); - // Check if it's a pure SVG file - // Look for &svg_data, + SkBitmap &decoded_bitmap, + std::optional target_width, + std::optional target_height) { - return true; - } + try + { + // Convert vector to null-terminated string + string svg_string(svg_data.begin(), svg_data.end()); - return false; - } + // Parse SVG + NSVGimage *image = nsvgParse(const_cast(svg_string.c_str()), "px", 96.0f); + if (!image) + { + cerr << "Failed to parse SVG data" << endl; + return false; + } - // Helper function to decode SVG data to SkBitmap - static bool Decode(const vector &svg_data, - SkBitmap &decoded_bitmap, - std::optional target_width, - std::optional target_height) - { - try - { - // Convert vector to null-terminated string - string svg_string(svg_data.begin(), svg_data.end()); + // Calculate dimensions - use target dimensions if provided, otherwise use original + int width, height; - // Parse SVG - NSVGimage *image = nsvgParse(const_cast(svg_string.c_str()), "px", 96.0f); - if (!image) - { - cerr << "Failed to parse SVG data" << endl; - return false; - } + // Use value primitives to simplify the checking + int target_width_value = target_width.value_or(-1); + int target_height_value = target_height.value_or(-1); - // Calculate dimensions - use target dimensions if provided, otherwise use original - int width, height; + if (target_width_value > 0 && target_height_value > 0) + { + // Both dimensions specified + width = target_width_value; + height = target_height_value; + } + else if (target_width_value > 0) + { + // Only width specified, maintain aspect ratio + width = target_width_value; + height = static_cast((target_width_value * image->height) / image->width); + } + else if (target_height_value > 0) + { + // Only height specified, maintain aspect ratio + height = target_height_value; + width = static_cast((target_height_value * image->width) / image->height); + } + else + { + // No target dimensions, use original + width = static_cast(image->width); + height = static_cast(image->height); + } - // Use value primitives to simplify the checking - int target_width_value = target_width.value_or(-1); - int target_height_value = target_height.value_or(-1); + if (width <= 0 || height <= 0) + { + cerr << "Invalid SVG dimensions: " << width << "x" << height << endl; + nsvgDelete(image); + return false; + } - if (target_width_value > 0 && target_height_value > 0) - { - // Both dimensions specified - width = target_width_value; - height = target_height_value; - } - else if (target_width_value > 0) - { - // Only width specified, maintain aspect ratio - width = target_width_value; - height = static_cast((target_width_value * image->height) / image->width); - } - else if (target_height_value > 0) - { - // Only height specified, maintain aspect ratio - height = target_height_value; - width = static_cast((target_height_value * image->width) / image->height); - } - else - { - // No target dimensions, use original - width = static_cast(image->width); - height = static_cast(image->height); - } + // Apply size constraints similar to other image formats + int max_size = max(width, height); + if (max_size > transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) + { + float scale = static_cast(transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) / max_size; + width = static_cast(width * scale); + height = static_cast(height * scale); + } - if (width <= 0 || height <= 0) - { - cerr << "Invalid SVG dimensions: " << width << "x" << height << endl; - nsvgDelete(image); - return false; - } + // Create rasterizer + NSVGrasterizer *rast = nsvgCreateRasterizer(); + if (!rast) + { + cerr << "Failed to create SVG rasterizer" << endl; + nsvgDelete(image); + return false; + } - // Apply size constraints similar to other image formats - int max_size = max(width, height); - if (max_size > transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) - { - float scale = static_cast(transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) / max_size; - width = static_cast(width * scale); - height = static_cast(height * scale); - } + // Allocate bitmap + SkImageInfo info = SkImageInfo::Make(width, + height, + kN32_SkColorType, + kPremul_SkAlphaType); + decoded_bitmap.allocPixels(info); - // Create rasterizer - NSVGrasterizer *rast = nsvgCreateRasterizer(); - if (!rast) - { - cerr << "Failed to create SVG rasterizer" << endl; + // Clear the bitmap to transparent + decoded_bitmap.eraseColor(SK_ColorTRANSPARENT); + + // Calculate scale factor + float scale = min(static_cast(width) / image->width, + static_cast(height) / image->height); + + // Rasterize SVG to bitmap + nsvgRasterize(rast, + image, + 0, + 0, + scale, + static_cast(decoded_bitmap.getPixels()), + width, + height, + decoded_bitmap.rowBytes()); + + // Cleanup + nsvgDeleteRasterizer(rast); nsvgDelete(image); + return true; + } + catch (const exception &e) + { + cerr << "SVG decoding error: " << e.what() << endl; return false; } - - // Allocate bitmap - SkImageInfo info = SkImageInfo::Make(width, - height, - kN32_SkColorType, - kPremul_SkAlphaType); - decoded_bitmap.allocPixels(info); - - // Clear the bitmap to transparent - decoded_bitmap.eraseColor(SK_ColorTRANSPARENT); - - // Calculate scale factor - float scale = min(static_cast(width) / image->width, - static_cast(height) / image->height); - - // Rasterize SVG to bitmap - nsvgRasterize(rast, - image, - 0, - 0, - scale, - static_cast(decoded_bitmap.getPixels()), - width, - height, - decoded_bitmap.rowBytes()); - - // Cleanup - nsvgDeleteRasterizer(rast); - nsvgDelete(image); - return true; - } - catch (const exception &e) - { - cerr << "SVG decoding error: " << e.what() << endl; - return false; } } - } - bool ImageCodec::Decode(const vector &image_data, - EncodedImageFormat *image_format, - SkBitmap &decoded_bitmap, - const string &src_hint, - std::optional target_width, - std::optional target_height) - { - // Check if this is SVG data first - if (SvgDecoder::IsSVG(image_data)) + bool ImageCodec::Decode(const vector &image_data, + EncodedImageFormat *image_format, + SkBitmap &decoded_bitmap, + const string &src_hint, + std::optional target_width, + std::optional target_height) { - if (image_format != nullptr) - *image_format = EncodedImageFormat::SVG(); - return SvgDecoder::Decode(image_data, decoded_bitmap, target_width, target_height); - } + // Check if this is SVG data first + if (SvgDecoder::IsSVG(image_data)) + { + if (image_format != nullptr) + *image_format = EncodedImageFormat::SVG(); + return SvgDecoder::Decode(image_data, decoded_bitmap, target_width, target_height); + } - static constexpr const SkCodecs::Decoder decoders[] = { - SkBmpDecoder::Decoder(), - SkPngDecoder::Decoder(), - SkJpegDecoder::Decoder(), - SkWebpDecoder::Decoder(), - SkGifDecoder::Decoder()}; - - bool is_image_data_decoded = false; - sk_sp imageData = SkData::MakeWithoutCopy(image_data.data(), image_data.size()); - unique_ptr codec = SkCodec::MakeFromData(imageData, decoders); - if (codec) - { - try + static constexpr const SkCodecs::Decoder decoders[] = { + SkBmpDecoder::Decoder(), + SkPngDecoder::Decoder(), + SkJpegDecoder::Decoder(), + SkWebpDecoder::Decoder(), + SkGifDecoder::Decoder()}; + + bool is_image_data_decoded = false; + sk_sp imageData = SkData::MakeWithoutCopy(image_data.data(), image_data.size()); + unique_ptr codec = SkCodec::MakeFromData(imageData, decoders); + if (codec) { - SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); - int max_size = max(info.width(), info.height()); - if (max_size > transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) + try { - // We need to constrain the image size to avoid the huge memory usage, for example, if there are 20 images, - // each image is 4096x4096, the total size will be 20 * 4096 * 4096 * 4 = 20 * 64MB ~ 1.28GB, which is too - // much for a single application. - // - // In Web standard, no guarantee that the image size is less than a certain size, in JSAR, we do downsample - // the oversized image to fit the maximum allowed size to avoid the huge memory usage for the - // back-compatibility. - // - // TODO(yorkie): support tweaking or disabling for different platforms? - int original_width = info.width(); - int original_height = info.height(); - float scale = min(static_cast(transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) / original_width, - static_cast(transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) / original_height); - int scaled_width = static_cast(original_width * scale); - int scaled_height = static_cast(original_height * scale); - SkImageInfo scaled_info = info.makeWH(scaled_width, scaled_height); - - // Allocate the scaled bitmap with the scaled image info. - SkBitmap scaled_bitmap; - scaled_bitmap.allocPixels(scaled_info); - - // Call `getPixels()` first to check if the current codec supports scaling. - auto r = codec->getPixels(scaled_info, scaled_bitmap.getPixels(), scaled_bitmap.rowBytes()); - if (r == SkCodec::kSuccess) - { - // Returns the scaled bitmap if the scaling is successful. - decoded_bitmap = scaled_bitmap; - } - else if (r == SkCodec::kInvalidScale) + SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); + int max_size = max(info.width(), info.height()); + if (max_size > transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) { - // `InvalidScale` means the codec does not support scaling, so we need to do the scaling after decoding - // the original pixels. - SkBitmap original_bitmap; - original_bitmap.allocPixels(info); - const SkPixmap &original_pixmap = original_bitmap.pixmap(); - - // Decoding the original image pixels. - r = codec->getPixels(original_pixmap); - if (r != SkCodec::kSuccess) - throw runtime_error("Could not decode the original image data."); - - // Use linear filtering to scale the original pixmap to the scaled bitmap. - if (original_pixmap.scalePixels(scaled_bitmap.pixmap(), - SkSamplingOptions(SkFilterMode::kLinear))) + // We need to constrain the image size to avoid the huge memory usage, for example, if there are 20 images, + // each image is 4096x4096, the total size will be 20 * 4096 * 4096 * 4 = 20 * 64MB ~ 1.28GB, which is too + // much for a single application. + // + // In Web standard, no guarantee that the image size is less than a certain size, in JSAR, we do downsample + // the oversized image to fit the maximum allowed size to avoid the huge memory usage for the + // back-compatibility. + // + // TODO(yorkie): support tweaking or disabling for different platforms? + int original_width = info.width(); + int original_height = info.height(); + float scale = min(static_cast(transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) / original_width, + static_cast(transmute::ImageProcessor::DEFAULT_MAX_IMAGE_SIZE) / original_height); + int scaled_width = static_cast(original_width * scale); + int scaled_height = static_cast(original_height * scale); + SkImageInfo scaled_info = info.makeWH(scaled_width, scaled_height); + + // Allocate the scaled bitmap with the scaled image info. + SkBitmap scaled_bitmap; + scaled_bitmap.allocPixels(scaled_info); + + // Call `getPixels()` first to check if the current codec supports scaling. + auto r = codec->getPixels(scaled_info, scaled_bitmap.getPixels(), scaled_bitmap.rowBytes()); + if (r == SkCodec::kSuccess) { + // Returns the scaled bitmap if the scaling is successful. decoded_bitmap = scaled_bitmap; } - else + else if (r == SkCodec::kInvalidScale) { - // FIXME(yorkie): should use `original_bitmap` as the fallback? - throw runtime_error("Could not scale a valid bitmap."); + // `InvalidScale` means the codec does not support scaling, so we need to do the scaling after decoding + // the original pixels. + SkBitmap original_bitmap; + original_bitmap.allocPixels(info); + const SkPixmap &original_pixmap = original_bitmap.pixmap(); + + // Decoding the original image pixels. + r = codec->getPixels(original_pixmap); + if (r != SkCodec::kSuccess) + throw runtime_error("Could not decode the original image data."); + + // Use linear filtering to scale the original pixmap to the scaled bitmap. + if (original_pixmap.scalePixels(scaled_bitmap.pixmap(), + SkSamplingOptions(SkFilterMode::kLinear))) + { + decoded_bitmap = scaled_bitmap; + } + else + { + // FIXME(yorkie): should use `original_bitmap` as the fallback? + throw runtime_error("Could not scale a valid bitmap."); + } } } + else + { + // No need to scale if the image size is within the maximum allowed size. + decoded_bitmap.allocPixels(info); + auto r = codec->getPixels(info, decoded_bitmap.getPixels(), decoded_bitmap.rowBytes()); + if (r != SkCodec::kSuccess) + throw runtime_error(SkCodec::ResultToString(r)); + } + + if (image_format != nullptr) + *image_format = EncodedImageFormat(codec->getEncodedFormat()); + is_image_data_decoded = true; } - else + catch (const exception &e) { - // No need to scale if the image size is within the maximum allowed size. - decoded_bitmap.allocPixels(info); - auto r = codec->getPixels(info, decoded_bitmap.getPixels(), decoded_bitmap.rowBytes()); - if (r != SkCodec::kSuccess) - throw runtime_error(SkCodec::ResultToString(r)); + cerr << "Failed to decode the image: " << e.what() << endl + << " size: " << image_data.size() << endl + << " data: " << (image_data.data() != nullptr ? "valid" : "(empty)") << endl; + is_image_data_decoded = false; } - - if (image_format != nullptr) - *image_format = EncodedImageFormat(codec->getEncodedFormat()); - is_image_data_decoded = true; } - catch (const exception &e) + else { - cerr << "Failed to decode the image: " << e.what() << endl + cerr << "Failed to create the image codec, url: " << src_hint << endl << " size: " << image_data.size() << endl << " data: " << (image_data.data() != nullptr ? "valid" : "(empty)") << endl; is_image_data_decoded = false; } - } - else - { - cerr << "Failed to create the image codec, url: " << src_hint << endl - << " size: " << image_data.size() << endl - << " data: " << (image_data.data() != nullptr ? "valid" : "(empty)") << endl; - is_image_data_decoded = false; - } - return is_image_data_decoded; + return is_image_data_decoded; + } } -} +} // namespace endor diff --git a/src/client/canvas/image_codec.hpp b/src/client/canvas/image_codec.hpp index 1622fb66a..c8b2acfc2 100644 --- a/src/client/canvas/image_codec.hpp +++ b/src/client/canvas/image_codec.hpp @@ -7,64 +7,67 @@ #include #include -namespace canvas +namespace endor { - class EncodedImageFormat + namespace canvas { - public: - enum : int + class EncodedImageFormat { - kUnknown, - kBMP, - kGIF, - kICO, - kJPEG, - kPNG, - kWBMP, - kWEBP, - kPKM, - kKTX, - kASTC, - kDNG, - kHEIF, - kAVIF, - kJPEGXL, - kSVG - }; + public: + enum : int + { + kUnknown, + kBMP, + kGIF, + kICO, + kJPEG, + kPNG, + kWBMP, + kWEBP, + kPKM, + kKTX, + kASTC, + kDNG, + kHEIF, + kAVIF, + kJPEGXL, + kSVG + }; - public: - EncodedImageFormat(int format = kUnknown) - : format_(format) - { - } - EncodedImageFormat(SkEncodedImageFormat); + public: + EncodedImageFormat(int format = kUnknown) + : format_(format) + { + } + EncodedImageFormat(SkEncodedImageFormat); - friend std::ostream &operator<<(std::ostream &os, const EncodedImageFormat &format); + friend std::ostream &operator<<(std::ostream &os, const EncodedImageFormat &format); - static inline EncodedImageFormat SVG() - { - return EncodedImageFormat(kSVG); - } + static inline EncodedImageFormat SVG() + { + return EncodedImageFormat(kSVG); + } - public: - inline bool isSVG() const - { - return format_ == kSVG; - } + public: + inline bool isSVG() const + { + return format_ == kSVG; + } - private: - int format_ = kUnknown; - }; + private: + int format_ = kUnknown; + }; - class ImageCodec - { - public: - // Decoding image data using Skia codecs. - static bool Decode(const std::vector &image_data, - EncodedImageFormat *image_format, - SkBitmap &decoded_bitmap, - const std::string &src_hint, - std::optional target_width = std::nullopt, - std::optional target_height = std::nullopt); - }; -} + class ImageCodec + { + public: + // Decoding image data using Skia codecs. + static bool Decode(const std::vector &image_data, + EncodedImageFormat *image_format, + SkBitmap &decoded_bitmap, + const std::string &src_hint, + std::optional target_width = std::nullopt, + std::optional target_height = std::nullopt); + }; + } +} // namespace endor diff --git a/src/client/canvas/image_data.cpp b/src/client/canvas/image_data.cpp index fc3d8c5f4..c9c0ac34c 100644 --- a/src/client/canvas/image_data.cpp +++ b/src/client/canvas/image_data.cpp @@ -4,55 +4,58 @@ #include #include "./image_data.hpp" -namespace canvas +namespace endor { - ImageData::ImageData(size_t width, size_t height, std::string colorSpaceName) + namespace canvas { - size_t rowBytes = width * 4; - pixelsStorage = new char[height * rowBytes]; - memset(pixelsStorage, 0, height * rowBytes); - - pixmap.reset(SkImageInfo::MakeN32Premul(width, height), pixelsStorage, rowBytes); - updateColorSpace(colorSpaceName); - } - - ImageData::ImageData(std::vector &dataArray, size_t width, size_t height, std::string colorSpaceName) - { - size_t rowBytes = width * 4; - pixelsStorage = new char[height * rowBytes]; - if (dataArray.size() < height * rowBytes) + ImageData::ImageData(size_t width, size_t height, std::string colorSpaceName) + { + size_t rowBytes = width * 4; + pixelsStorage = new char[height * rowBytes]; memset(pixelsStorage, 0, height * rowBytes); - memcpy(pixelsStorage, dataArray.data(), height * rowBytes); - pixmap.reset(SkImageInfo::MakeN32Premul(width, height), pixelsStorage, rowBytes); - updateColorSpace(colorSpaceName); - } + pixmap.reset(SkImageInfo::MakeN32Premul(width, height), pixelsStorage, rowBytes); + updateColorSpace(colorSpaceName); + } - ImageData::~ImageData() - { - pixmap.reset(); - if (pixelsStorage) + ImageData::ImageData(std::vector &dataArray, size_t width, size_t height, std::string colorSpaceName) { - delete[] pixelsStorage; - pixelsStorage = nullptr; + size_t rowBytes = width * 4; + pixelsStorage = new char[height * rowBytes]; + if (dataArray.size() < height * rowBytes) + memset(pixelsStorage, 0, height * rowBytes); + memcpy(pixelsStorage, dataArray.data(), height * rowBytes); + + pixmap.reset(SkImageInfo::MakeN32Premul(width, height), pixelsStorage, rowBytes); + updateColorSpace(colorSpaceName); } - } - bool ImageData::updateColorSpace(std::string colorSpaceName) - { - if (colorSpaceName == "srgb" || colorSpaceName == "display-p3") + ImageData::~ImageData() { - if (colorSpaceName == "srgb") - pixmap.setColorSpace(SkColorSpace::MakeSRGB()); - else if (colorSpaceName == "display-p3") - pixmap.setColorSpace(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kDisplayP3)); - - colorSpaceName_ = colorSpaceName; - return true; + pixmap.reset(); + if (pixelsStorage) + { + delete[] pixelsStorage; + pixelsStorage = nullptr; + } } - else + + bool ImageData::updateColorSpace(std::string colorSpaceName) { - return false; + if (colorSpaceName == "srgb" || colorSpaceName == "display-p3") + { + if (colorSpaceName == "srgb") + pixmap.setColorSpace(SkColorSpace::MakeSRGB()); + else if (colorSpaceName == "display-p3") + pixmap.setColorSpace(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kDisplayP3)); + + colorSpaceName_ = colorSpaceName; + return true; + } + else + { + return false; + } } } -} +} // namespace endor diff --git a/src/client/canvas/image_data.hpp b/src/client/canvas/image_data.hpp index 04a3fc38f..bb09f4269 100644 --- a/src/client/canvas/image_data.hpp +++ b/src/client/canvas/image_data.hpp @@ -9,71 +9,74 @@ #include #include "./image_source.hpp" -namespace canvas +namespace endor { - class ImageData : public ImageSource, - public scripting_base::JSObjectHolder + namespace canvas { - public: - ImageData(size_t width, size_t height, std::string colorSpaceName = "srgb"); - ImageData(std::vector &dataArray, size_t width, size_t height, std::string colorSpaceName = "srgb"); - ~ImageData(); - - public: - size_t width() const override - { - return pixmap.width(); - } - size_t height() const override - { - return pixmap.height(); - } - bool readPixels(SkPixmap &dst) const override + class ImageData : public ImageSource, + public scripting_base::JSObjectHolder { - return pixmap.readPixels(dst, 0, 0); - } - bool peekPixels(SkPixmap *outPixmap) const override - { - if (outPixmap == nullptr) - return false; - *outPixmap = pixmap; - return true; - } + public: + ImageData(size_t width, size_t height, std::string colorSpaceName = "srgb"); + ImageData(std::vector &dataArray, size_t width, size_t height, std::string colorSpaceName = "srgb"); + ~ImageData(); - public: - inline SkColorSpace *colorSpace() const - { - return pixmap.colorSpace(); - } - inline std::string colorSpaceName() const - { - return colorSpaceName_; - } - inline void *addr() const - { - return pixmap.writable_addr(); - } - inline size_t rowBytes() const - { - return pixmap.rowBytes(); - } - inline size_t computeByteSize() const - { - return pixmap.computeByteSize(); - } + public: + size_t width() const override + { + return pixmap.width(); + } + size_t height() const override + { + return pixmap.height(); + } + bool readPixels(SkPixmap &dst) const override + { + return pixmap.readPixels(dst, 0, 0); + } + bool peekPixels(SkPixmap *outPixmap) const override + { + if (outPixmap == nullptr) + return false; + *outPixmap = pixmap; + return true; + } + + public: + inline SkColorSpace *colorSpace() const + { + return pixmap.colorSpace(); + } + inline std::string colorSpaceName() const + { + return colorSpaceName_; + } + inline void *addr() const + { + return pixmap.writable_addr(); + } + inline size_t rowBytes() const + { + return pixmap.rowBytes(); + } + inline size_t computeByteSize() const + { + return pixmap.computeByteSize(); + } - private: - /** + private: + /** * Update the color space by name. * * @param colorSpaceName The name of the color space: "srgb" or "display-p3". * @return True if the color space was updated, false otherwise. */ - bool updateColorSpace(std::string colorSpaceName); + bool updateColorSpace(std::string colorSpaceName); - private: - SkPixmap pixmap; - std::string colorSpaceName_; - char *pixelsStorage = nullptr; - }; -} + private: + SkPixmap pixmap; + std::string colorSpaceName_; + char *pixelsStorage = nullptr; + }; + } +} // namespace endor diff --git a/src/client/canvas/image_source.hpp b/src/client/canvas/image_source.hpp index 78a6a767e..20d807e52 100644 --- a/src/client/canvas/image_source.hpp +++ b/src/client/canvas/image_source.hpp @@ -7,74 +7,77 @@ #include #include -namespace canvas +namespace endor { - /** - * The virtual class `ImageSource` defines pure virtual methods for these DOM classes, which pixels could be read from - * some APIs like: createImageBitmap(image), gl.texImage2D(image) and others. - */ - class ImageSource + namespace canvas { - public: - ImageSource() - { - } - virtual ~ImageSource() = default; - - public: /** - * Returns the width of this image. + * The virtual class `ImageSource` defines pure virtual methods for these DOM classes, which pixels could be read from + * some APIs like: createImageBitmap(image), gl.texImage2D(image) and others. */ - virtual size_t width() const = 0; + class ImageSource + { + public: + ImageSource() + { + } + virtual ~ImageSource() = default; - /** - * Returns the height of this image. - */ - virtual size_t height() const = 0; + public: + /** + * Returns the width of this image. + */ + virtual size_t width() const = 0; - /** - * Read the image pixels to the given `SkPixmap`. - * - * @param dst The SkPixmap to receive the data. - * @returns true if the read is finished, otherwise false. - */ - virtual bool readPixels(SkPixmap &dst) const = 0; + /** + * Returns the height of this image. + */ + virtual size_t height() const = 0; - /** - * Peek the image pixels to the given `SkPixmap` without copying. - * - * @param pixmap The SkPixmap to receive the data. - * @returns true if the peek is finished, otherwise false. - */ - virtual bool peekPixels(SkPixmap *pixmap) const = 0; + /** + * Read the image pixels to the given `SkPixmap`. + * + * @param dst The SkPixmap to receive the data. + * @returns true if the read is finished, otherwise false. + */ + virtual bool readPixels(SkPixmap &dst) const = 0; - /** - * Create a new `SkImage` from this image source. - */ - inline sk_sp makeSkImage() - { - auto bitmap = makeSkBitmap(); - if (bitmap == nullptr) - return nullptr; - else - return SkImages::RasterFromBitmap(*bitmap); - } + /** + * Peek the image pixels to the given `SkPixmap` without copying. + * + * @param pixmap The SkPixmap to receive the data. + * @returns true if the peek is finished, otherwise false. + */ + virtual bool peekPixels(SkPixmap *pixmap) const = 0; - /** - * Create a new `SkBitmap` from this image source. - */ - std::shared_ptr makeSkBitmap() - { - SkImageInfo info = SkImageInfo::MakeN32Premul(width(), height()); - SkBitmap bitmap; - bitmap.allocPixels(info); + /** + * Create a new `SkImage` from this image source. + */ + inline sk_sp makeSkImage() + { + auto bitmap = makeSkBitmap(); + if (bitmap == nullptr) + return nullptr; + else + return SkImages::RasterFromBitmap(*bitmap); + } + + /** + * Create a new `SkBitmap` from this image source. + */ + std::shared_ptr makeSkBitmap() + { + SkImageInfo info = SkImageInfo::MakeN32Premul(width(), height()); + SkBitmap bitmap; + bitmap.allocPixels(info); - SkPixmap pixmap; - pixmap.reset(info, bitmap.getPixels(), bitmap.rowBytes()); + SkPixmap pixmap; + pixmap.reset(info, bitmap.getPixels(), bitmap.rowBytes()); - if (!readPixels(pixmap)) - return nullptr; - return std::make_shared(bitmap); - } - }; -} + if (!readPixels(pixmap)) + return nullptr; + return std::make_shared(bitmap); + } + }; + } +} // namespace endor diff --git a/src/client/canvas/path2d.cpp b/src/client/canvas/path2d.cpp index 1456ef316..c9f11c9a7 100644 --- a/src/client/canvas/path2d.cpp +++ b/src/client/canvas/path2d.cpp @@ -1,139 +1,142 @@ #include "./path2d.hpp" -namespace canvas +namespace endor { - inline float radToDeg(float rad) + namespace canvas { - return rad * 180 / M_PI; - } - - inline float degToRad(int deg) - { - return deg * M_PI / 180; - } - - inline bool almostEqual(float a, float b, float epsilon = 0.00001) - { - return fabs(a - b) < epsilon; - } - - void ellipseHelper(std::shared_ptr skPath, - float x, - float y, - float radiusX, - float radiusY, - float startAngle, - float endAngle) - { - auto sweepDegrees = radToDeg(endAngle - startAngle); - auto startDegrees = radToDeg(startAngle); - - auto oval = SkRect::MakeLTRB(x - radiusX, y - radiusY, x + radiusX, y + radiusY); - if (almostEqual(fabs(sweepDegrees), 360)) + inline float radToDeg(float rad) { - auto halfSweep = sweepDegrees / 2; - skPath->arcTo(oval, startDegrees, halfSweep, false); - skPath->arcTo(oval, startDegrees + halfSweep, halfSweep, false); + return rad * 180 / M_PI; } - else + + inline float degToRad(int deg) { - skPath->arcTo(oval, startDegrees, sweepDegrees, false); + return deg * M_PI / 180; } - } - void Path2D::ApplyArcToTangent(std::shared_ptr skPath, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) - { - skPath->arcTo(x1, y1, x2, y2, radius); - } + inline bool almostEqual(float a, float b, float epsilon = 0.00001) + { + return fabs(a - b) < epsilon; + } - void Path2D::Ellipse(std::shared_ptr skPath, + void ellipseHelper(std::shared_ptr skPath, float x, float y, float radiusX, float radiusY, - float rotation, float startAngle, - float endAngle, - bool ccw) - { - if (radiusX < 0 || radiusY < 0) - throw std::runtime_error("radii cannot be negative"); - - // based off of CanonicalizeAngle in Chrome. - auto tao = 2 * M_PI; - auto newStartAngle = fmod(startAngle, tao); - if (newStartAngle < 0) - newStartAngle += tao; - auto delta = newStartAngle - startAngle; - startAngle = newStartAngle; - endAngle += delta; - - // Based off of AdjustEndAngle in Chrome. - if (!ccw && (endAngle - startAngle) >= tao) - endAngle = startAngle + tao; - else if (ccw && (startAngle - endAngle) >= tao) - endAngle = startAngle - tao; - else if (!ccw && startAngle > endAngle) - endAngle = startAngle + (tao - fmod(startAngle - endAngle, tao)); - else if (ccw && startAngle < endAngle) - endAngle = startAngle - (tao - fmod(endAngle - startAngle, tao)); - - if (!rotation) - { - ellipseHelper(skPath, x, y, radiusX, radiusY, startAngle, endAngle); - } - else - { - SkMatrix rotated, rotatedInvert; - rotated.setRotate(rotation, x, y); - rotatedInvert.setRotate(-rotation, x, y); - skPath->transform(rotatedInvert); - ellipseHelper(skPath, x, y, radiusX, radiusY, startAngle, endAngle); - skPath->transform(rotated); + float endAngle) + { + auto sweepDegrees = radToDeg(endAngle - startAngle); + auto startDegrees = radToDeg(startAngle); + + auto oval = SkRect::MakeLTRB(x - radiusX, y - radiusY, x + radiusX, y + radiusY); + if (almostEqual(fabs(sweepDegrees), 360)) + { + auto halfSweep = sweepDegrees / 2; + skPath->arcTo(oval, startDegrees, halfSweep, false); + skPath->arcTo(oval, startDegrees + halfSweep, halfSweep, false); + } + else + { + skPath->arcTo(oval, startDegrees, sweepDegrees, false); + } } - } - void Path2D::addPath() - { - } + void Path2D::ApplyArcToTangent(std::shared_ptr skPath, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) + { + skPath->arcTo(x1, y1, x2, y2, radius); + } - void Path2D::closePath() - { - } + void Path2D::Ellipse(std::shared_ptr skPath, + float x, + float y, + float radiusX, + float radiusY, + float rotation, + float startAngle, + float endAngle, + bool ccw) + { + if (radiusX < 0 || radiusY < 0) + throw std::runtime_error("radii cannot be negative"); + + // based off of CanonicalizeAngle in Chrome. + auto tao = 2 * M_PI; + auto newStartAngle = fmod(startAngle, tao); + if (newStartAngle < 0) + newStartAngle += tao; + auto delta = newStartAngle - startAngle; + startAngle = newStartAngle; + endAngle += delta; + + // Based off of AdjustEndAngle in Chrome. + if (!ccw && (endAngle - startAngle) >= tao) + endAngle = startAngle + tao; + else if (ccw && (startAngle - endAngle) >= tao) + endAngle = startAngle - tao; + else if (!ccw && startAngle > endAngle) + endAngle = startAngle + (tao - fmod(startAngle - endAngle, tao)); + else if (ccw && startAngle < endAngle) + endAngle = startAngle - (tao - fmod(endAngle - startAngle, tao)); + + if (!rotation) + { + ellipseHelper(skPath, x, y, radiusX, radiusY, startAngle, endAngle); + } + else + { + SkMatrix rotated, rotatedInvert; + rotated.setRotate(rotation, x, y); + rotatedInvert.setRotate(-rotation, x, y); + skPath->transform(rotatedInvert); + ellipseHelper(skPath, x, y, radiusX, radiusY, startAngle, endAngle); + skPath->transform(rotated); + } + } - void Path2D::moveTo() - { - } + void Path2D::addPath() + { + } - void Path2D::lineTo() - { - } + void Path2D::closePath() + { + } - void Path2D::bezierCurveTo() - { - } + void Path2D::moveTo() + { + } - void Path2D::quadraticCurveTo() - { - } + void Path2D::lineTo() + { + } - void Path2D::arc() - { - } + void Path2D::bezierCurveTo() + { + } - void Path2D::arcTo() - { - } + void Path2D::quadraticCurveTo() + { + } - void Path2D::ellipse() - { - } + void Path2D::arc() + { + } - void Path2D::rect() - { - } + void Path2D::arcTo() + { + } - void Path2D::roundRect() - { + void Path2D::ellipse() + { + } + + void Path2D::rect() + { + } + + void Path2D::roundRect() + { + } } -} +} // namespace endor diff --git a/src/client/canvas/path2d.hpp b/src/client/canvas/path2d.hpp index 183d1583e..9b3112de6 100644 --- a/src/client/canvas/path2d.hpp +++ b/src/client/canvas/path2d.hpp @@ -3,41 +3,44 @@ #include #include -namespace canvas +namespace endor { - class Path2D + namespace canvas { - public: - Path2D() + class Path2D { - } + public: + Path2D() + { + } - public: - static void ApplyArcToTangent(std::shared_ptr skPath, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius); - static void Ellipse(std::shared_ptr skPath, - float x, - float y, - float radiusX, - float radiusY, - float rotation, - float startAngle, - float endAngle, - bool ccw); + public: + static void ApplyArcToTangent(std::shared_ptr skPath, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius); + static void Ellipse(std::shared_ptr skPath, + float x, + float y, + float radiusX, + float radiusY, + float rotation, + float startAngle, + float endAngle, + bool ccw); - public: - void addPath(); - void closePath(); - void moveTo(); - void lineTo(); - void bezierCurveTo(); - void quadraticCurveTo(); - void arc(); - void arcTo(); - void ellipse(); - void rect(); - void roundRect(); + public: + void addPath(); + void closePath(); + void moveTo(); + void lineTo(); + void bezierCurveTo(); + void quadraticCurveTo(); + void arc(); + void arcTo(); + void ellipse(); + void rect(); + void roundRect(); - private: - SkPath skPath; - }; -} + private: + SkPath skPath; + }; + } +} // namespace endor diff --git a/src/client/canvas/rendering_context.hpp b/src/client/canvas/rendering_context.hpp index 95e293c46..a12dba93b 100644 --- a/src/client/canvas/rendering_context.hpp +++ b/src/client/canvas/rendering_context.hpp @@ -6,51 +6,54 @@ #include #include "../per_process.hpp" -namespace canvas +namespace endor { - enum class RenderingContextType + namespace canvas { - Unset, - RenderingContext2D, - WebGL, - WebGL2, - BitmapRenderer, - }; - - template - class RenderingContextBase : public scripting_base::JSObjectHolder - { - public: - RenderingContextBase(RenderingContextType type, std::shared_ptr canvasRef) - : contextType(type) - , canvasRef(canvasRef) - , clientContext(TrClientContextPerProcess::Get()) + enum class RenderingContextType { - assert(clientContext != nullptr); - assert(canvasRef != nullptr && "Canvas reference cannot be null"); - } - virtual ~RenderingContextBase() = default; - - protected: - // Notify the context's canvas object that the pixels might be changed. - void notifyCanvasUpdated() + Unset, + RenderingContext2D, + WebGL, + WebGL2, + BitmapRenderer, + }; + + template + class RenderingContextBase : public scripting_base::JSObjectHolder { - if (TR_UNLIKELY(canvasRef.expired())) - return; - - auto canvas = canvasRef.lock(); - assert(canvas != nullptr && "Canvas reference is expired"); - if (canvas->pixels_updated_callback_ != nullptr) - canvas->pixels_updated_callback_(); - } - - public: - RenderingContextType contextType; - std::weak_ptr canvasRef; - - protected: - TrClientContextPerProcess *clientContext = nullptr; - }; -} + public: + RenderingContextBase(RenderingContextType type, std::shared_ptr canvasRef) + : contextType(type) + , canvasRef(canvasRef) + , clientContext(TrClientContextPerProcess::Get()) + { + assert(clientContext != nullptr); + assert(canvasRef != nullptr && "Canvas reference cannot be null"); + } + virtual ~RenderingContextBase() = default; + + protected: + // Notify the context's canvas object that the pixels might be changed. + void notifyCanvasUpdated() + { + if (TR_UNLIKELY(canvasRef.expired())) + return; + + auto canvas = canvasRef.lock(); + assert(canvas != nullptr && "Canvas reference is expired"); + if (canvas->pixels_updated_callback_ != nullptr) + canvas->pixels_updated_callback_(); + } + + public: + RenderingContextType contextType; + std::weak_ptr canvasRef; + + protected: + TrClientContextPerProcess *clientContext = nullptr; + }; + } +} // namespace endor #include "./rendering_context2d.hpp" diff --git a/src/client/canvas/rendering_context2d-inl.hpp b/src/client/canvas/rendering_context2d-inl.hpp index 43aa487f2..4e34d5950 100644 --- a/src/client/canvas/rendering_context2d-inl.hpp +++ b/src/client/canvas/rendering_context2d-inl.hpp @@ -1,96 +1,100 @@ +#pragma once + #include #include #include #include #include +#include #include "./rendering_context2d.hpp" #include "./path2d.hpp" -#include "../per_process.hpp" -namespace canvas +namespace endor { - template - CanvasRenderingContext2D::CanvasRenderingContext2D(std::shared_ptr canvasRef) - : RenderingContextBase(RenderingContextType::RenderingContext2D, canvasRef) + namespace canvas { - reset(canvasRef->skSurface); - - skPaint = std::make_shared(); - skPaint->setAntiAlias(true); - skPaint->setStrokeMiter(10); - skPaint->setBlendMode(globalCompositeOperation); - skPaint->setColor(fillStyle); - skPaint->setStrokeWidth(strokeWidth); - - auto fontMgr = this->clientContext->getFontCacheManager(); - skFont = std::make_shared(fontMgr.getTypeface(), 50); - skFont->setSubpixel(true); - } + template + CanvasRenderingContext2D::CanvasRenderingContext2D(std::shared_ptr canvasRef) + : RenderingContextBase(RenderingContextType::RenderingContext2D, canvasRef) + { + reset(canvasRef->skSurface); - template - bool CanvasRenderingContext2D::setFillStyle(const std::string &style) - { - auto color = crates::css2::parsing::parseColor(style); - fillStyle = SkColorSetARGB(color.a(), color.r(), color.g(), color.b()); - return true; - } + skPaint = std::make_shared(); + skPaint->setAntiAlias(true); + skPaint->setStrokeMiter(10); + skPaint->setBlendMode(globalCompositeOperation); + skPaint->setColor(fillStyle); + skPaint->setStrokeWidth(strokeWidth); - template - bool CanvasRenderingContext2D::setStrokeStyle(const std::string &style) - { - auto color = crates::css2::parsing::parseColor(style); - strokeStyle = SkColorSetARGB(color.a(), color.r(), color.g(), color.b()); - return true; - } + auto fontMgr = this->clientContext->getFontCacheManager(); + skFont = std::make_shared(fontMgr.getTypeface(), 50); + skFont->setSubpixel(true); + } - template - std::string CanvasRenderingContext2D::getFont() - { - return fontStr; - } + template + bool CanvasRenderingContext2D::setFillStyle(const std::string &style) + { + auto color = crates::css2::parsing::parseColor(style); + fillStyle = SkColorSetARGB(color.a(), color.r(), color.g(), color.b()); + return true; + } - template - bool CanvasRenderingContext2D::setFont(const std::string &str) - { - font::FontShorthandParser descriptor(str); - if (descriptor.success) + template + bool CanvasRenderingContext2D::setStrokeStyle(const std::string &style) { - auto fontMgr = this->clientContext->getFontCacheManager(); - auto typeface = fontMgr.getTypeface(descriptor); - if (typeface) + auto color = crates::css2::parsing::parseColor(style); + strokeStyle = SkColorSetARGB(color.a(), color.r(), color.g(), color.b()); + return true; + } + + template + std::string CanvasRenderingContext2D::getFont() + { + return fontStr; + } + + template + bool CanvasRenderingContext2D::setFont(const std::string &str) + { + font::FontShorthandParser descriptor(str); + if (descriptor.success) { - skFont->setSize(descriptor.sizeInPx); - skFont->setTypeface(typeface); - fontStr = str; - return true; + auto fontMgr = this->clientContext->getFontCacheManager(); + auto typeface = fontMgr.getTypeface(descriptor); + if (typeface) + { + skFont->setSize(descriptor.sizeInPx); + skFont->setTypeface(typeface); + fontStr = str; + return true; + } + else + { + std::cerr << "Failed to find typeface for font: " << str << std::endl; + return false; + } } else { - std::cerr << "Failed to find typeface for font: " << str << std::endl; + std::cerr << "Failed to parse font descriptor: " << str << std::endl; return false; } } - else + + template + float CanvasRenderingContext2D::getGlobalAlpha() { - std::cerr << "Failed to parse font descriptor: " << str << std::endl; - return false; + return globalAlpha; } - } - - template - float CanvasRenderingContext2D::getGlobalAlpha() - { - return globalAlpha; - } - template - bool CanvasRenderingContext2D::setGlobalAlpha(float alpha) - { - globalAlpha = alpha; - return true; - } + template + bool CanvasRenderingContext2D::setGlobalAlpha(float alpha) + { + globalAlpha = alpha; + return true; + } #define GCO_MAP(XX) \ XX(SkBlendMode::kSrcOver, "source-over") \ @@ -122,34 +126,34 @@ namespace canvas XX(SkBlendMode::kColor, "color") \ XX(SkBlendMode::kLuminosity, "luminosity") - template - std::string CanvasRenderingContext2D::getGlobalCompositeOperation() - { - switch (globalCompositeOperation) + template + std::string CanvasRenderingContext2D::getGlobalCompositeOperation() { + switch (globalCompositeOperation) + { #define XX(mode, name) \ case mode: \ return name; - GCO_MAP(XX) + GCO_MAP(XX) #undef XX - default: - return ""; + default: + return ""; + } } - } - template - bool CanvasRenderingContext2D::setGlobalCompositeOperation(const std::string &str) - { + template + bool CanvasRenderingContext2D::setGlobalCompositeOperation(const std::string &str) + { #define XX(mode, name) \ if (str == name) \ { \ globalCompositeOperation = mode; \ return true; \ } - GCO_MAP(XX) + GCO_MAP(XX) #undef XX - return false; - } + return false; + } #define TEXT_ALIGN_MAP(XX) \ XX(TextAlign::Start, "start") \ @@ -158,34 +162,34 @@ namespace canvas XX(TextAlign::Right, "right") \ XX(TextAlign::Center, "center") - template - std::string CanvasRenderingContext2D::getTextAlign() - { - switch (textAlign) + template + std::string CanvasRenderingContext2D::getTextAlign() { + switch (textAlign) + { #define XX(id, name) \ case id: \ return name; - TEXT_ALIGN_MAP(XX) + TEXT_ALIGN_MAP(XX) #undef XX - default: - return ""; + default: + return ""; + } } - } - template - bool CanvasRenderingContext2D::setTextAlign(const std::string &str) - { + template + bool CanvasRenderingContext2D::setTextAlign(const std::string &str) + { #define XX(id, name) \ if (str == name) \ { \ textAlign = id; \ return true; \ } - TEXT_ALIGN_MAP(XX) + TEXT_ALIGN_MAP(XX) #undef XX - return false; - } + return false; + } #define TEXT_BASELINE_MAP(XX) \ XX(TextBaseline::Top, "top") \ @@ -195,750 +199,751 @@ namespace canvas XX(TextBaseline::Ideographic, "ideographic") \ XX(TextBaseline::Bottom, "bottom") - template - std::string CanvasRenderingContext2D::getTextBaseline() - { - switch (textBaseline) + template + std::string CanvasRenderingContext2D::getTextBaseline() { + switch (textBaseline) + { #define XX(id, name) \ case id: \ return name; - TEXT_BASELINE_MAP(XX) + TEXT_BASELINE_MAP(XX) #undef XX - default: - return ""; + default: + return ""; + } } - } - template - bool CanvasRenderingContext2D::setTextBaseline(const std::string &str) - { + template + bool CanvasRenderingContext2D::setTextBaseline(const std::string &str) + { #define XX(id, name) \ if (str == name) \ { \ textBaseline = id; \ return true; \ } - TEXT_BASELINE_MAP(XX) + TEXT_BASELINE_MAP(XX) #undef XX - return false; - } + return false; + } - template - float CanvasRenderingContext2D::getLineDashOffset() - { - return lineDashOffset; - } + template + float CanvasRenderingContext2D::getLineDashOffset() + { + return lineDashOffset; + } - template - float CanvasRenderingContext2D::setLineDashOffset(float offset) - { - lineDashOffset = offset; - return true; - } + template + float CanvasRenderingContext2D::setLineDashOffset(float offset) + { + lineDashOffset = offset; + return true; + } - template - float CanvasRenderingContext2D::getLineWidth() - { - return skPaint->getStrokeWidth(); - } + template + float CanvasRenderingContext2D::getLineWidth() + { + return skPaint->getStrokeWidth(); + } - template - bool CanvasRenderingContext2D::setLineWidth(float width) - { - strokeWidth = width; - skPaint->setStrokeWidth(strokeWidth); - return true; - } + template + bool CanvasRenderingContext2D::setLineWidth(float width) + { + strokeWidth = width; + skPaint->setStrokeWidth(strokeWidth); + return true; + } #define LINE_CAP_MAP(XX) \ XX(SkPaint::kButt_Cap, "butt") \ XX(SkPaint::kRound_Cap, "round") \ XX(SkPaint::kSquare_Cap, "square") - template - std::string CanvasRenderingContext2D::getLineCap() - { - switch (skPaint->getStrokeCap()) + template + std::string CanvasRenderingContext2D::getLineCap() { + switch (skPaint->getStrokeCap()) + { #define XX(id, name) \ case id: \ return name; - LINE_CAP_MAP(XX) + LINE_CAP_MAP(XX) #undef XX - default: - return ""; + default: + return ""; + } } - } - template - bool CanvasRenderingContext2D::setLineCap(const std::string &str) - { + template + bool CanvasRenderingContext2D::setLineCap(const std::string &str) + { #define XX(id, name) \ if (str == name) \ { \ skPaint->setStrokeCap(id); \ return true; \ } - LINE_CAP_MAP(XX) + LINE_CAP_MAP(XX) #undef XX - return false; - } + return false; + } #define LINE_JOIN_MAP(XX) \ XX(SkPaint::kMiter_Join, "miter") \ XX(SkPaint::kRound_Join, "round") \ XX(SkPaint::kBevel_Join, "bevel") - template - std::string CanvasRenderingContext2D::getLineJoin() - { - switch (skPaint->getStrokeJoin()) + template + std::string CanvasRenderingContext2D::getLineJoin() { + switch (skPaint->getStrokeJoin()) + { #define XX(id, name) \ case id: \ return name; - LINE_JOIN_MAP(XX) + LINE_JOIN_MAP(XX) #undef XX - default: - return ""; + default: + return ""; + } } - } - template - bool CanvasRenderingContext2D::setLineJoin(const std::string &str) - { + template + bool CanvasRenderingContext2D::setLineJoin(const std::string &str) + { #define XX(id, name) \ if (str == name) \ { \ skPaint->setStrokeJoin(id); \ return true; \ } - LINE_JOIN_MAP(XX) + LINE_JOIN_MAP(XX) #undef XX - return false; - } - - template - void CanvasRenderingContext2D::fill() - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + return false; + } - auto fillPaint = getFillPaint(); - auto shadowPaint = getShadowPaint(fillPaint); - if (shadowPaint != nullptr) + template + void CanvasRenderingContext2D::fill() { - skCanvas->save(); - // TODO - skCanvas->drawPath(*currentPath, *shadowPaint); - skCanvas->restore(); - delete shadowPaint; + if (TR_UNLIKELY(skCanvas == nullptr)) + return; + + auto fillPaint = getFillPaint(); + auto shadowPaint = getShadowPaint(fillPaint); + if (shadowPaint != nullptr) + { + skCanvas->save(); + // TODO + skCanvas->drawPath(*currentPath, *shadowPaint); + skCanvas->restore(); + delete shadowPaint; + } + skCanvas->drawPath(*currentPath, fillPaint); + this->notifyCanvasUpdated(); } - skCanvas->drawPath(*currentPath, fillPaint); - this->notifyCanvasUpdated(); - } - template - void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height) - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + template + void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height) + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return; - auto fillPaint = getFillPaint(); - // TODO: shadow painting - skCanvas->drawRect(SkRect::MakeXYWH(x, y, width, height), fillPaint); - this->notifyCanvasUpdated(); - } + auto fillPaint = getFillPaint(); + // TODO: shadow painting + skCanvas->drawRect(SkRect::MakeXYWH(x, y, width, height), fillPaint); + this->notifyCanvasUpdated(); + } - template - void CanvasRenderingContext2D::fillText(const std::string &text, float x, float y) - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + template + void CanvasRenderingContext2D::fillText(const std::string &text, float x, float y) + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return; - auto fillPaint = getFillPaint(); - auto textBlob = SkTextBlob::MakeFromString(text.c_str(), *skFont); + auto fillPaint = getFillPaint(); + auto textBlob = SkTextBlob::MakeFromString(text.c_str(), *skFont); - /** + /** * Adjust text's position based on `textAlign` and `textBaseline`. */ - auto textMetrics = measureText(text); - switch (textAlign) - { - case TextAlign::Center: - x -= textMetrics.width / 2; - break; - case TextAlign::Right: - x -= textMetrics.width; - break; - case TextAlign::Start: - case TextAlign::End: - case TextAlign::Left: - default: - break; - } - - switch (textBaseline) - { - case TextBaseline::Top: - y -= textMetrics.actualBoundingBoxAscent; - break; - case TextBaseline::Middle: - y -= (textMetrics.actualBoundingBoxAscent - textMetrics.actualBoundingBoxDescent) / 2; - break; - case TextBaseline::Bottom: - y -= textMetrics.actualBoundingBoxDescent; - break; - case TextBaseline::Hanging: - case TextBaseline::Alphabetic: - case TextBaseline::Ideographic: - default: - break; - } - skCanvas->drawTextBlob(textBlob, x, y, fillPaint); - this->notifyCanvasUpdated(); - } + auto textMetrics = measureText(text); + switch (textAlign) + { + case TextAlign::Center: + x -= textMetrics.width / 2; + break; + case TextAlign::Right: + x -= textMetrics.width; + break; + case TextAlign::Start: + case TextAlign::End: + case TextAlign::Left: + default: + break; + } - template - void CanvasRenderingContext2D::stroke() - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + switch (textBaseline) + { + case TextBaseline::Top: + y -= textMetrics.actualBoundingBoxAscent; + break; + case TextBaseline::Middle: + y -= (textMetrics.actualBoundingBoxAscent - textMetrics.actualBoundingBoxDescent) / 2; + break; + case TextBaseline::Bottom: + y -= textMetrics.actualBoundingBoxDescent; + break; + case TextBaseline::Hanging: + case TextBaseline::Alphabetic: + case TextBaseline::Ideographic: + default: + break; + } + skCanvas->drawTextBlob(textBlob, x, y, fillPaint); + this->notifyCanvasUpdated(); + } - // TODO: support path - auto path = *currentPath; - auto strokePaint = getStrokePaint(); - auto shadowPaint = getShadowPaint(strokePaint); - if (shadowPaint != nullptr) + template + void CanvasRenderingContext2D::stroke() { - skCanvas->save(); - // TODO - skCanvas->drawPath(path, *shadowPaint); - skCanvas->restore(); - delete shadowPaint; + if (TR_UNLIKELY(skCanvas == nullptr)) + return; + + // TODO: support path + auto path = *currentPath; + auto strokePaint = getStrokePaint(); + auto shadowPaint = getShadowPaint(strokePaint); + if (shadowPaint != nullptr) + { + skCanvas->save(); + // TODO + skCanvas->drawPath(path, *shadowPaint); + skCanvas->restore(); + delete shadowPaint; + } + skCanvas->drawPath(path, strokePaint); + this->notifyCanvasUpdated(); } - skCanvas->drawPath(path, strokePaint); - this->notifyCanvasUpdated(); - } - template - void CanvasRenderingContext2D::strokeRect(float x, float y, float width, float height) - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + template + void CanvasRenderingContext2D::strokeRect(float x, float y, float width, float height) + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return; - auto strokePaint = getStrokePaint(); - // TODO: shadow painting - skCanvas->drawRect(SkRect::MakeXYWH(x, y, width, height), strokePaint); - this->notifyCanvasUpdated(); - } + auto strokePaint = getStrokePaint(); + // TODO: shadow painting + skCanvas->drawRect(SkRect::MakeXYWH(x, y, width, height), strokePaint); + this->notifyCanvasUpdated(); + } - template - void CanvasRenderingContext2D::clearRect(float x, float y, float width, float height) - { - skPaint->setStyle(SkPaint::kFill_Style); - skPaint->setBlendMode(SkBlendMode::kClear); - if (skCanvas != nullptr) + template + void CanvasRenderingContext2D::clearRect(float x, float y, float width, float height) { - skCanvas->drawRect(SkRect::MakeXYWH(x, y, width, height), *skPaint); - this->notifyCanvasUpdated(); + skPaint->setStyle(SkPaint::kFill_Style); + skPaint->setBlendMode(SkBlendMode::kClear); + if (skCanvas != nullptr) + { + skCanvas->drawRect(SkRect::MakeXYWH(x, y, width, height), *skPaint); + this->notifyCanvasUpdated(); + } + skPaint->setBlendMode(globalCompositeOperation); } - skPaint->setBlendMode(globalCompositeOperation); - } - template - void CanvasRenderingContext2D::rect(float x, float y, float width, float height) - { - if (currentPath == nullptr) - return; - currentPath->addRect(SkRect::MakeXYWH(x, y, width, height)); - } + template + void CanvasRenderingContext2D::rect(float x, float y, float width, float height) + { + if (currentPath == nullptr) + return; + currentPath->addRect(SkRect::MakeXYWH(x, y, width, height)); + } - template - void CanvasRenderingContext2D::ellipse(float x, - float y, - float radiusX, - float radiusY, - float rotation, - float startAngle, - float endAngle, - bool ccw) - { - ellipseToSkPath(currentPath, x, y, radiusX, radiusY, rotation, startAngle, endAngle, ccw); - } + template + void CanvasRenderingContext2D::ellipse(float x, + float y, + float radiusX, + float radiusY, + float rotation, + float startAngle, + float endAngle, + bool ccw) + { + ellipseToSkPath(currentPath, x, y, radiusX, radiusY, rotation, startAngle, endAngle, ccw); + } - template - const std::vector &CanvasRenderingContext2D::getLineDash() - { - return lineDash; - } + template + const std::vector &CanvasRenderingContext2D::getLineDash() + { + return lineDash; + } - template - void CanvasRenderingContext2D::setLineDash(const std::vector &segments) - { - auto count = segments.size(); - if (count % 2 == 1) + template + void CanvasRenderingContext2D::setLineDash(const std::vector &segments) { - lineDash.resize(count * 2); - for (size_t i = 0; i < count; i++) + auto count = segments.size(); + if (count % 2 == 1) + { + lineDash.resize(count * 2); + for (size_t i = 0; i < count; i++) + { + lineDash[i * 2] = segments[i]; + lineDash[i * 2 + 1] = segments[i]; + } + } + else { - lineDash[i * 2] = segments[i]; - lineDash[i * 2 + 1] = segments[i]; + lineDash.resize(count); + for (size_t i = 0; i < count; i++) + lineDash[i] = segments[i]; } } - else + + template + void CanvasRenderingContext2D::beginPath() { - lineDash.resize(count); - for (size_t i = 0; i < count; i++) - lineDash[i] = segments[i]; + if (currentPath != nullptr) + currentPath.reset(); + currentPath = std::make_shared(); } - } - template - void CanvasRenderingContext2D::beginPath() - { - if (currentPath != nullptr) - currentPath.reset(); - currentPath = std::make_shared(); - } + template + void CanvasRenderingContext2D::closePath() + { + closeSkPath(currentPath); + } - template - void CanvasRenderingContext2D::closePath() - { - closeSkPath(currentPath); - } + template + void CanvasRenderingContext2D::moveTo(float x, float y) + { + if (currentPath != nullptr) + currentPath->moveTo(x, y); + } - template - void CanvasRenderingContext2D::moveTo(float x, float y) - { - if (currentPath != nullptr) - currentPath->moveTo(x, y); - } + template + void CanvasRenderingContext2D::lineTo(float x, float y) + { + if (currentPath == nullptr) + return; + if (currentPath->isEmpty()) + currentPath->moveTo(x, y); + currentPath->lineTo(x, y); + } - template - void CanvasRenderingContext2D::lineTo(float x, float y) - { - if (currentPath == nullptr) - return; - if (currentPath->isEmpty()) - currentPath->moveTo(x, y); - currentPath->lineTo(x, y); - } + template + void CanvasRenderingContext2D::bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) + { + if (currentPath == nullptr) + return; + if (currentPath->isEmpty()) + currentPath->moveTo(cp1x, cp1y); + currentPath->cubicTo(cp1x, cp1y, cp2x, cp2y, x, y); + } - template - void CanvasRenderingContext2D::bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) - { - if (currentPath == nullptr) - return; - if (currentPath->isEmpty()) - currentPath->moveTo(cp1x, cp1y); - currentPath->cubicTo(cp1x, cp1y, cp2x, cp2y, x, y); - } + template + void CanvasRenderingContext2D::quadraticCurveTo(float cpx, float cpy, float x, float y) + { + if (currentPath == nullptr) + return; + if (currentPath->isEmpty()) + currentPath->moveTo(cpx, cpy); + currentPath->quadTo(cpx, cpy, x, y); + } - template - void CanvasRenderingContext2D::quadraticCurveTo(float cpx, float cpy, float x, float y) - { - if (currentPath == nullptr) - return; - if (currentPath->isEmpty()) - currentPath->moveTo(cpx, cpy); - currentPath->quadTo(cpx, cpy, x, y); - } + template + void CanvasRenderingContext2D::arc(float x, float y, float radius, float startAngle, float endAngle, bool ccw) + { + Path2D::Ellipse(currentPath, x, y, radius, radius, 0, startAngle, endAngle, ccw); + } - template - void CanvasRenderingContext2D::arc(float x, float y, float radius, float startAngle, float endAngle, bool ccw) - { - Path2D::Ellipse(currentPath, x, y, radius, radius, 0, startAngle, endAngle, ccw); - } + template + void CanvasRenderingContext2D::arcTo(float x1, float y1, float x2, float y2, float radius) + { + if (currentPath == nullptr) + return; + if (currentPath->isEmpty()) + currentPath->moveTo(x1, y1); + Path2D::ApplyArcToTangent(currentPath, x1, y1, x2, y2, radius); + } - template - void CanvasRenderingContext2D::arcTo(float x1, float y1, float x2, float y2, float radius) - { - if (currentPath == nullptr) - return; - if (currentPath->isEmpty()) - currentPath->moveTo(x1, y1); - Path2D::ApplyArcToTangent(currentPath, x1, y1, x2, y2, radius); - } + template + TextMetrics CanvasRenderingContext2D::measureText(const std::string &text) + { + TextMetrics textMetrics; + SkGlyphID ids[256]; + int glyphsCount = skFont->textToGlyphs(text.c_str(), text.size(), SkTextEncoding::kUTF8, ids, 256); + + SkScalar widths[glyphsCount]; + skFont->getWidths(ids, glyphsCount, widths); + + SkScalar totalWidth = 0; + for (int i = 0; i < glyphsCount; i++) + totalWidth += widths[i]; + textMetrics.width = totalWidth; + + SkFontMetrics metrics; + skFont->getMetrics(&metrics); + textMetrics.fontBoundingBoxAscent = metrics.fAscent; + textMetrics.fontBoundingBoxDescent = metrics.fDescent; + + SkRect bounds; + skFont->measureText(text.c_str(), text.size(), SkTextEncoding::kUTF8, &bounds); + textMetrics.actualBoundingBoxLeft = bounds.left(); + textMetrics.actualBoundingBoxRight = bounds.right(); + textMetrics.actualBoundingBoxAscent = -bounds.top(); + textMetrics.actualBoundingBoxDescent = bounds.bottom(); + + // Others + textMetrics.emHeightAscent = metrics.fCapHeight; + textMetrics.emHeightDescent = metrics.fXHeight; + textMetrics.alphabeticBaseline = metrics.fAscent; + textMetrics.hangingBaseline = metrics.fAscent - metrics.fCapHeight; + textMetrics.ideographicBaseline = metrics.fAscent - metrics.fXHeight; + return textMetrics; + } - template - TextMetrics CanvasRenderingContext2D::measureText(const std::string &text) - { - TextMetrics textMetrics; - SkGlyphID ids[256]; - int glyphsCount = skFont->textToGlyphs(text.c_str(), text.size(), SkTextEncoding::kUTF8, ids, 256); - - SkScalar widths[glyphsCount]; - skFont->getWidths(ids, glyphsCount, widths); - - SkScalar totalWidth = 0; - for (int i = 0; i < glyphsCount; i++) - totalWidth += widths[i]; - textMetrics.width = totalWidth; - - SkFontMetrics metrics; - skFont->getMetrics(&metrics); - textMetrics.fontBoundingBoxAscent = metrics.fAscent; - textMetrics.fontBoundingBoxDescent = metrics.fDescent; - - SkRect bounds; - skFont->measureText(text.c_str(), text.size(), SkTextEncoding::kUTF8, &bounds); - textMetrics.actualBoundingBoxLeft = bounds.left(); - textMetrics.actualBoundingBoxRight = bounds.right(); - textMetrics.actualBoundingBoxAscent = -bounds.top(); - textMetrics.actualBoundingBoxDescent = bounds.bottom(); - - // Others - textMetrics.emHeightAscent = metrics.fCapHeight; - textMetrics.emHeightDescent = metrics.fXHeight; - textMetrics.alphabeticBaseline = metrics.fAscent; - textMetrics.hangingBaseline = metrics.fAscent - metrics.fCapHeight; - textMetrics.ideographicBaseline = metrics.fAscent - metrics.fXHeight; - return textMetrics; - } + template + void CanvasRenderingContext2D::transform(float a, float b, float c, float d, float e, float f) + { + if (TR_LIKELY(skCanvas != nullptr)) + skCanvas->concat(SkMatrix::MakeAll(a, b, e, c, d, f, 0, 0, 1)); + } - template - void CanvasRenderingContext2D::transform(float a, float b, float c, float d, float e, float f) - { - if (TR_LIKELY(skCanvas != nullptr)) - skCanvas->concat(SkMatrix::MakeAll(a, b, e, c, d, f, 0, 0, 1)); - } + template + void CanvasRenderingContext2D::setTransform(float a, float b, float c, float d, float e, float f) + { + if (TR_LIKELY(skCanvas != nullptr)) + { + skCanvas->resetMatrix(); + skCanvas->concat(SkMatrix::MakeAll(a, b, e, c, d, f, 0, 0, 1)); + } + } - template - void CanvasRenderingContext2D::setTransform(float a, float b, float c, float d, float e, float f) - { - if (TR_LIKELY(skCanvas != nullptr)) + template + void CanvasRenderingContext2D::resetTransform() { - skCanvas->resetMatrix(); - skCanvas->concat(SkMatrix::MakeAll(a, b, e, c, d, f, 0, 0, 1)); + if (TR_LIKELY(skCanvas != nullptr)) + skCanvas->resetMatrix(); } - } - template - void CanvasRenderingContext2D::resetTransform() - { - if (TR_LIKELY(skCanvas != nullptr)) - skCanvas->resetMatrix(); - } + template + void CanvasRenderingContext2D::scale(float sx, float sy) + { + if (TR_LIKELY(skCanvas != nullptr)) + skCanvas->scale(sx, sy); + } - template - void CanvasRenderingContext2D::scale(float sx, float sy) - { - if (TR_LIKELY(skCanvas != nullptr)) - skCanvas->scale(sx, sy); - } + template + void CanvasRenderingContext2D::rotate(float angle) + { + if (TR_LIKELY(skCanvas != nullptr)) + skCanvas->rotate(angle); + } - template - void CanvasRenderingContext2D::rotate(float angle) - { - if (TR_LIKELY(skCanvas != nullptr)) - skCanvas->rotate(angle); - } + template + void CanvasRenderingContext2D::translate(float tx, float ty) + { + if (TR_LIKELY(skCanvas != nullptr)) + skCanvas->translate(tx, ty); + } - template - void CanvasRenderingContext2D::translate(float tx, float ty) - { - if (TR_LIKELY(skCanvas != nullptr)) - skCanvas->translate(tx, ty); - } + template + void CanvasRenderingContext2D::drawImage(std::shared_ptr image, SkRect dstRect, SkRect srcRect) + { + if (TR_UNLIKELY(skCanvas == nullptr) || image == nullptr) + return; - template - void CanvasRenderingContext2D::drawImage(std::shared_ptr image, SkRect dstRect, SkRect srcRect) - { - if (TR_UNLIKELY(skCanvas == nullptr) || image == nullptr) - return; - - SkPaint imagePaint = getFillPaint(); - imagePaint.setColor(SK_ColorWHITE); - sk_sp imageSource = image->makeSkImage(); - if (TR_UNLIKELY(imageSource == nullptr)) - { - std::cerr << "Failed to load image to draw" << std::endl; - return; - } - skCanvas->drawImageRect(imageSource, - srcRect, - dstRect, - SkSamplingOptions(), - &imagePaint, - SkCanvas::kFast_SrcRectConstraint); - this->notifyCanvasUpdated(); - } + SkPaint imagePaint = getFillPaint(); + imagePaint.setColor(SK_ColorWHITE); + sk_sp imageSource = image->makeSkImage(); + if (TR_UNLIKELY(imageSource == nullptr)) + { + std::cerr << "Failed to load image to draw" << std::endl; + return; + } + skCanvas->drawImageRect(imageSource, + srcRect, + dstRect, + SkSamplingOptions(), + &imagePaint, + SkCanvas::kFast_SrcRectConstraint); + this->notifyCanvasUpdated(); + } - template - void CanvasRenderingContext2D::drawImage(std::shared_ptr image, float dx, float dy) - { - SkRect dstRect = SkRect::MakeXYWH(dx, dy, image->width(), image->height()); - SkRect srcRect = SkRect::MakeXYWH(0, 0, image->width(), image->height()); - drawImage(image, dstRect, srcRect); - } + template + void CanvasRenderingContext2D::drawImage(std::shared_ptr image, float dx, float dy) + { + SkRect dstRect = SkRect::MakeXYWH(dx, dy, image->width(), image->height()); + SkRect srcRect = SkRect::MakeXYWH(0, 0, image->width(), image->height()); + drawImage(image, dstRect, srcRect); + } - template - void CanvasRenderingContext2D::drawImage(std::shared_ptr image, float dx, float dy, float dWidth, float dHeight) - { - SkRect dstRect = SkRect::MakeXYWH(dx, dy, dWidth, dHeight); - SkRect srcRect = SkRect::MakeXYWH(0, 0, image->width(), image->height()); - drawImage(image, dstRect, srcRect); - } + template + void CanvasRenderingContext2D::drawImage(std::shared_ptr image, float dx, float dy, float dWidth, float dHeight) + { + SkRect dstRect = SkRect::MakeXYWH(dx, dy, dWidth, dHeight); + SkRect srcRect = SkRect::MakeXYWH(0, 0, image->width(), image->height()); + drawImage(image, dstRect, srcRect); + } - template - void CanvasRenderingContext2D::drawImage(std::shared_ptr image, - float sx, - float sy, - float sWidth, - float sHeight, - float dx, - float dy, - float dWidth, - float dHeight) - { - SkRect dstRect = SkRect::MakeXYWH(dx, dy, dWidth, dHeight); - SkRect srcRect = SkRect::MakeXYWH(sx, sy, sWidth, sHeight); - drawImage(image, dstRect, srcRect); - } + template + void CanvasRenderingContext2D::drawImage(std::shared_ptr image, + float sx, + float sy, + float sWidth, + float sHeight, + float dx, + float dy, + float dWidth, + float dHeight) + { + SkRect dstRect = SkRect::MakeXYWH(dx, dy, dWidth, dHeight); + SkRect srcRect = SkRect::MakeXYWH(sx, sy, sWidth, sHeight); + drawImage(image, dstRect, srcRect); + } - inline bool isColorSpaceEqual(skcms_Matrix3x3 x, skcms_Matrix3x3 y) - { - for (int i = 0; i < 3; i++) + inline bool isColorSpaceEqual(skcms_Matrix3x3 x, skcms_Matrix3x3 y) { - for (int j = 0; j < 3; j++) + for (int i = 0; i < 3; i++) { - if (x.vals[i][j] != y.vals[i][j]) + for (int j = 0; j < 3; j++) { - return false; + if (x.vals[i][j] != y.vals[i][j]) + { + return false; + } } } + return true; } - return true; - } - - template - std::shared_ptr CanvasRenderingContext2D::createImageData(float width, float height, const std::string &colorSpace) - { - return std::make_shared(width, height, colorSpace); - } - template - std::shared_ptr CanvasRenderingContext2D::createImageData(std::shared_ptr otherImageData) - { - auto width = otherImageData->width(); - auto height = otherImageData->height(); - - skcms_Matrix3x3 gamut; - if (otherImageData->colorSpace()->toXYZD50(&gamut)) + template + std::shared_ptr CanvasRenderingContext2D::createImageData(float width, float height, const std::string &colorSpace) { - if (isColorSpaceEqual(gamut, SkNamedGamut::kDisplayP3)) - return createImageData(width, height, "display-p3"); - // TODO: support other color spaces? (e.g. AdobeRGB, ProPhotoRGB, etc.) + return std::make_shared(width, height, colorSpace); } - return createImageData(width, height); - } - template - std::shared_ptr CanvasRenderingContext2D::getImageData(float x, float y, float width, float height) - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return nullptr; + template + std::shared_ptr CanvasRenderingContext2D::createImageData(std::shared_ptr otherImageData) + { + auto width = otherImageData->width(); + auto height = otherImageData->height(); - SkImageInfo dstImageInfo = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); - uint8_t *dstPixels = new uint8_t[dstImageInfo.computeMinByteSize()]; - if (!skCanvas->readPixels(dstImageInfo, dstPixels, dstImageInfo.minRowBytes(), x, y)) - return nullptr; + skcms_Matrix3x3 gamut; + if (otherImageData->colorSpace()->toXYZD50(&gamut)) + { + if (isColorSpaceEqual(gamut, SkNamedGamut::kDisplayP3)) + return createImageData(width, height, "display-p3"); + // TODO: support other color spaces? (e.g. AdobeRGB, ProPhotoRGB, etc.) + } + return createImageData(width, height); + } - std::vector dataArray(dstPixels, dstPixels + dstImageInfo.computeMinByteSize()); - delete[] dstPixels; - return std::make_shared(dataArray, width, height); - } + template + std::shared_ptr CanvasRenderingContext2D::getImageData(float x, float y, float width, float height) + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return nullptr; - template - bool CanvasRenderingContext2D::putImageData(std::shared_ptr image, float dx, float dy) - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return false; + SkImageInfo dstImageInfo = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); + uint8_t *dstPixels = new uint8_t[dstImageInfo.computeMinByteSize()]; + if (!skCanvas->readPixels(dstImageInfo, dstPixels, dstImageInfo.minRowBytes(), x, y)) + return nullptr; - auto bitmap = image->makeSkBitmap(); - if (TR_UNLIKELY(bitmap == nullptr)) - return false; + std::vector dataArray(dstPixels, dstPixels + dstImageInfo.computeMinByteSize()); + delete[] dstPixels; + return std::make_shared(dataArray, width, height); + } - bool r = skCanvas->writePixels(*bitmap, dx, dy); - this->notifyCanvasUpdated(); - return r; - } + template + bool CanvasRenderingContext2D::putImageData(std::shared_ptr image, float dx, float dy) + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return false; - template - bool CanvasRenderingContext2D::putImageData(std::shared_ptr image, - float dx, - float dy, - float dirtyX, - float dirtyY, - float dirtyWidth, - float dirtyHeight) - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return false; + auto bitmap = image->makeSkBitmap(); + if (TR_UNLIKELY(bitmap == nullptr)) + return false; - if (dirtyWidth < 0) - { - dirtyX += dirtyWidth; - dirtyWidth = abs(dirtyWidth); - } - if (dirtyHeight < 0) - { - dirtyY += dirtyHeight; - dirtyHeight = abs(dirtyHeight); - } - if (dirtyX < 0) - { - dirtyWidth += dirtyX; - dirtyX = 0; + bool r = skCanvas->writePixels(*bitmap, dx, dy); + this->notifyCanvasUpdated(); + return r; } - if (dirtyY < 0) + + template + bool CanvasRenderingContext2D::putImageData(std::shared_ptr image, + float dx, + float dy, + float dirtyX, + float dirtyY, + float dirtyWidth, + float dirtyHeight) { - dirtyHeight += dirtyY; - dirtyY = 0; - } + if (TR_UNLIKELY(skCanvas == nullptr)) + return false; - if (dirtyWidth <= 0 || dirtyHeight <= 0) - return false; + if (dirtyWidth < 0) + { + dirtyX += dirtyWidth; + dirtyWidth = abs(dirtyWidth); + } + if (dirtyHeight < 0) + { + dirtyY += dirtyHeight; + dirtyHeight = abs(dirtyHeight); + } + if (dirtyX < 0) + { + dirtyWidth += dirtyX; + dirtyX = 0; + } + if (dirtyY < 0) + { + dirtyHeight += dirtyY; + dirtyY = 0; + } - SkRect srcRect = SkRect::MakeXYWH(dirtyX, dirtyY, dirtyWidth, dirtyHeight); - SkRect dstRect = SkRect::MakeXYWH(dx + dirtyX, dy + dirtyY, dirtyWidth, dirtyHeight); + if (dirtyWidth <= 0 || dirtyHeight <= 0) + return false; - SkMatrix invertedCtm; - if (currentTransform.invert(&invertedCtm)) - { - skCanvas->save(); - skCanvas->concat(invertedCtm); - skCanvas->drawImageRect(image->makeSkImage(), - srcRect, - dstRect, - SkSamplingOptions(), - nullptr, - SkCanvas::kFast_SrcRectConstraint); - skCanvas->restore(); - this->notifyCanvasUpdated(); - return true; - } - else - { - return false; + SkRect srcRect = SkRect::MakeXYWH(dirtyX, dirtyY, dirtyWidth, dirtyHeight); + SkRect dstRect = SkRect::MakeXYWH(dx + dirtyX, dy + dirtyY, dirtyWidth, dirtyHeight); + + SkMatrix invertedCtm; + if (currentTransform.invert(&invertedCtm)) + { + skCanvas->save(); + skCanvas->concat(invertedCtm); + skCanvas->drawImageRect(image->makeSkImage(), + srcRect, + dstRect, + SkSamplingOptions(), + nullptr, + SkCanvas::kFast_SrcRectConstraint); + skCanvas->restore(); + this->notifyCanvasUpdated(); + return true; + } + else + { + return false; + } } - } - template - void CanvasRenderingContext2D::save() - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + template + void CanvasRenderingContext2D::save() + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return; - // Save the current state of the canvas - // This includes the current transformation matrix, clip, and paint state. - // NOTE(yorkie): SkCanvas::save() does not save the paint state, so we need to handle it manually. - skCanvas->save(); - } + // Save the current state of the canvas + // This includes the current transformation matrix, clip, and paint state. + // NOTE(yorkie): SkCanvas::save() does not save the paint state, so we need to handle it manually. + skCanvas->save(); + } - template - void CanvasRenderingContext2D::restore() - { - if (TR_UNLIKELY(skCanvas == nullptr)) - return; + template + void CanvasRenderingContext2D::restore() + { + if (TR_UNLIKELY(skCanvas == nullptr)) + return; - // Restore the last saved state of the canvas - // This includes the current transformation matrix, clip, and paint state. - skCanvas->restore(); - } + // Restore the last saved state of the canvas + // This includes the current transformation matrix, clip, and paint state. + skCanvas->restore(); + } - template - void CanvasRenderingContext2D::reset(sk_sp skSurface) - { - if (skSurface == nullptr) + template + void CanvasRenderingContext2D::reset(sk_sp skSurface) { - skCanvas = nullptr; + if (skSurface == nullptr) + { + skCanvas = nullptr; + } + else + { + skCanvas = skSurface->getCanvas(); + skCanvas->drawColor(SK_ColorWHITE); + // TODO(yorkie): should redraw last content? + } } - else + + template + SkPaint CanvasRenderingContext2D::getFillPaint() { - skCanvas = skSurface->getCanvas(); - skCanvas->drawColor(SK_ColorWHITE); - // TODO(yorkie): should redraw last content? + SkPaint paint(*skPaint); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(fillStyle); + // TODO: pattern, gradient + return paint; } - } - template - SkPaint CanvasRenderingContext2D::getFillPaint() - { - SkPaint paint(*skPaint); - paint.setStyle(SkPaint::kFill_Style); - paint.setColor(fillStyle); - // TODO: pattern, gradient - return paint; - } + template + SkPaint CanvasRenderingContext2D::getStrokePaint() + { + SkPaint paint = *skPaint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(strokeStyle); + // TODO + paint.setStrokeWidth(strokeWidth); - template - SkPaint CanvasRenderingContext2D::getStrokePaint() - { - SkPaint paint = *skPaint; - paint.setStyle(SkPaint::kStroke_Style); - paint.setColor(strokeStyle); - // TODO - paint.setStrokeWidth(strokeWidth); + if (lineDash.size()) + { + auto effect = SkDashPathEffect::Make(lineDash.data(), lineDash.size(), 0); + paint.setPathEffect(effect); + } + return paint; + } - if (lineDash.size()) + template + SkPaint *CanvasRenderingContext2D::getShadowPaint(SkPaint &basePaint) { - auto effect = SkDashPathEffect::Make(lineDash.data(), lineDash.size(), 0); - paint.setPathEffect(effect); + return nullptr; } - return paint; - } - template - SkPaint *CanvasRenderingContext2D::getShadowPaint(SkPaint &basePaint) - { - return nullptr; - } + template + void CanvasRenderingContext2D::closeSkPath(std::shared_ptr path) + { + if (path == nullptr || path->isEmpty()) + return; + SkRect bounds = path->getBounds(); + if (bounds.width() || bounds.height()) + path->close(); + } - template - void CanvasRenderingContext2D::closeSkPath(std::shared_ptr path) - { - if (path == nullptr || path->isEmpty()) - return; - SkRect bounds = path->getBounds(); - if (bounds.width() || bounds.height()) - path->close(); - } + template + bool CanvasRenderingContext2D::ellipseToSkPath(std::shared_ptr path, + float x, + float y, + float radiusX, + float radiusY, + float rotation, + float startAngle, + float endAngle, + bool ccw) + { + if (radiusX < 0 || radiusY < 0) + return false; - template - bool CanvasRenderingContext2D::ellipseToSkPath(std::shared_ptr path, - float x, - float y, - float radiusX, - float radiusY, - float rotation, - float startAngle, - float endAngle, - bool ccw) - { - if (radiusX < 0 || radiusY < 0) - return false; + auto tao = 2 * M_PI; + auto newStartAngle = fmod(startAngle, tao); + if (newStartAngle < 0) + newStartAngle += tao; + auto delta = newStartAngle - startAngle; + startAngle = newStartAngle; + endAngle += delta; + + if (!ccw && (endAngle - startAngle) >= tao) + endAngle = startAngle + tao; + else if (ccw && (startAngle - endAngle) >= tao) + endAngle = startAngle - tao; + else if (!ccw && startAngle > endAngle) + endAngle = startAngle + (tao - fmod(startAngle - endAngle, tao)); + else if (ccw && startAngle < endAngle) + endAngle = startAngle - (tao - fmod(endAngle - startAngle, tao)); - auto tao = 2 * M_PI; - auto newStartAngle = fmod(startAngle, tao); - if (newStartAngle < 0) - newStartAngle += tao; - auto delta = newStartAngle - startAngle; - startAngle = newStartAngle; - endAngle += delta; - - if (!ccw && (endAngle - startAngle) >= tao) - endAngle = startAngle + tao; - else if (ccw && (startAngle - endAngle) >= tao) - endAngle = startAngle - tao; - else if (!ccw && startAngle > endAngle) - endAngle = startAngle + (tao - fmod(startAngle - endAngle, tao)); - else if (ccw && startAngle < endAngle) - endAngle = startAngle - (tao - fmod(endAngle - startAngle, tao)); - - // TODO - return true; + // TODO + return true; + } } -} +} // namespace endor diff --git a/src/client/canvas/rendering_context2d.hpp b/src/client/canvas/rendering_context2d.hpp index 6790c08de..5a8813bac 100644 --- a/src/client/canvas/rendering_context2d.hpp +++ b/src/client/canvas/rendering_context2d.hpp @@ -20,164 +20,167 @@ #include "./image_data.hpp" #include "./text_metrics.hpp" -namespace canvas +namespace endor { - enum class TextAlign + namespace canvas { - Start, - End, - Left, - Right, - Center - }; - - enum class TextBaseline - { - Top, - Hanging, - Middle, - Alphabetic, - Ideographic, - Bottom - }; - - template - class CanvasRenderingContext2D : public RenderingContextBase - { - using RenderingContextBase::RenderingContextBase; - - public: - CanvasRenderingContext2D(std::shared_ptr canvasRef); - - public: - bool setFillStyle(const std::string &style); - bool setStrokeStyle(const std::string &style); - std::string getFont(); - bool setFont(const std::string &fontStr); - float getGlobalAlpha(); - bool setGlobalAlpha(float alpha); - std::string getGlobalCompositeOperation(); - bool setGlobalCompositeOperation(const std::string &str); - std::string getTextAlign(); - bool setTextAlign(const std::string &str); - std::string getTextBaseline(); - bool setTextBaseline(const std::string &str); - float getLineDashOffset(); - float setLineDashOffset(float offset); - float getLineWidth(); - bool setLineWidth(float width); - std::string getLineCap(); - bool setLineCap(const std::string &str); - std::string getLineJoin(); - bool setLineJoin(const std::string &str); - - public: - void fill(); - void fillRect(float x, float y, float width, float height); - void fillText(const std::string &text, float x, float y); - void stroke(); - void strokeRect(float x, float y, float width, float height); - void clearRect(float x, float y, float width, float height); - - void rect(float x, float y, float width, float height); - void ellipse(float x, - float y, - float radiusX, - float radiusY, - float rotation, - float startAngle, - float endAngle, - bool ccw = false); - - const std::vector &getLineDash(); - void setLineDash(const std::vector &segments); - void beginPath(); - void closePath(); - void moveTo(float x, float y); - void lineTo(float x, float y); - void bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y); - void quadraticCurveTo(float cpx, float cpy, float x, float y); - void arc(float x, float y, float radius, float startAngle, float endAngle, bool ccw); - void arcTo(float x1, float y1, float x2, float y2, float radius); - TextMetrics measureText(const std::string &text); - void transform(float a, float b, float c, float d, float e, float f); - void setTransform(float a, float b, float c, float d, float e, float f); - void resetTransform(); - void scale(float sx, float sy); - void rotate(float angle); - void translate(float tx, float ty); - void drawImage(std::shared_ptr image, SkRect dstRect, SkRect srcRect); - void drawImage(std::shared_ptr image, float dx, float dy); - void drawImage(std::shared_ptr image, float dx, float dy, float dWidth, float dHeight); - void drawImage(std::shared_ptr image, - float sx, - float sy, - float sWidth, - float sHeight, - float dx, - float dy, - float dWidth, - float dHeight); - std::shared_ptr createImageData(float width, float height, const std::string &colorSpace = "srgb"); - std::shared_ptr createImageData(std::shared_ptr otherImageData); - std::shared_ptr getImageData(float x, float y, float width, float height); - bool putImageData(std::shared_ptr image, float dx, float dy); - bool putImageData(std::shared_ptr image, - float dx, - float dy, - float dirtyX, - float dirtyY, - float dirtyWidth, - float dirtyHeight); - void save(); - void restore(); - - // This resets the Skia surface and reinitializes the context. - void reset(sk_sp); - - private: - SkPaint getFillPaint(); - SkPaint getStrokePaint(); - SkPaint *getShadowPaint(SkPaint &basePaint); - void closeSkPath(std::shared_ptr path); - bool ellipseToSkPath(std::shared_ptr path, - float x, - float y, - float radiusX, - float radiusY, - float rotation, - float startAngle, - float endAngle, - bool ccw); - - private: - SkCanvas *skCanvas; - std::shared_ptr skPaint; - std::shared_ptr skFont; - - private: - std::shared_ptr currentPath = nullptr; - SkMatrix currentTransform = SkMatrix::I(); - - private: // text & font - std::string fontStr = "14px monospace"; - TextAlign textAlign = TextAlign::Start; - TextBaseline textBaseline = TextBaseline::Alphabetic; - - public: // style - SkColor fillStyle = SK_ColorBLACK; - SkColor strokeStyle = SK_ColorBLACK; - - private: - float shadowBlur = 0.0f; - SkColor shadowColor = SK_ColorTRANSPARENT; - glm::vec2 shadowOffset = glm::vec2(0.0f); - float globalAlpha = 1.0f; - float strokeWidth = 1.0f; - SkBlendMode globalCompositeOperation = SkBlendMode::kSrcOver; - std::vector lineDash; - float lineDashOffset = 0.0f; - }; -} + enum class TextAlign + { + Start, + End, + Left, + Right, + Center + }; + + enum class TextBaseline + { + Top, + Hanging, + Middle, + Alphabetic, + Ideographic, + Bottom + }; + + template + class CanvasRenderingContext2D : public RenderingContextBase + { + using RenderingContextBase::RenderingContextBase; + + public: + CanvasRenderingContext2D(std::shared_ptr canvasRef); + + public: + bool setFillStyle(const std::string &style); + bool setStrokeStyle(const std::string &style); + std::string getFont(); + bool setFont(const std::string &fontStr); + float getGlobalAlpha(); + bool setGlobalAlpha(float alpha); + std::string getGlobalCompositeOperation(); + bool setGlobalCompositeOperation(const std::string &str); + std::string getTextAlign(); + bool setTextAlign(const std::string &str); + std::string getTextBaseline(); + bool setTextBaseline(const std::string &str); + float getLineDashOffset(); + float setLineDashOffset(float offset); + float getLineWidth(); + bool setLineWidth(float width); + std::string getLineCap(); + bool setLineCap(const std::string &str); + std::string getLineJoin(); + bool setLineJoin(const std::string &str); + + public: + void fill(); + void fillRect(float x, float y, float width, float height); + void fillText(const std::string &text, float x, float y); + void stroke(); + void strokeRect(float x, float y, float width, float height); + void clearRect(float x, float y, float width, float height); + + void rect(float x, float y, float width, float height); + void ellipse(float x, + float y, + float radiusX, + float radiusY, + float rotation, + float startAngle, + float endAngle, + bool ccw = false); + + const std::vector &getLineDash(); + void setLineDash(const std::vector &segments); + void beginPath(); + void closePath(); + void moveTo(float x, float y); + void lineTo(float x, float y); + void bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y); + void quadraticCurveTo(float cpx, float cpy, float x, float y); + void arc(float x, float y, float radius, float startAngle, float endAngle, bool ccw); + void arcTo(float x1, float y1, float x2, float y2, float radius); + TextMetrics measureText(const std::string &text); + void transform(float a, float b, float c, float d, float e, float f); + void setTransform(float a, float b, float c, float d, float e, float f); + void resetTransform(); + void scale(float sx, float sy); + void rotate(float angle); + void translate(float tx, float ty); + void drawImage(std::shared_ptr image, SkRect dstRect, SkRect srcRect); + void drawImage(std::shared_ptr image, float dx, float dy); + void drawImage(std::shared_ptr image, float dx, float dy, float dWidth, float dHeight); + void drawImage(std::shared_ptr image, + float sx, + float sy, + float sWidth, + float sHeight, + float dx, + float dy, + float dWidth, + float dHeight); + std::shared_ptr createImageData(float width, float height, const std::string &colorSpace = "srgb"); + std::shared_ptr createImageData(std::shared_ptr otherImageData); + std::shared_ptr getImageData(float x, float y, float width, float height); + bool putImageData(std::shared_ptr image, float dx, float dy); + bool putImageData(std::shared_ptr image, + float dx, + float dy, + float dirtyX, + float dirtyY, + float dirtyWidth, + float dirtyHeight); + void save(); + void restore(); + + // This resets the Skia surface and reinitializes the context. + void reset(sk_sp); + + private: + SkPaint getFillPaint(); + SkPaint getStrokePaint(); + SkPaint *getShadowPaint(SkPaint &basePaint); + void closeSkPath(std::shared_ptr path); + bool ellipseToSkPath(std::shared_ptr path, + float x, + float y, + float radiusX, + float radiusY, + float rotation, + float startAngle, + float endAngle, + bool ccw); + + private: + SkCanvas *skCanvas; + std::shared_ptr skPaint; + std::shared_ptr skFont; + + private: + std::shared_ptr currentPath = nullptr; + SkMatrix currentTransform = SkMatrix::I(); + + private: // text & font + std::string fontStr = "14px monospace"; + TextAlign textAlign = TextAlign::Start; + TextBaseline textBaseline = TextBaseline::Alphabetic; + + public: // style + SkColor fillStyle = SK_ColorBLACK; + SkColor strokeStyle = SK_ColorBLACK; + + private: + float shadowBlur = 0.0f; + SkColor shadowColor = SK_ColorTRANSPARENT; + glm::vec2 shadowOffset = glm::vec2(0.0f); + float globalAlpha = 1.0f; + float strokeWidth = 1.0f; + SkBlendMode globalCompositeOperation = SkBlendMode::kSrcOver; + std::vector lineDash; + float lineDashOffset = 0.0f; + }; + } +} // namespace endor #include "./rendering_context2d-inl.hpp" diff --git a/src/client/canvas/text_metrics.hpp b/src/client/canvas/text_metrics.hpp index 54f399a8e..e6c6d07aa 100644 --- a/src/client/canvas/text_metrics.hpp +++ b/src/client/canvas/text_metrics.hpp @@ -2,25 +2,28 @@ #include -namespace canvas +namespace endor { - class TextMetrics final : public scripting_base::JSObjectHolder + namespace canvas { - public: - TextMetrics() = default; + class TextMetrics final : public scripting_base::JSObjectHolder + { + public: + TextMetrics() = default; - public: - float width; - float actualBoundingBoxLeft; - float actualBoundingBoxRight; - float fontBoundingBoxAscent; - float fontBoundingBoxDescent; - float actualBoundingBoxAscent; - float actualBoundingBoxDescent; - float emHeightAscent; - float emHeightDescent; - float hangingBaseline; - float alphabeticBaseline; - float ideographicBaseline; - }; -} + public: + float width; + float actualBoundingBoxLeft; + float actualBoundingBoxRight; + float fontBoundingBoxAscent; + float fontBoundingBoxDescent; + float actualBoundingBoxAscent; + float actualBoundingBoxDescent; + float emHeightAscent; + float emHeightDescent; + float hangingBaseline; + float alphabeticBaseline; + float ideographicBaseline; + }; + } +} // namespace endor diff --git a/src/client/classes.hpp b/src/client/classes.hpp index e3c86f16b..91bd3a358 100644 --- a/src/client/classes.hpp +++ b/src/client/classes.hpp @@ -6,69 +6,73 @@ * @namespace browser * The `browser` namespace contains classes related to the browser window, */ -namespace browser + +namespace endor { - class Location; // Represents the location (URL) of a browser window - class Navigator; // Represents the navigator object of a browser window - class Window; // Represents a browser window -} + namespace browser + { + class Location; // Represents the location (URL) of a browser window + class Navigator; // Represents the navigator object of a browser window + class Window; // Represents a browser window + } -/** + /** * @namespace dom * The `dom` namespace contains classes related to the Document Object Model (DOM), * such as `Document` and `Element`. */ -namespace dom -{ - class BrowsingContext; - class RuntimeContext; + namespace dom + { + class BrowsingContext; + class RuntimeContext; - class Document; // Represents an HTML or XML document - class Element; // Represents an element in the DOM tree - class SceneObject; // Represents a scene object in the DOM tree - class Node; // Represents a node in the DOM tree - class Text; // Represents a text node in the DOM tree -} + class Document; // Represents an HTML or XML document + class Element; // Represents an element in the DOM tree + class SceneObject; // Represents a scene object in the DOM tree + class Node; // Represents a node in the DOM tree + class Text; // Represents a text node in the DOM tree + } -/** + /** * @namespace builtin_scene * The `builtin_scene` namespace contains classes related to scene management, * such as `Scene` and `Hierarchy`. */ -namespace builtin_scene -{ - class Scene; // Represents a 3D scene - class Hierarchy; // Represents the hierarchy of objects in a scene -} + namespace builtin_scene + { + class Scene; // Represents a 3D scene + class Hierarchy; // Represents the hierarchy of objects in a scene + } -/** + /** * @namespace client_graphics * The `client_graphics` namespace contains classes related to graphics rendering, * such as `WebGLContext` and `WebGL2Context`. */ -namespace client_graphics -{ - class WebGLContext; // Represents a WebGL 1.0 rendering context - class WebGL2Context; // Represents a WebGL 2.0 rendering context -} + namespace client_graphics + { + class WebGLContext; // Represents a WebGL 1.0 rendering context + class WebGL2Context; // Represents a WebGL 2.0 rendering context + } -/** + /** * @namespace client_xr * The `client_xr` namespace contains classes related to extended reality (XR) devices, * such as `XRDeviceClient`. */ -namespace client_xr -{ - class XRDeviceClient; // Represents a client for interacting with XR devices -} + namespace client_xr + { + class XRDeviceClient; // Represents a client for interacting with XR devices + } -/** + /** * @namespace media_client * The `media_client` namespace contains classes related to media playback, * such as `MediaPlayer` and `AudioPlayer`. */ -namespace media_client -{ - class MediaPlayer; // Represents a media player for video and audio playback - class AudioPlayer; // Represents an audio player for audio playback -} + namespace media_client + { + class MediaPlayer; // Represents a media player for video and audio playback + class AudioPlayer; // Represents an audio player for audio playback + } +} // namespace endor diff --git a/src/client/cssom/box_bounding.hpp b/src/client/cssom/box_bounding.hpp index 4099f1898..36d0768b8 100644 --- a/src/client/cssom/box_bounding.hpp +++ b/src/client/cssom/box_bounding.hpp @@ -5,44 +5,47 @@ #include #include -namespace client_cssom +namespace endor { - struct DefaultBoundingBox + namespace client_cssom { - std::optional width = std::nullopt; - std::optional height = std::nullopt; - std::optional depth = std::nullopt; - }; + struct DefaultBoundingBox + { + std::optional width = std::nullopt; + std::optional height = std::nullopt; + std::optional depth = std::nullopt; + }; - inline std::ostream &operator<<(std::ostream &os, const DefaultBoundingBox &box) - { - os << "("; - os << (box.width.has_value() ? std::to_string(box.width.value()) : "null") << ", "; - os << (box.height.has_value() ? std::to_string(box.height.value()) : "null") << ", "; - os << (box.depth.has_value() ? std::to_string(box.depth.value()) : "null"); - return os << ")"; - } + inline std::ostream &operator<<(std::ostream &os, const DefaultBoundingBox &box) + { + os << "("; + os << (box.width.has_value() ? std::to_string(box.width.value()) : "null") << ", "; + os << (box.height.has_value() ? std::to_string(box.height.value()) : "null") << ", "; + os << (box.depth.has_value() ? std::to_string(box.depth.value()) : "null"); + return os << ")"; + } - class BoxBounding - { - public: - virtual ~BoxBounding() = default; + class BoxBounding + { + public: + virtual ~BoxBounding() = default; - public: - /** + public: + /** * The `getLayoutRects()` method returns a collection of `DOMRect` objects that indicate the bounding rectangles * for each CSS border box in a layout. */ - virtual std::vector getLayoutRects() const - { - return std::vector(); - } + virtual std::vector getLayoutRects() const + { + return std::vector(); + } - protected: - // The default bouding box, which will be updated to the layout system. - // - // Such as the `HTMLImageElement` will update the bounding box after the image is loaded or the style has changed, - // it will compute a matched bounding box for the later layouting. - DefaultBoundingBox defaultBoundingBox_{std::nullopt, std::nullopt, std::nullopt}; - }; -} + protected: + // The default bouding box, which will be updated to the layout system. + // + // Such as the `HTMLImageElement` will update the bounding box after the image is loaded or the style has changed, + // it will compute a matched bounding box for the later layouting. + DefaultBoundingBox defaultBoundingBox_{std::nullopt, std::nullopt, std::nullopt}; + }; + } +} // namespace endor diff --git a/src/client/cssom/box_offset.hpp b/src/client/cssom/box_offset.hpp index d08a01b90..10ebbf8a1 100644 --- a/src/client/cssom/box_offset.hpp +++ b/src/client/cssom/box_offset.hpp @@ -1,16 +1,19 @@ #pragma once -namespace client_cssom +namespace endor { - class BoxOffset + namespace client_cssom { - public: - virtual ~BoxOffset() = default; + class BoxOffset + { + public: + virtual ~BoxOffset() = default; - public: - virtual float offsetWidth() const = 0; - virtual float &offsetWidth() = 0; - virtual float offsetHeight() const = 0; - virtual float &offsetHeight() = 0; - }; -} + public: + virtual float offsetWidth() const = 0; + virtual float &offsetWidth() = 0; + virtual float offsetHeight() const = 0; + virtual float &offsetHeight() = 0; + }; + } +} // namespace endor diff --git a/src/client/cssom/computed_style.cpp b/src/client/cssom/computed_style.cpp index 144034001..d084cfa5e 100644 --- a/src/client/cssom/computed_style.cpp +++ b/src/client/cssom/computed_style.cpp @@ -22,176 +22,238 @@ #include "./parsers/css_variable_parser.hpp" #include "./variable_reference_tracker.hpp" -namespace client_cssom +namespace endor { - using namespace std; - - ComputedStyle::Difference ComputedStyle::ComputeDifference(const ComputedStyle &old_style, - const ComputedStyle &new_style) + namespace client_cssom { - // Fast check for empty styles. - if (old_style.empty() && new_style.empty()) - return kEqual; - - // Fast check for sizes, namely if the sizes of the styles are different. - if (old_style.size() != new_style.size()) - return kNonInherited; + using namespace std; - // Compare each properties in the styles. - return old_style == new_style ? kEqual : kNonInherited; - } + ComputedStyle::Difference ComputedStyle::ComputeDifference(const ComputedStyle &old_style, + const ComputedStyle &new_style) + { + // Fast check for empty styles. + if (old_style.empty() && new_style.empty()) + return kEqual; - ComputedStyle ComputedStyle::Make(const CSSStyleDeclaration &style, shared_ptr target_node) - { - return ComputedStyle(style, values::computed::Context::From(target_node)); - } + // Fast check for sizes, namely if the sizes of the styles are different. + if (old_style.size() != new_style.size()) + return kNonInherited; - ComputedStyle::ComputedStyle(const CSSStyleDeclaration &style, optional context) - : map() - { - // Set up variable change callback for recomputation - variable_tracker_.setVariableChangeCallback( - [this](const std::string &property_name, const std::string &new_value, const values::computed::Context &context) - { - auto property_it = find(property_name); - if (property_it != end()) - { - values::computed::Context mutable_context = context; - computeProperty(property_name, property_it->second, mutable_context); - } - }); + // Compare each properties in the styles. + return old_style == new_style ? kEqual : kNonInherited; + } - update(style, context); - } + ComputedStyle ComputedStyle::Make(const CSSStyleDeclaration &style, shared_ptr target_node) + { + return ComputedStyle(style, values::computed::Context::From(target_node)); + } - ComputedStyle::operator crates::layout2::LayoutStyle() const - { - using namespace crates::layout2; - LayoutStyle layoutStyle; - - layoutStyle.setDisplay(display_.toLayoutValue()); - layoutStyle.setBoxSizing(box_sizing_.toLayoutValue()); - layoutStyle.setOverflowX(overflow_x_.toLayoutValue()); - layoutStyle.setOverflowY(overflow_y_.toLayoutValue()); - - // Position - layoutStyle.setPosition(position_type_.toLayoutValue()); - layoutStyle.setTop(inset_.top().toLayoutValue()); - layoutStyle.setRight(inset_.right().toLayoutValue()); - layoutStyle.setBottom(inset_.bottom().toLayoutValue()); - layoutStyle.setLeft(inset_.left().toLayoutValue()); - - // Sizes - layoutStyle.setWidth(width_.toLayoutValue()); - layoutStyle.setHeight(height_.toLayoutValue()); - layoutStyle.setMinWidth(min_width_.toLayoutValue()); - layoutStyle.setMinHeight(min_height_.toLayoutValue()); - layoutStyle.setMaxWidth(max_width_.toLayoutValue()); - layoutStyle.setMaxHeight(max_height_.toLayoutValue()); - - // Margin - layoutStyle.setMarginTop(margin_.top().toLayoutValue()); - layoutStyle.setMarginRight(margin_.right().toLayoutValue()); - layoutStyle.setMarginBottom(margin_.bottom().toLayoutValue()); - layoutStyle.setMarginLeft(margin_.left().toLayoutValue()); - - // Padding - layoutStyle.setPaddingTop(padding_.top().toLayoutValue()); - layoutStyle.setPaddingRight(padding_.right().toLayoutValue()); - layoutStyle.setPaddingBottom(padding_.bottom().toLayoutValue()); - layoutStyle.setPaddingLeft(padding_.left().toLayoutValue()); - - // Border - layoutStyle.setBorderTop(border_width_.top().toLayoutValue()); - layoutStyle.setBorderRight(border_width_.right().toLayoutValue()); - layoutStyle.setBorderBottom(border_width_.bottom().toLayoutValue()); - layoutStyle.setBorderLeft(border_width_.left().toLayoutValue()); - - // Flexbox - layoutStyle.setAlignItems(align_items_.toLayoutValue()); - layoutStyle.setAlignSelf(align_self_.toLayoutValue()); - layoutStyle.setAlignContent(align_content_.toLayoutValue()); - layoutStyle.setJustifyContent(justify_content_.toLayoutValue()); - layoutStyle.setJustifySelf(justify_self_.toLayoutValue()); - layoutStyle.setJustifyItems(justify_items_.toLayoutValue()); - layoutStyle.setRowGap(row_gap_.toLayoutValue()); - layoutStyle.setColumnGap(column_gap_.toLayoutValue()); - layoutStyle.setFlexDirection(flex_direction_.toLayoutValue()); - layoutStyle.setFlexWrap(flex_wrap_.toLayoutValue()); - layoutStyle.setFlexGrow(flex_grow_.value); - layoutStyle.setFlexShrink(flex_shrink_.value); - - // Grid + ComputedStyle::ComputedStyle(const CSSStyleDeclaration &style, optional context) + : map() + { + // Set up variable change callback for recomputation + variable_tracker_.setVariableChangeCallback( + [this](const std::string &property_name, const std::string &new_value, const values::computed::Context &context) + { + auto property_it = find(property_name); + if (property_it != end()) + { + values::computed::Context mutable_context = context; + computeProperty(property_name, property_it->second, mutable_context); + } + }); + + update(style, context); + } + + ComputedStyle::operator crates::layout2::LayoutStyle() const + { + using namespace crates::layout2; + LayoutStyle layoutStyle; + + layoutStyle.setDisplay(display_.toLayoutValue()); + layoutStyle.setBoxSizing(box_sizing_.toLayoutValue()); + layoutStyle.setOverflowX(overflow_x_.toLayoutValue()); + layoutStyle.setOverflowY(overflow_y_.toLayoutValue()); + + // Position + layoutStyle.setPosition(position_type_.toLayoutValue()); + layoutStyle.setTop(inset_.top().toLayoutValue()); + layoutStyle.setRight(inset_.right().toLayoutValue()); + layoutStyle.setBottom(inset_.bottom().toLayoutValue()); + layoutStyle.setLeft(inset_.left().toLayoutValue()); + + // Sizes + layoutStyle.setWidth(width_.toLayoutValue()); + layoutStyle.setHeight(height_.toLayoutValue()); + layoutStyle.setMinWidth(min_width_.toLayoutValue()); + layoutStyle.setMinHeight(min_height_.toLayoutValue()); + layoutStyle.setMaxWidth(max_width_.toLayoutValue()); + layoutStyle.setMaxHeight(max_height_.toLayoutValue()); + + // Margin + layoutStyle.setMarginTop(margin_.top().toLayoutValue()); + layoutStyle.setMarginRight(margin_.right().toLayoutValue()); + layoutStyle.setMarginBottom(margin_.bottom().toLayoutValue()); + layoutStyle.setMarginLeft(margin_.left().toLayoutValue()); + + // Padding + layoutStyle.setPaddingTop(padding_.top().toLayoutValue()); + layoutStyle.setPaddingRight(padding_.right().toLayoutValue()); + layoutStyle.setPaddingBottom(padding_.bottom().toLayoutValue()); + layoutStyle.setPaddingLeft(padding_.left().toLayoutValue()); + + // Border + layoutStyle.setBorderTop(border_width_.top().toLayoutValue()); + layoutStyle.setBorderRight(border_width_.right().toLayoutValue()); + layoutStyle.setBorderBottom(border_width_.bottom().toLayoutValue()); + layoutStyle.setBorderLeft(border_width_.left().toLayoutValue()); + + // Flexbox + layoutStyle.setAlignItems(align_items_.toLayoutValue()); + layoutStyle.setAlignSelf(align_self_.toLayoutValue()); + layoutStyle.setAlignContent(align_content_.toLayoutValue()); + layoutStyle.setJustifyContent(justify_content_.toLayoutValue()); + layoutStyle.setJustifySelf(justify_self_.toLayoutValue()); + layoutStyle.setJustifyItems(justify_items_.toLayoutValue()); + layoutStyle.setRowGap(row_gap_.toLayoutValue()); + layoutStyle.setColumnGap(column_gap_.toLayoutValue()); + layoutStyle.setFlexDirection(flex_direction_.toLayoutValue()); + layoutStyle.setFlexWrap(flex_wrap_.toLayoutValue()); + layoutStyle.setFlexGrow(flex_grow_.value); + layoutStyle.setFlexShrink(flex_shrink_.value); + + // Grid #define LAYOUT_USE_PROPERTY_STRING(PROP, NAME) \ if (hasProperty(PROP)) \ layoutStyle.set##NAME(getPropertyValue(PROP)); - LAYOUT_USE_PROPERTY_STRING("grid-template-rows", GridTemplateRows) - LAYOUT_USE_PROPERTY_STRING("grid-template-columns", GridTemplateColumns) - LAYOUT_USE_PROPERTY_STRING("grid-auto-rows", GridAutoRows) - LAYOUT_USE_PROPERTY_STRING("grid-auto-columns", GridAutoColumns) - LAYOUT_USE_PROPERTY_STRING("grid-auto-flow", GridAutoFlow) - LAYOUT_USE_PROPERTY_STRING("grid-row-start", GridRowStart) - LAYOUT_USE_PROPERTY_STRING("grid-row-end", GridRowEnd) - LAYOUT_USE_PROPERTY_STRING("grid-column-start", GridColumnStart) - LAYOUT_USE_PROPERTY_STRING("grid-column-end", GridColumnEnd) + LAYOUT_USE_PROPERTY_STRING("grid-template-rows", GridTemplateRows) + LAYOUT_USE_PROPERTY_STRING("grid-template-columns", GridTemplateColumns) + LAYOUT_USE_PROPERTY_STRING("grid-auto-rows", GridAutoRows) + LAYOUT_USE_PROPERTY_STRING("grid-auto-columns", GridAutoColumns) + LAYOUT_USE_PROPERTY_STRING("grid-auto-flow", GridAutoFlow) + LAYOUT_USE_PROPERTY_STRING("grid-row-start", GridRowStart) + LAYOUT_USE_PROPERTY_STRING("grid-row-end", GridRowEnd) + LAYOUT_USE_PROPERTY_STRING("grid-column-start", GridColumnStart) + LAYOUT_USE_PROPERTY_STRING("grid-column-end", GridColumnEnd) #undef LAYOUT_USE_PROPERTY_STRING - // Text properties - layoutStyle.setTextAlign(text_align_.toLayoutValue()); - - return layoutStyle; - } + // Text properties + layoutStyle.setTextAlign(text_align_.toLayoutValue()); - void ComputedStyle::resetProperties(optional other, values::computed::Context &context) - { - clear(); + return layoutStyle; + } - if (other.has_value()) + void ComputedStyle::resetProperties(optional other, values::computed::Context &context) { - // First pass: Set all custom properties - unordered_map cssProperties; - for (const auto &[name, value] : other.value()) + clear(); + + if (other.has_value()) { - auto it = find(name); - if (it != end()) + // First pass: Set all custom properties + unordered_map cssProperties; + for (const auto &[name, value] : other.value()) { - if (it->second == value) - continue; // No need to update if the value is the same. - it->second = value; // Update the existing property. - } - else - { - setPropertyInternal(name, value); + auto it = find(name); + if (it != end()) + { + if (it->second == value) + continue; // No need to update if the value is the same. + it->second = value; // Update the existing property. + } + else + { + setPropertyInternal(name, value); + } + + if (name.length() >= 2 && name.substr(0, 2) == "--") + { + setCustomProperty(name, value); + } + else + { + cssProperties[name] = value; + } } - if (name.length() >= 2 && name.substr(0, 2) == "--") - { - setCustomProperty(name, value); - } - else + // Second pass: Process regular properties + for (const auto &[name, value] : cssProperties) + computeProperty(name, value, context); + } + } + + size_t ComputedStyle::inheritProperties(const ComputedStyle &other, values::computed::Context &context) + { + size_t inherited = 0; + + // First pass: Inherit custom properties + unordered_map inheritedProperties; + for (const auto &[propertyName, value] : other) + { + if (IsInheritedProperty(propertyName)) { - cssProperties[name] = value; + auto it = find(propertyName); + if (it != end()) + { + if (it->second == value) + continue; // No need to update if the value is the same. + it->second = value; // Update the existing property. + } + else + { + setPropertyInternal(propertyName, value); + } + inherited += 1; + + if (propertyName.length() >= 2 && propertyName.substr(0, 2) == "--") + { + setCustomProperty(propertyName, value); + } + else + { + inheritedProperties[propertyName] = value; + } } } - // Second pass: Process regular properties - for (const auto &[name, value] : cssProperties) + // Second pass: Inherit regular properties + for (const auto &[name, value] : inheritedProperties) computeProperty(name, value, context); + return inherited; } - } - size_t ComputedStyle::inheritProperties(const ComputedStyle &other, values::computed::Context &context) - { - size_t inherited = 0; + bool ComputedStyle::update(values::computed::Context &context) + { + resetProperties(context.resetStyle(), context); + auto inherited_style = context.inheritedStyle(); + if (inherited_style.has_value()) + { + inheritProperties(inherited_style.value(), context); + } + + // Compute shorthand properties such as `margin`, `padding`, `border`, etc. + computeShorthandProperties(context); + updateBaseComputedStyle(); + return true; + } - // First pass: Inherit custom properties - unordered_map inheritedProperties; - for (const auto &[propertyName, value] : other) + bool ComputedStyle::update(const CSSStyleDeclaration &from_style, optional context) { - if (IsInheritedProperty(propertyName)) + if (from_style.length() == 0) + return false; + + // Two-pass approach: first process custom properties, then regular properties + // This ensures custom properties are available when resolving variables in regular properties + + // First pass: Process all custom properties (CSS variables) + unordered_map css_properties; + for (int index = 0; index < from_style.length(); index++) { - auto it = find(propertyName); + string name = from_style.item(index); + string value = from_style.getPropertyValue(name); + + auto it = find(name); if (it != end()) { if (it->second == value) @@ -200,496 +262,437 @@ namespace client_cssom } else { - setPropertyInternal(propertyName, value); + setPropertyInternal(name, value); } - inherited += 1; - if (propertyName.length() >= 2 && propertyName.substr(0, 2) == "--") + // Only process custom properties in the first pass + if (name.length() >= 2 && name.substr(0, 2) == "--") { - setCustomProperty(propertyName, value); + setCustomProperty(name, value); } else { - inheritedProperties[propertyName] = value; + css_properties[name] = value; } } - } - // Second pass: Inherit regular properties - for (const auto &[name, value] : inheritedProperties) - computeProperty(name, value, context); - return inherited; - } - - bool ComputedStyle::update(values::computed::Context &context) - { - resetProperties(context.resetStyle(), context); - auto inherited_style = context.inheritedStyle(); - if (inherited_style.has_value()) - { - inheritProperties(inherited_style.value(), context); - } - - // Compute shorthand properties such as `margin`, `padding`, `border`, etc. - computeShorthandProperties(context); - updateBaseComputedStyle(); - return true; - } - - bool ComputedStyle::update(const CSSStyleDeclaration &from_style, optional context) - { - if (from_style.length() == 0) - return false; - - // Two-pass approach: first process custom properties, then regular properties - // This ensures custom properties are available when resolving variables in regular properties - - // First pass: Process all custom properties (CSS variables) - unordered_map css_properties; - for (int index = 0; index < from_style.length(); index++) - { - string name = from_style.item(index); - string value = from_style.getPropertyValue(name); - - auto it = find(name); - if (it != end()) - { - if (it->second == value) - continue; // No need to update if the value is the same. - it->second = value; // Update the existing property. - } - else + // Second pass: Process all regular properties (non-custom properties) + if (context.has_value()) { - setPropertyInternal(name, value); + for (const auto &[name, value] : css_properties) + { + computeProperty(name, value, context.value()); + } + computeShorthandProperties(context.value()); } - // Only process custom properties in the first pass - if (name.length() >= 2 && name.substr(0, 2) == "--") - { - setCustomProperty(name, value); - } - else - { - css_properties[name] = value; - } + // Compute shorthand properties such as `margin`, `padding`, `border`, etc. + if (context.has_value()) + computeShorthandProperties(context.value()); + updateBaseComputedStyle(); + return true; } - // Second pass: Process all regular properties (non-custom properties) - if (context.has_value()) + optional ComputedStyle::getTransitionProperty(uint32_t index) const { - for (const auto &[name, value] : css_properties) + if (index >= transition_properties_.size()) + return nullopt; + + const auto &property = transition_properties_[index]; + if (index < transition_durations_.size() && index < transition_delays_.size() && + index < transition_timing_functions_.size()) { - computeProperty(name, value, context.value()); + return TransitionProperty{ + property, + transition_durations_[index], + transition_delays_[index], + transition_timing_functions_[index]}; } - computeShorthandProperties(context.value()); + return nullopt; } - // Compute shorthand properties such as `margin`, `padding`, `border`, etc. - if (context.has_value()) - computeShorthandProperties(context.value()); - updateBaseComputedStyle(); - return true; - } + // CSS Variables (Custom Properties) Implementation - optional ComputedStyle::getTransitionProperty(uint32_t index) const - { - if (index >= transition_properties_.size()) - return nullopt; - - const auto &property = transition_properties_[index]; - if (index < transition_durations_.size() && index < transition_delays_.size() && - index < transition_timing_functions_.size()) + void ComputedStyle::setCustomProperty(const string &name, const string &value) { - return TransitionProperty{ - property, - transition_durations_[index], - transition_delays_[index], - transition_timing_functions_[index]}; + custom_properties_[name] = value; } - return nullopt; - } - // CSS Variables (Custom Properties) Implementation - - void ComputedStyle::setCustomProperty(const string &name, const string &value) - { - custom_properties_[name] = value; - } - - string ComputedStyle::getCustomProperty(const string &name) const - { - auto it = custom_properties_.find(name); - return (it != custom_properties_.end()) ? it->second : ""; - } + string ComputedStyle::getCustomProperty(const string &name) const + { + auto it = custom_properties_.find(name); + return (it != custom_properties_.end()) ? it->second : ""; + } - bool ComputedStyle::hasCustomProperty(const string &name) const - { - return custom_properties_.find(name) != custom_properties_.end(); - } + bool ComputedStyle::hasCustomProperty(const string &name) const + { + return custom_properties_.find(name) != custom_properties_.end(); + } - void ComputedStyle::inheritCustomProperties(const ComputedStyle &parentStyle) - { - // CSS custom properties inherit by default - for (const auto &prop : parentStyle.custom_properties_) + void ComputedStyle::inheritCustomProperties(const ComputedStyle &parentStyle) { - // Only inherit if not already set - if (custom_properties_.find(prop.first) == custom_properties_.end()) + // CSS custom properties inherit by default + for (const auto &prop : parentStyle.custom_properties_) { - custom_properties_[prop.first] = prop.second; + // Only inherit if not already set + if (custom_properties_.find(prop.first) == custom_properties_.end()) + { + custom_properties_[prop.first] = prop.second; + } } } - } - string ComputedStyle::resolveVariables(const string &value, const values::computed::Context &context) const - { - css_variable_parser::CSSVariableParser parser(value); - - // Create a variable resolver function that looks up variables in this style and inherited styles - auto resolver = [this, &context](const string &variable_name) -> optional + string ComputedStyle::resolveVariables(const string &value, const values::computed::Context &context) const { - // First try local custom properties - if (hasCustomProperty(variable_name)) - return getCustomProperty(variable_name); - - // Then try parent style (inheritance) - auto inherited_style = context.inheritedStyle(); - if (inherited_style.has_value() && inherited_style->hasCustomProperty(variable_name)) - return inherited_style->getCustomProperty(variable_name); + css_variable_parser::CSSVariableParser parser(value); - // Variable not found - return nullopt; - }; + // Create a variable resolver function that looks up variables in this style and inherited styles + auto resolver = [this, &context](const string &variable_name) -> optional + { + // First try local custom properties + if (hasCustomProperty(variable_name)) + return getCustomProperty(variable_name); - string result = parser.resolveVariables(resolver); + // Then try parent style (inheritance) + auto inherited_style = context.inheritedStyle(); + if (inherited_style.has_value() && inherited_style->hasCustomProperty(variable_name)) + return inherited_style->getCustomProperty(variable_name); - // Check if parsing was successful - if (!parser.isValid()) - { - // If parsing failed, return the original value - return value; - } + // Variable not found + return nullopt; + }; - return result; - } + string result = parser.resolveVariables(resolver); - void ComputedStyle::setPropertyInternal(const string &name, const string &value) - { - insert({name, value}); - } + // Check if parsing was successful + if (!parser.isValid()) + { + // If parsing failed, return the original value + return value; + } - void ComputedStyle::computeProperty(const string &name, const string &value, values::computed::Context &context) - { - using namespace crates::css2; + return result; + } - // Handle CSS custom properties (variables) - if (name.length() >= 2 && name.substr(0, 2) == "--") + void ComputedStyle::setPropertyInternal(const string &name, const string &value) { - setCustomProperty(name, value); - return; + insert({name, value}); } - // Clear existing variable dependencies for this property - variable_tracker_.clearDependencies(name); - - // Track variable dependencies before resolving - css_variable_parser::CSSVariableParser parser(value); - auto variableReferences = parser.getVariableReferences(); - for (const auto &varRef : variableReferences) + void ComputedStyle::computeProperty(const string &name, const string &value, values::computed::Context &context) { - variable_tracker_.trackDependency(varRef.variable_name, name); - } + using namespace crates::css2; + + // Handle CSS custom properties (variables) + if (name.length() >= 2 && name.substr(0, 2) == "--") + { + setCustomProperty(name, value); + return; + } - // Resolve variables in the value for regular properties - string resolvedValue = resolveVariables(value, context); + // Clear existing variable dependencies for this property + variable_tracker_.clearDependencies(name); - // Box model properties - if (name == "display") - { - display_ = Parse::ParseSingleValue(resolvedValue); - bitfields_.SetHasDisplay(true); - } - else if (name == "box-sizing") - { - box_sizing_ = Parse::ParseSingleValue(resolvedValue); - bitfields_.SetHasBoxSizing(true); - } - else if (name == "z-index") - { - z_index_ = stoi(resolvedValue); - bitfields_.SetHasZIndex(true); - } - else if (name == "position") - { - position_type_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } - else if (name == "top") - { - inset_.setTop(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - } - else if (name == "right") - { - inset_.setRight(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - } - else if (name == "bottom") - { - inset_.setBottom(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - } - else if (name == "left") - { - inset_.setLeft(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - } - else if (name == "overflow-x") - { - overflow_x_ = Parse::ParseSingleValue(resolvedValue); - bitfields_.SetHasOverflowX(true); - } - else if (name == "overflow-y") - { - overflow_y_ = Parse::ParseSingleValue(resolvedValue); - bitfields_.SetHasOverflowY(true); - } + // Track variable dependencies before resolving + css_variable_parser::CSSVariableParser parser(value); + auto variableReferences = parser.getVariableReferences(); + for (const auto &varRef : variableReferences) + { + variable_tracker_.trackDependency(varRef.variable_name, name); + } + + // Resolve variables in the value for regular properties + string resolvedValue = resolveVariables(value, context); + + // Box model properties + if (name == "display") + { + display_ = Parse::ParseSingleValue(resolvedValue); + bitfields_.SetHasDisplay(true); + } + else if (name == "box-sizing") + { + box_sizing_ = Parse::ParseSingleValue(resolvedValue); + bitfields_.SetHasBoxSizing(true); + } + else if (name == "z-index") + { + z_index_ = stoi(resolvedValue); + bitfields_.SetHasZIndex(true); + } + else if (name == "position") + { + position_type_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } + else if (name == "top") + { + inset_.setTop(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + } + else if (name == "right") + { + inset_.setRight(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + } + else if (name == "bottom") + { + inset_.setBottom(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + } + else if (name == "left") + { + inset_.setLeft(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + } + else if (name == "overflow-x") + { + overflow_x_ = Parse::ParseSingleValue(resolvedValue); + bitfields_.SetHasOverflowX(true); + } + else if (name == "overflow-y") + { + overflow_y_ = Parse::ParseSingleValue(resolvedValue); + bitfields_.SetHasOverflowY(true); + } - // Margin - if (name == "margin-top") - margin_.setTop(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - else if (name == "margin-right") - margin_.setRight(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - else if (name == "margin-bottom") - margin_.setBottom(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - else if (name == "margin-left") - margin_.setLeft(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - - // Padding - if (name == "padding-top") - padding_.setTop(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - else if (name == "padding-right") - padding_.setRight(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - else if (name == "padding-bottom") - padding_.setBottom(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - else if (name == "padding-left") - padding_.setLeft(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); - - // Sizes - else if (name == "width") - width_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "height") - height_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "min-width") - min_width_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "min-height") - min_height_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - - /** + // Margin + if (name == "margin-top") + margin_.setTop(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + else if (name == "margin-right") + margin_.setRight(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + else if (name == "margin-bottom") + margin_.setBottom(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + else if (name == "margin-left") + margin_.setLeft(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + + // Padding + if (name == "padding-top") + padding_.setTop(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + else if (name == "padding-right") + padding_.setRight(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + else if (name == "padding-bottom") + padding_.setBottom(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + else if (name == "padding-left") + padding_.setLeft(Parse::ParseSingleValue(resolvedValue).toComputedValue(context)); + + // Sizes + else if (name == "width") + width_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "height") + height_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "min-width") + min_width_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "min-height") + min_height_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + + /** * Border properties */ - // border-width - else if (name == "border-top-width") - border_width_.top() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - else if (name == "border-right-width") - border_width_.right() = Parse::ParseSingleValue(resolvedValue) + // border-width + else if (name == "border-top-width") + border_width_.top() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - else if (name == "border-bottom-width") - border_width_.bottom() = Parse::ParseSingleValue(resolvedValue) + else if (name == "border-right-width") + border_width_.right() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-bottom-width") + border_width_.bottom() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-left-width") + border_width_.left() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - else if (name == "border-left-width") - border_width_.left() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - // border-style - else if (name == "border-top-style") - border_style_.top() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - else if (name == "border-right-style") - border_style_.right() = Parse::ParseSingleValue(resolvedValue) + // border-style + else if (name == "border-top-style") + border_style_.top() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - else if (name == "border-bottom-style") - border_style_.bottom() = Parse::ParseSingleValue(resolvedValue) + else if (name == "border-right-style") + border_style_.right() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-bottom-style") + border_style_.bottom() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-left-style") + border_style_.left() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - else if (name == "border-left-style") - border_style_.left() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - // border-color - else if (name == "border-top-color") - border_color_.top() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - else if (name == "border-right-color") - border_color_.right() = Parse::ParseSingleValue(resolvedValue) + // border-color + else if (name == "border-top-color") + border_color_.top() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - else if (name == "border-bottom-color") - border_color_.bottom() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - else if (name == "border-left-color") - border_color_.left() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - // border-radius - else if (name == "border-top-left-radius") - border_radius_.topLeft() = Parse::ParseSingleValue(resolvedValue) + else if (name == "border-right-color") + border_color_.right() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-bottom-color") + border_color_.bottom() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - else if (name == "border-top-right-radius") - border_radius_.topRight() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - else if (name == "border-bottom-left-radius") - border_radius_.bottomLeft() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - else if (name == "border-bottom-right-radius") - border_radius_.bottomRight() = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - - // Font properties - if (name == "font-family") - fonts_ = parsing::parseFontFamily(resolvedValue); - else if (name == "font-size") - font_size_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "font-weight") - font_weight_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "font-style") - font_style_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "line-height") - line_height_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - - // Visibility properties - // TODO: implement visibility properties - - // Text properties - else if (name == "text-align") - text_align_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "direction") - text_direction_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "vertical-align") - vertical_align_ = Parse::ParseSingleValue(resolvedValue); - - // Color properties - else if (name == "color") - { - color_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - bitfields_.SetHasColor(true); - } - else if (name == "background-color") - { - background_color_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - bitfields_.SetHasBackgroundColor(true); - } - else if (name == "background-image") - { - background_image_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - bitfields_.SetHasBackgroundImage(true); - } - else if (name == "background-blend-mode") - { - background_blend_mode_ = Parse::ParseSingleValue(resolvedValue) + else if (name == "border-left-color") + border_color_.left() = Parse::ParseSingleValue(resolvedValue) .toComputedValue(context); - } - else if (name == "background-clip") - { - background_clip_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } - else if (name == "background-origin") - { - background_origin_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } - else if (name == "background-repeat") - { - background_repeat_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } - else if (name == "background-size") - { - background_size_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } + // border-radius + else if (name == "border-top-left-radius") + border_radius_.topLeft() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-top-right-radius") + border_radius_.topRight() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-bottom-left-radius") + border_radius_.bottomLeft() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + else if (name == "border-bottom-right-radius") + border_radius_.bottomRight() = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + + // Font properties + if (name == "font-family") + fonts_ = parsing::parseFontFamily(resolvedValue); + else if (name == "font-size") + font_size_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "font-weight") + font_weight_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "font-style") + font_style_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "line-height") + line_height_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + + // Visibility properties + // TODO: implement visibility properties + + // Text properties + else if (name == "text-align") + text_align_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "direction") + text_direction_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "vertical-align") + vertical_align_ = Parse::ParseSingleValue(resolvedValue); + + // Color properties + else if (name == "color") + { + color_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + bitfields_.SetHasColor(true); + } + else if (name == "background-color") + { + background_color_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + bitfields_.SetHasBackgroundColor(true); + } + else if (name == "background-image") + { + background_image_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + bitfields_.SetHasBackgroundImage(true); + } + else if (name == "background-blend-mode") + { + background_blend_mode_ = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + } + else if (name == "background-clip") + { + background_clip_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } + else if (name == "background-origin") + { + background_origin_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } + else if (name == "background-repeat") + { + background_repeat_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } + else if (name == "background-size") + { + background_size_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } - // Flexbox - else if (name == "flex-direction") - flex_direction_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "flex-grow") - flex_grow_ = stof(resolvedValue); - else if (name == "flex-shrink") - flex_shrink_ = stof(resolvedValue); - else if (name == "align-items") - align_items_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "align-self") - align_self_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "align-content") - align_content_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "justify-content") - justify_content_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "justify-self") - justify_self_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "justify-items") - justify_items_ = Parse::ParseSingleValue(resolvedValue); - else if (name == "row-gap") - row_gap_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - else if (name == "column-gap") - column_gap_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - - /** + // Flexbox + else if (name == "flex-direction") + flex_direction_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "flex-grow") + flex_grow_ = stof(resolvedValue); + else if (name == "flex-shrink") + flex_shrink_ = stof(resolvedValue); + else if (name == "align-items") + align_items_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "align-self") + align_self_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "align-content") + align_content_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "justify-content") + justify_content_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "justify-self") + justify_self_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "justify-items") + justify_items_ = Parse::ParseSingleValue(resolvedValue); + else if (name == "row-gap") + row_gap_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + else if (name == "column-gap") + column_gap_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + + /** * Transform properties */ - else if (name == "transform") - { - transform_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - bitfields_.SetHasTransform(transform_.empty() == false); - } + else if (name == "transform") + { + transform_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + bitfields_.SetHasTransform(transform_.empty() == false); + } - /** + /** * Visual Effects properties */ - else if (name == "filter") - { - filter_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } - else if (name == "backdrop-filter") - { - backdrop_filter_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); - } + else if (name == "filter") + { + filter_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } + else if (name == "backdrop-filter") + { + backdrop_filter_ = Parse::ParseSingleValue(resolvedValue).toComputedValue(context); + } - /** + /** * Transitions and animations */ - else if (name == "transition-property") - { - transition_properties_ = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); - } - else if (name == "transition-duration") - { - transition_durations_ = Parse::ParseValuesArray(resolvedValue) - .toComputedValues(context); - } - else if (name == "transition-delay") - { - transition_delays_ = Parse::ParseValuesArray(resolvedValue) - .toComputedValues(context); + else if (name == "transition-property") + { + transition_properties_ = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + } + else if (name == "transition-duration") + { + transition_durations_ = Parse::ParseValuesArray(resolvedValue) + .toComputedValues(context); + } + else if (name == "transition-delay") + { + transition_delays_ = Parse::ParseValuesArray(resolvedValue) + .toComputedValues(context); + } + else if (name == "transition-timing-function") + { + transition_timing_functions_ = Parse::ParseValuesArray(resolvedValue) + .toComputedValues(context); + } } - else if (name == "transition-timing-function") + + void ComputedStyle::computeShorthandProperties(values::computed::Context &context) { - transition_timing_functions_ = Parse::ParseValuesArray(resolvedValue) - .toComputedValues(context); - } - } + bool hasBackgroundPositionX = hasProperty("background-position-x"); + bool hasBackgroundPositionY = hasProperty("background-position-y"); - void ComputedStyle::computeShorthandProperties(values::computed::Context &context) - { - bool hasBackgroundPositionX = hasProperty("background-position-x"); - bool hasBackgroundPositionY = hasProperty("background-position-y"); + if (hasBackgroundPositionX || hasBackgroundPositionY) + { + string shorthandValue; + vector parts; + if (hasBackgroundPositionX) + parts.push_back(getPropertyValue("background-position-x")); + if (hasBackgroundPositionY) + parts.push_back(getPropertyValue("background-position-y")); + shorthandValue = accumulate(parts.begin(), parts.end(), string(), [](const string &a, const string &b) + { return a.empty() ? b : (a + " " + b); }); + + string resolvedValue = resolveVariables(shorthandValue, context); + background_position_ = Parse::ParseSingleValue(resolvedValue) + .toComputedValue(context); + } + } - if (hasBackgroundPositionX || hasBackgroundPositionY) + void ComputedStyle::updateBaseComputedStyle() { - string shorthandValue; - vector parts; - if (hasBackgroundPositionX) - parts.push_back(getPropertyValue("background-position-x")); - if (hasBackgroundPositionY) - parts.push_back(getPropertyValue("background-position-y")); - shorthandValue = accumulate(parts.begin(), parts.end(), string(), [](const string &a, const string &b) - { return a.empty() ? b : (a + " " + b); }); - - string resolvedValue = resolveVariables(shorthandValue, context); - background_position_ = Parse::ParseSingleValue(resolvedValue) - .toComputedValue(context); + // TODO: implement updateBaseComputedStyle } } - - void ComputedStyle::updateBaseComputedStyle() - { - // TODO: implement updateBaseComputedStyle - } -} +} // namespace endor diff --git a/src/client/cssom/computed_style.hpp b/src/client/cssom/computed_style.hpp index 0bae88bc6..331e04bdb 100644 --- a/src/client/cssom/computed_style.hpp +++ b/src/client/cssom/computed_style.hpp @@ -15,600 +15,602 @@ #include "./css_style_declaration.hpp" #include "./variable_reference_tracker.hpp" -namespace client_cssom +namespace endor { - enum class Visibility + namespace client_cssom { - kVisible, - kHidden, - kCollapse, - }; - - enum class PointerEvents - { - kAuto, - kNone, - }; - - class ComputedStyle : std::map - { - public: - enum Difference + enum class Visibility { - kEqual, - kNonInherited, - kPseudoElementStyle, - kIndependentInherited, - kInherited, - kDescendantAffecting, + kVisible, + kHidden, + kCollapse, }; - static Difference ComputeDifference(const ComputedStyle &old_style, const ComputedStyle &new_style); - static bool IsInheritedProperty(const std::string property) + + enum class PointerEvents { - // CSS custom properties always inherit by default - if (property.length() >= 2 && property.substr(0, 2) == "--") - return true; - - static const std::unordered_set inherited_properties = { - "font-size", - "font-weight", - "font-style", - "line-height", - "color", - "visibility", - "text-align", - "direction", - "text-decoration", - "text-transform", - "letter-spacing", - "word-spacing", - "white-space", - "text-indent"}; - return inherited_properties.find(property) != inherited_properties.end(); - } - static bool IsAnimatableProperty(const std::string &property) + kAuto, + kNone, + }; + + class ComputedStyle : std::map { - static const std::unordered_set animatable_properties = { - // CSS Transforms - "transform", - "transform-origin", - "perspective", - "perspective-origin", - "backface-visibility", - - // Basic Box Model - "width", - "height", - "max-width", - "max-height", - "min-width", - "min-height", - "margin", - "margin-top", - "margin-right", - "margin-bottom", - "margin-left", - "padding", - "padding-top", - "padding-right", - "padding-bottom", - "padding-left", - - // Border - "border-width", - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width", - "border-radius", - "border-top-left-radius", - "border-top-right-radius", - "border-bottom-right-radius", - "border-bottom-left-radius", - "border-color", - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color", - - // Positioning - "top", - "right", - "bottom", - "left", - - // Flexbox - "flex-grow", - "flex-shrink", - "flex-basis", - "gap", - "row-gap", - "column-gap", - - // Colors and Backgrounds - "color", - "background-color", - "background-position", - "background-size", - "background-image", - "box-shadow", - "text-shadow", - - // Text - "font-size", - "font-weight", - "letter-spacing", - "word-spacing", - "line-height", - "text-indent", - "vertical-align", - - // Visibility and Opacity - "opacity", - "visibility", - "z-index", - "clip", - "clip-path", - - // CSS Filters - "filter", - "backdrop-filter", + public: + enum Difference + { + kEqual, + kNonInherited, + kPseudoElementStyle, + kIndependentInherited, + kInherited, + kDescendantAffecting, }; - return animatable_properties.find(property) != animatable_properties.end(); - } + static Difference ComputeDifference(const ComputedStyle &old_style, const ComputedStyle &new_style); + static bool IsInheritedProperty(const std::string property) + { + // CSS custom properties always inherit by default + if (property.length() >= 2 && property.substr(0, 2) == "--") + return true; + + static const std::unordered_set inherited_properties = { + "font-size", + "font-weight", + "font-style", + "line-height", + "color", + "visibility", + "text-align", + "direction", + "text-decoration", + "text-transform", + "letter-spacing", + "word-spacing", + "white-space", + "text-indent"}; + return inherited_properties.find(property) != inherited_properties.end(); + } + static bool IsAnimatableProperty(const std::string &property) + { + static const std::unordered_set animatable_properties = { + // CSS Transforms + "transform", + "transform-origin", + "perspective", + "perspective-origin", + "backface-visibility", + + // Basic Box Model + "width", + "height", + "max-width", + "max-height", + "min-width", + "min-height", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + + // Border + "border-width", + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width", + "border-radius", + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius", + "border-color", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + + // Positioning + "top", + "right", + "bottom", + "left", + + // Flexbox + "flex-grow", + "flex-shrink", + "flex-basis", + "gap", + "row-gap", + "column-gap", + + // Colors and Backgrounds + "color", + "background-color", + "background-position", + "background-size", + "background-image", + "box-shadow", + "text-shadow", + + // Text + "font-size", + "font-weight", + "letter-spacing", + "word-spacing", + "line-height", + "text-indent", + "vertical-align", + + // Visibility and Opacity + "opacity", + "visibility", + "z-index", + "clip", + "clip-path", + + // CSS Filters + "filter", + "backdrop-filter", + }; + return animatable_properties.find(property) != animatable_properties.end(); + } - // Create a `ComputedStyle` from a `CSSStyleDeclaration` and a target node to create context. - static ComputedStyle Make(const CSSStyleDeclaration &style, std::shared_ptr target_node); + // Create a `ComputedStyle` from a `CSSStyleDeclaration` and a target node to create context. + static ComputedStyle Make(const CSSStyleDeclaration &style, std::shared_ptr target_node); - public: - ComputedStyle() = default; - ComputedStyle(const ComputedStyle &) = default; - ComputedStyle(const CSSStyleDeclaration &, std::optional context); + public: + ComputedStyle() = default; + ComputedStyle(const ComputedStyle &) = default; + ComputedStyle(const CSSStyleDeclaration &, std::optional context); - operator crates::layout2::LayoutStyle() const; - friend std::ostream &operator<<(std::ostream &os, const ComputedStyle &style) - { - if (style.empty()) + operator crates::layout2::LayoutStyle() const; + friend std::ostream &operator<<(std::ostream &os, const ComputedStyle &style) { - os << "ComputedStyle {}"; + if (style.empty()) + { + os << "ComputedStyle {}"; + } + else + { + os << "ComputedStyle {" << std::endl; + for (const auto &[key, value] : style) + os << " " << key << ": " << value << std::endl; + os << "}"; + } + return os; } - else + + public: + inline bool hasProperty(const std::string &name) const + { + return find(name) != end(); + } + inline std::string getPropertyValue(const std::string &name) const { - os << "ComputedStyle {" << std::endl; - for (const auto &[key, value] : style) - os << " " << key << ": " << value << std::endl; - os << "}"; + const auto &it = find(name); + if (it != end()) + return it->second; + return ""; } - return os; - } - public: - inline bool hasProperty(const std::string &name) const - { - return find(name) != end(); - } - inline std::string getPropertyValue(const std::string &name) const - { - const auto &it = find(name); - if (it != end()) - return it->second; - return ""; - } - - void resetProperties(std::optional, values::computed::Context &); - size_t inheritProperties(const ComputedStyle &, values::computed::Context &); - - // Update the computed style from a context. - bool update(values::computed::Context &); - // Update the computed style from the given `CSSStyleDeclaration`, and a context to compute the values. - bool update(const CSSStyleDeclaration &, std::optional); - - // If this `ComputedStyle` is affected by animation/transition, then the unanimated "base" style is called the - // "base computed style". It is used to compute the animated style. - // - // This method returns if the base computed style is set, namely if the computed style is affected by animations or - // transitions. - inline bool hasBaseComputedStyle() const - { - return base_computed_style_ != nullptr; - } - // Returns the base computed style, it asserts that the base computed style is set. - inline const ComputedStyle *getBaseComputedStyle() const - { - return base_computed_style_.get(); - } - inline const ComputedStyle *getBaseComputedStyleOrThis() const - { - return hasBaseComputedStyle() ? getBaseComputedStyle() : this; - } + void resetProperties(std::optional, values::computed::Context &); + size_t inheritProperties(const ComputedStyle &, values::computed::Context &); - // Properties - inline const values::computed::Display &display() const - { - return display_; - } - inline const values::computed::BoxSizing &boxSizing() const - { - return box_sizing_; - } - inline const values::computed::Overflow &overflowX() const - { - return overflow_x_; - } - inline const values::computed::Overflow &overflowY() const - { - return overflow_y_; - } - inline const values::computed::Margin &margin() const - { - return margin_; - } - inline const values::computed::Padding &padding() const - { - return padding_; - } + // Update the computed style from a context. + bool update(values::computed::Context &); + // Update the computed style from the given `CSSStyleDeclaration`, and a context to compute the values. + bool update(const CSSStyleDeclaration &, std::optional); - inline const values::computed::Size &width() const - { - return width_; - } - inline const values::computed::Size &height() const - { - return height_; - } - inline const values::computed::Size &minWidth() const - { - return min_width_; - } - inline const values::computed::Size &minHeight() const - { - return min_height_; - } - inline const values::computed::MaxSize &maxWidth() const - { - return max_width_; - } - inline const values::computed::MaxSize &maxHeight() const - { - return max_height_; - } + // If this `ComputedStyle` is affected by animation/transition, then the unanimated "base" style is called the + // "base computed style". It is used to compute the animated style. + // + // This method returns if the base computed style is set, namely if the computed style is affected by animations or + // transitions. + inline bool hasBaseComputedStyle() const + { + return base_computed_style_ != nullptr; + } + // Returns the base computed style, it asserts that the base computed style is set. + inline const ComputedStyle *getBaseComputedStyle() const + { + return base_computed_style_.get(); + } + inline const ComputedStyle *getBaseComputedStyleOrThis() const + { + return hasBaseComputedStyle() ? getBaseComputedStyle() : this; + } - inline const values::computed::BorderWidth &borderWidth() const - { - return border_width_; - } - inline const values::computed::BorderColor &borderColor() const - { - return border_color_; - } - inline const values::computed::BorderStyle &borderStyle() const - { - return border_style_; - } - inline const values::computed::BorderRadius &borderRadius() const - { - return border_radius_; - } - inline const values::computed::BorderCornerRadius &borderTopLeftRadius() const - { - return border_radius_.topLeft(); - } - inline const values::computed::BorderCornerRadius &borderTopRightRadius() const - { - return border_radius_.topRight(); - } - inline const values::computed::BorderCornerRadius &borderBottomLeftRadius() const - { - return border_radius_.bottomLeft(); - } - inline const values::computed::BorderCornerRadius &borderBottomRightRadius() const - { - return border_radius_.bottomRight(); - } + // Properties + inline const values::computed::Display &display() const + { + return display_; + } + inline const values::computed::BoxSizing &boxSizing() const + { + return box_sizing_; + } + inline const values::computed::Overflow &overflowX() const + { + return overflow_x_; + } + inline const values::computed::Overflow &overflowY() const + { + return overflow_y_; + } + inline const values::computed::Margin &margin() const + { + return margin_; + } + inline const values::computed::Padding &padding() const + { + return padding_; + } - // Returns `true` if any of the border radius values are non-zero. - inline const bool hasBorderRadius() const - { - if (!borderTopLeftRadius().isZero()) - return true; - if (!borderTopRightRadius().isZero()) - return true; - if (!borderBottomLeftRadius().isZero()) - return true; - if (!borderBottomRightRadius().isZero()) - return true; - return false; - } - - inline const values::computed::PositionType &positionType() const - { - return position_type_; - } - inline bool isPositioned() const - { - return !position_type_.isStatic(); - } - inline bool hasZIndex() const - { - return bitfields_.HasZIndex(); - } - inline std::optional zIndex() const - { - return bitfields_.HasZIndex() - ? std::make_optional(z_index_) - : std::nullopt; - } + inline const values::computed::Size &width() const + { + return width_; + } + inline const values::computed::Size &height() const + { + return height_; + } + inline const values::computed::Size &minWidth() const + { + return min_width_; + } + inline const values::computed::Size &minHeight() const + { + return min_height_; + } + inline const values::computed::MaxSize &maxWidth() const + { + return max_width_; + } + inline const values::computed::MaxSize &maxHeight() const + { + return max_height_; + } - inline Visibility visibility() const - { - return visibility_.value_or(Visibility::kVisible); - } - inline PointerEvents pointerEvents() const - { - return pointer_events_.value_or(PointerEvents::kAuto); - } + inline const values::computed::BorderWidth &borderWidth() const + { + return border_width_; + } + inline const values::computed::BorderColor &borderColor() const + { + return border_color_; + } + inline const values::computed::BorderStyle &borderStyle() const + { + return border_style_; + } + inline const values::computed::BorderRadius &borderRadius() const + { + return border_radius_; + } + inline const values::computed::BorderCornerRadius &borderTopLeftRadius() const + { + return border_radius_.topLeft(); + } + inline const values::computed::BorderCornerRadius &borderTopRightRadius() const + { + return border_radius_.topRight(); + } + inline const values::computed::BorderCornerRadius &borderBottomLeftRadius() const + { + return border_radius_.bottomLeft(); + } + inline const values::computed::BorderCornerRadius &borderBottomRightRadius() const + { + return border_radius_.bottomRight(); + } - inline const std::vector &fonts() const - { - return fonts_; - } - inline const values::computed::FontSize &fontSize() const - { - return font_size_; - } - inline const values::computed::FontWeight &fontWeight() const - { - return font_weight_; - } - inline const values::computed::FontStyle &fontStyle() const - { - return font_style_; - } - inline const values::computed::LineHeight &lineHeight() const - { - return line_height_; - } + // Returns `true` if any of the border radius values are non-zero. + inline const bool hasBorderRadius() const + { + if (!borderTopLeftRadius().isZero()) + return true; + if (!borderTopRightRadius().isZero()) + return true; + if (!borderBottomLeftRadius().isZero()) + return true; + if (!borderBottomRightRadius().isZero()) + return true; + return false; + } - inline const values::computed::TextAlign &textAlign() const - { - return text_align_; - } - inline const values::computed::Direction &textDirection() const - { - return text_direction_; - } - inline const values::computed::VerticalAlign &verticalAlign() const - { - return vertical_align_; - } + inline const values::computed::PositionType &positionType() const + { + return position_type_; + } + inline bool isPositioned() const + { + return !position_type_.isStatic(); + } + inline bool hasZIndex() const + { + return bitfields_.HasZIndex(); + } + inline std::optional zIndex() const + { + return bitfields_.HasZIndex() + ? std::make_optional(z_index_) + : std::nullopt; + } - inline const values::computed::Color &color() const - { - return color_; - } - inline bool hasColor() const - { - return bitfields_.HasColor(); - } - inline const values::computed::Color &backgroundColor() const - { - return background_color_; - } - inline bool hasBackgroundColor() const - { - return bitfields_.HasBackgroundColor(); - } - inline values::computed::Image &backgroundImage() - { - return background_image_; - } - inline const values::computed::Image &backgroundImage() const - { - return background_image_; - } - inline bool hasBackgroundImage() const - { - return bitfields_.HasBackgroundImage(); - } - inline const values::computed::BackgroundBlendMode &backgroundBlendMode() const - { - return background_blend_mode_; - } - inline const values::computed::BackgroundClip &backgroundClip() const - { - return background_clip_; - } - inline const values::computed::BackgroundOrigin &backgroundOrigin() const - { - return background_origin_; - } - inline const values::computed::BackgroundRepeat &backgroundRepeat() const - { - return background_repeat_; - } - inline const values::computed::BackgroundSize &backgroundSize() const - { - return background_size_; - } - inline const values::computed::BackgroundPosition &backgroundPosition() const - { - return background_position_; - } + inline Visibility visibility() const + { + return visibility_.value_or(Visibility::kVisible); + } + inline PointerEvents pointerEvents() const + { + return pointer_events_.value_or(PointerEvents::kAuto); + } - // Visibility utility functions. - inline bool visibleToHitTesting() const - { - return visibility_ == Visibility::kVisible && - pointer_events_ != PointerEvents::kNone; - } + inline const std::vector &fonts() const + { + return fonts_; + } + inline const values::computed::FontSize &fontSize() const + { + return font_size_; + } + inline const values::computed::FontWeight &fontWeight() const + { + return font_weight_; + } + inline const values::computed::FontStyle &fontStyle() const + { + return font_style_; + } + inline const values::computed::LineHeight &lineHeight() const + { + return line_height_; + } - // 3D Transforms - inline const bool hasTransform() const - { - return bitfields_.HasTransform(); - } - inline const values::computed::Transform &transform() const - { - return transform_; - } - inline const size_t applyTransformTo(glm::mat4 &matrix) const - { - return transform_.applyTo(matrix); - } - inline const size_t applyTransformTo(glm::mat4 &matrix, const glm::vec2 &elementSize) const - { - return transform_.applyTo(matrix, elementSize); - } + inline const values::computed::TextAlign &textAlign() const + { + return text_align_; + } + inline const values::computed::Direction &textDirection() const + { + return text_direction_; + } + inline const values::computed::VerticalAlign &verticalAlign() const + { + return vertical_align_; + } - // Visual Effects - inline const values::computed::Filter &filter() const - { - return filter_; - } - inline const values::computed::Filter &backdropFilter() const - { - return backdrop_filter_; - } + inline const values::computed::Color &color() const + { + return color_; + } + inline bool hasColor() const + { + return bitfields_.HasColor(); + } + inline const values::computed::Color &backgroundColor() const + { + return background_color_; + } + inline bool hasBackgroundColor() const + { + return bitfields_.HasBackgroundColor(); + } + inline values::computed::Image &backgroundImage() + { + return background_image_; + } + inline const values::computed::Image &backgroundImage() const + { + return background_image_; + } + inline bool hasBackgroundImage() const + { + return bitfields_.HasBackgroundImage(); + } + inline const values::computed::BackgroundBlendMode &backgroundBlendMode() const + { + return background_blend_mode_; + } + inline const values::computed::BackgroundClip &backgroundClip() const + { + return background_clip_; + } + inline const values::computed::BackgroundOrigin &backgroundOrigin() const + { + return background_origin_; + } + inline const values::computed::BackgroundRepeat &backgroundRepeat() const + { + return background_repeat_; + } + inline const values::computed::BackgroundSize &backgroundSize() const + { + return background_size_; + } + inline const values::computed::BackgroundPosition &backgroundPosition() const + { + return background_position_; + } - // Transitions and animations - inline const std::vector &transitionProperties() const - { - return transition_properties_; - } - inline const std::vector &transitionDurations() const - { - return transition_durations_; - } - inline const std::vector &transitionDelays() const - { - return transition_delays_; - } - inline const std::vector &transitionTimingFunctions() const - { - return transition_timing_functions_; - } + // Visibility utility functions. + inline bool visibleToHitTesting() const + { + return visibility_ == Visibility::kVisible && + pointer_events_ != PointerEvents::kNone; + } - struct TransitionProperty - { - values::computed::TransitionProperty property; - values::computed::Time duration; - values::computed::Time delay; - values::computed::TimingFunction timing_function; - }; - // Returns the transition property at the given index, otherwise returns `std::nullopt`. - std::optional getTransitionProperty(uint32_t index) const; - inline const size_t getTransitionPropertiesCount() const - { - return transition_properties_.size(); - } - inline const bool hasTransitionProperties() const - { - return !transition_properties_.empty(); - } - - void setCustomProperty(const std::string &name, const std::string &value); - std::string getCustomProperty(const std::string &name) const; - bool hasCustomProperty(const std::string &name) const; - void inheritCustomProperties(const ComputedStyle &parentStyle); - std::string resolveVariables(const std::string &value, const values::computed::Context &context) const; - - private: - void setPropertyInternal(const std::string &name, const std::string &value); - void computeProperty(const std::string &name, const std::string &value, values::computed::Context &); - void computeShorthandProperties(values::computed::Context &); - void updateBaseComputedStyle(); - - private: - // Box model - values::computed::Display display_ = values::computed::Display::Block(); - values::computed::BoxSizing box_sizing_ = values::computed::BoxSizing::ContentBox(); - values::computed::Overflow overflow_x_ = values::computed::Overflow::Visible(); - values::computed::Overflow overflow_y_ = values::computed::Overflow::Visible(); - values::computed::Margin margin_ = values::computed::Margin::Default(); - values::computed::Padding padding_ = values::computed::Padding::Default(); - - // Sizes - values::computed::Size width_ = values::computed::Size::Auto(); - values::computed::Size height_ = values::computed::Size::Auto(); - values::computed::Size min_width_ = values::computed::Size::Auto(); - values::computed::Size min_height_ = values::computed::Size::Auto(); - values::computed::MaxSize max_width_ = values::computed::MaxSize::None(); - values::computed::MaxSize max_height_ = values::computed::MaxSize::None(); - - // Border - values::computed::BorderWidth border_width_ = values::computed::BorderWidth::Default(); - values::computed::BorderColor border_color_ = values::computed::BorderColor::Default(); - values::computed::BorderStyle border_style_ = values::computed::BorderStyle::Default(); - values::computed::BorderRadius border_radius_ = values::computed::BorderRadius::Zero(); - - // Positional - values::computed::PositionType position_type_ = values::computed::PositionType::Static(); - values::computed::Inset inset_ = values::computed::Inset::Default(); - int z_index_ = 0; - - // Alignment - values::computed::AlignContent align_content_ = values::computed::AlignContent::Normal(); - values::computed::JustifyContent justify_content_ = values::computed::JustifyContent::Normal(); - values::computed::AlignSelf align_self_ = values::computed::AlignSelf::Auto(); - values::computed::AlignItems align_items_ = values::computed::AlignItems::Normal(); - values::computed::JustifySelf justify_self_ = values::computed::JustifySelf::Auto(); - values::computed::JustifyItems justify_items_ = values::computed::JustifyItems::Legacy(); - - // Flexbox - values::computed::LengthPercentage row_gap_; - values::computed::LengthPercentage column_gap_; - values::computed::FlexDirection flex_direction_; - values::computed::FlexWrap flex_wrap_; - values::CSSFloat flex_grow_ = 0.0f; - values::CSSFloat flex_shrink_ = 1.0f; - - // Grid - // TODO(yorkie): add grid properties when needed. - - // Visibility and UI - std::optional visibility_ = Visibility::kVisible; - std::optional pointer_events_ = PointerEvents::kAuto; - - // Font - std::vector fonts_; - values::computed::FontSize font_size_; - values::computed::FontWeight font_weight_; - values::computed::FontStyle font_style_; - values::computed::LineHeight line_height_ = values::computed::LineHeight::Normal(); - - // Text - values::computed::TextAlign text_align_; - values::computed::Direction text_direction_; - values::computed::VerticalAlign vertical_align_; - - // Colors - values::computed::Color color_ = values::computed::Color::Black(); - - // Background - values::computed::Color background_color_ = values::computed::Color::Transparent(); - values::computed::Image background_image_ = values::computed::Image::None(); - values::computed::BackgroundBlendMode background_blend_mode_ = values::computed::BackgroundBlendMode::Normal(); - values::computed::BackgroundClip background_clip_ = values::computed::BackgroundClip::BorderBox(); - values::computed::BackgroundOrigin background_origin_ = values::computed::BackgroundOrigin::PaddingBox(); - values::computed::BackgroundRepeat background_repeat_ = values::computed::BackgroundRepeat::Repeat(); - values::computed::BackgroundSize background_size_ = values::computed::BackgroundSize::Auto(); - values::computed::BackgroundPosition background_position_ = values::computed::BackgroundPosition::Default(); - - // 3D Transforms - values::computed::Transform transform_; - - // Visual Effects - values::computed::Filter filter_ = values::computed::Filter::None(); - values::computed::Filter backdrop_filter_ = values::computed::Filter::None(); - - // Transitions and animations - std::vector transition_properties_; - std::vector transition_durations_; - std::vector transition_delays_; - std::vector transition_timing_functions_; - std::shared_ptr base_computed_style_; - - private: // Bitfields for computed style properties. + // 3D Transforms + inline const bool hasTransform() const + { + return bitfields_.HasTransform(); + } + inline const values::computed::Transform &transform() const + { + return transform_; + } + inline const size_t applyTransformTo(glm::mat4 &matrix) const + { + return transform_.applyTo(matrix); + } + inline const size_t applyTransformTo(glm::mat4 &matrix, const glm::vec2 &elementSize) const + { + return transform_.applyTo(matrix, elementSize); + } + + // Visual Effects + inline const values::computed::Filter &filter() const + { + return filter_; + } + inline const values::computed::Filter &backdropFilter() const + { + return backdrop_filter_; + } + + // Transitions and animations + inline const std::vector &transitionProperties() const + { + return transition_properties_; + } + inline const std::vector &transitionDurations() const + { + return transition_durations_; + } + inline const std::vector &transitionDelays() const + { + return transition_delays_; + } + inline const std::vector &transitionTimingFunctions() const + { + return transition_timing_functions_; + } + + struct TransitionProperty + { + values::computed::TransitionProperty property; + values::computed::Time duration; + values::computed::Time delay; + values::computed::TimingFunction timing_function; + }; + // Returns the transition property at the given index, otherwise returns `std::nullopt`. + std::optional getTransitionProperty(uint32_t index) const; + inline const size_t getTransitionPropertiesCount() const + { + return transition_properties_.size(); + } + inline const bool hasTransitionProperties() const + { + return !transition_properties_.empty(); + } + + void setCustomProperty(const std::string &name, const std::string &value); + std::string getCustomProperty(const std::string &name) const; + bool hasCustomProperty(const std::string &name) const; + void inheritCustomProperties(const ComputedStyle &parentStyle); + std::string resolveVariables(const std::string &value, const values::computed::Context &context) const; + + private: + void setPropertyInternal(const std::string &name, const std::string &value); + void computeProperty(const std::string &name, const std::string &value, values::computed::Context &); + void computeShorthandProperties(values::computed::Context &); + void updateBaseComputedStyle(); + + private: + // Box model + values::computed::Display display_ = values::computed::Display::Block(); + values::computed::BoxSizing box_sizing_ = values::computed::BoxSizing::ContentBox(); + values::computed::Overflow overflow_x_ = values::computed::Overflow::Visible(); + values::computed::Overflow overflow_y_ = values::computed::Overflow::Visible(); + values::computed::Margin margin_ = values::computed::Margin::Default(); + values::computed::Padding padding_ = values::computed::Padding::Default(); + + // Sizes + values::computed::Size width_ = values::computed::Size::Auto(); + values::computed::Size height_ = values::computed::Size::Auto(); + values::computed::Size min_width_ = values::computed::Size::Auto(); + values::computed::Size min_height_ = values::computed::Size::Auto(); + values::computed::MaxSize max_width_ = values::computed::MaxSize::None(); + values::computed::MaxSize max_height_ = values::computed::MaxSize::None(); + + // Border + values::computed::BorderWidth border_width_ = values::computed::BorderWidth::Default(); + values::computed::BorderColor border_color_ = values::computed::BorderColor::Default(); + values::computed::BorderStyle border_style_ = values::computed::BorderStyle::Default(); + values::computed::BorderRadius border_radius_ = values::computed::BorderRadius::Zero(); + + // Positional + values::computed::PositionType position_type_ = values::computed::PositionType::Static(); + values::computed::Inset inset_ = values::computed::Inset::Default(); + int z_index_ = 0; + + // Alignment + values::computed::AlignContent align_content_ = values::computed::AlignContent::Normal(); + values::computed::JustifyContent justify_content_ = values::computed::JustifyContent::Normal(); + values::computed::AlignSelf align_self_ = values::computed::AlignSelf::Auto(); + values::computed::AlignItems align_items_ = values::computed::AlignItems::Normal(); + values::computed::JustifySelf justify_self_ = values::computed::JustifySelf::Auto(); + values::computed::JustifyItems justify_items_ = values::computed::JustifyItems::Legacy(); + + // Flexbox + values::computed::LengthPercentage row_gap_; + values::computed::LengthPercentage column_gap_; + values::computed::FlexDirection flex_direction_; + values::computed::FlexWrap flex_wrap_; + values::CSSFloat flex_grow_ = 0.0f; + values::CSSFloat flex_shrink_ = 1.0f; + + // Grid + // TODO(yorkie): add grid properties when needed. + + // Visibility and UI + std::optional visibility_ = Visibility::kVisible; + std::optional pointer_events_ = PointerEvents::kAuto; + + // Font + std::vector fonts_; + values::computed::FontSize font_size_; + values::computed::FontWeight font_weight_; + values::computed::FontStyle font_style_; + values::computed::LineHeight line_height_ = values::computed::LineHeight::Normal(); + + // Text + values::computed::TextAlign text_align_; + values::computed::Direction text_direction_; + values::computed::VerticalAlign vertical_align_; + + // Colors + values::computed::Color color_ = values::computed::Color::Black(); + + // Background + values::computed::Color background_color_ = values::computed::Color::Transparent(); + values::computed::Image background_image_ = values::computed::Image::None(); + values::computed::BackgroundBlendMode background_blend_mode_ = values::computed::BackgroundBlendMode::Normal(); + values::computed::BackgroundClip background_clip_ = values::computed::BackgroundClip::BorderBox(); + values::computed::BackgroundOrigin background_origin_ = values::computed::BackgroundOrigin::PaddingBox(); + values::computed::BackgroundRepeat background_repeat_ = values::computed::BackgroundRepeat::Repeat(); + values::computed::BackgroundSize background_size_ = values::computed::BackgroundSize::Auto(); + values::computed::BackgroundPosition background_position_ = values::computed::BackgroundPosition::Default(); + + // 3D Transforms + values::computed::Transform transform_; + + // Visual Effects + values::computed::Filter filter_ = values::computed::Filter::None(); + values::computed::Filter backdrop_filter_ = values::computed::Filter::None(); + + // Transitions and animations + std::vector transition_properties_; + std::vector transition_durations_; + std::vector transition_delays_; + std::vector transition_timing_functions_; + std::shared_ptr base_computed_style_; + + private: // Bitfields for computed style properties. #define ADD_BOOLEAN_BITFIELD(PRIVATE_NAME, PUBLIC_NAME) \ public: \ bool PUBLIC_NAME() const \ @@ -623,33 +625,34 @@ public: \ private: \ unsigned PRIVATE_NAME : 1 - class ComputedStyleBitfields - { - public: - explicit ComputedStyleBitfields() - : has_transform_(false) - , has_background_image_(false) + class ComputedStyleBitfields { - } - - ADD_BOOLEAN_BITFIELD(has_display_, HasDisplay); - ADD_BOOLEAN_BITFIELD(has_color_, HasColor); - ADD_BOOLEAN_BITFIELD(has_background_color_, HasBackgroundColor); - ADD_BOOLEAN_BITFIELD(has_background_image_, HasBackgroundImage); - ADD_BOOLEAN_BITFIELD(has_box_sizing_, HasBoxSizing); - ADD_BOOLEAN_BITFIELD(has_overflow_x_, HasOverflowX); - ADD_BOOLEAN_BITFIELD(has_overflow_y_, HasOverflowY); - ADD_BOOLEAN_BITFIELD(has_z_index_, HasZIndex); - ADD_BOOLEAN_BITFIELD(has_transform_, HasTransform); - }; + public: + explicit ComputedStyleBitfields() + : has_transform_(false) + , has_background_image_(false) + { + } + + ADD_BOOLEAN_BITFIELD(has_display_, HasDisplay); + ADD_BOOLEAN_BITFIELD(has_color_, HasColor); + ADD_BOOLEAN_BITFIELD(has_background_color_, HasBackgroundColor); + ADD_BOOLEAN_BITFIELD(has_background_image_, HasBackgroundImage); + ADD_BOOLEAN_BITFIELD(has_box_sizing_, HasBoxSizing); + ADD_BOOLEAN_BITFIELD(has_overflow_x_, HasOverflowX); + ADD_BOOLEAN_BITFIELD(has_overflow_y_, HasOverflowY); + ADD_BOOLEAN_BITFIELD(has_z_index_, HasZIndex); + ADD_BOOLEAN_BITFIELD(has_transform_, HasTransform); + }; #undef ADD_BOOLEAN_BITFIELD - ComputedStyleBitfields bitfields_; + ComputedStyleBitfields bitfields_; - // CSS Custom Properties (CSS Variables) support - std::unordered_map custom_properties_; + // CSS Custom Properties (CSS Variables) support + std::unordered_map custom_properties_; - // Variable dependency tracking - VariableReferenceTracker variable_tracker_; - }; -} + // Variable dependency tracking + VariableReferenceTracker variable_tracker_; + }; + } +} // namespace endor diff --git a/src/client/cssom/css_rule.cpp b/src/client/cssom/css_rule.cpp index 567b012a0..88d546528 100644 --- a/src/client/cssom/css_rule.cpp +++ b/src/client/cssom/css_rule.cpp @@ -1,29 +1,32 @@ #include "./css_rule.hpp" #include "./rules/all.hpp" -namespace client_cssom +namespace endor { - using namespace std; - using namespace rules; - using namespace crates; - - CSSRuleIndex CSSRuleList::insert(css2::stylesheets::CssRule &inner) + namespace client_cssom { - if (inner.type == css2::stylesheets::CssRuleType::kStyle) - { - auto &styleInner = dynamic_cast(inner); - push_back(make_shared(styleInner)); - return size() - 1; - } - else if (inner.type == css2::stylesheets::CssRuleType::kMedia) - { - auto &mediaInner = dynamic_cast(inner); - push_back(make_shared(mediaInner)); - return size() - 1; - } - else + using namespace std; + using namespace rules; + using namespace crates; + + CSSRuleIndex CSSRuleList::insert(css2::stylesheets::CssRule &inner) { - throw runtime_error("Unknown CSS rule type."); + if (inner.type == css2::stylesheets::CssRuleType::kStyle) + { + auto &styleInner = dynamic_cast(inner); + push_back(make_shared(styleInner)); + return size() - 1; + } + else if (inner.type == css2::stylesheets::CssRuleType::kMedia) + { + auto &mediaInner = dynamic_cast(inner); + push_back(make_shared(mediaInner)); + return size() - 1; + } + else + { + throw runtime_error("Unknown CSS rule type."); + } } } -} +} // namespace endor diff --git a/src/client/cssom/css_rule.hpp b/src/client/cssom/css_rule.hpp index ce9f3b18c..98c0cfb01 100644 --- a/src/client/cssom/css_rule.hpp +++ b/src/client/cssom/css_rule.hpp @@ -4,87 +4,90 @@ #include #include -namespace client_cssom +namespace endor { - // Forward declarations - class CSSStyleSheet; - - // Typedefs - using CSSRuleIndex = size_t; - - enum class CSSRuleType + namespace client_cssom { - kUnknownRule = 0x00, - kStyleRule, - kImportRule = 0x03, - kMediaRule, - kFontFaceRule, - kPageRule, - kKeyframesRule, - kNamespaceRule = 0x0a, - kCounterStyleRule, - kSupportsRule, - kFontFeatureValuesRule = 0x0e, - }; + // Forward declarations + class CSSStyleSheet; - class CSSRule - { - public: - CSSRule() = default; - virtual ~CSSRule() = default; + // Typedefs + using CSSRuleIndex = size_t; - public: - std::string cssText; - inline std::shared_ptr parentRule() const - { - return parentRule_.lock(); - } - inline std::shared_ptr parentStyleSheet() const + enum class CSSRuleType { - return parentStyleSheet_.lock(); - } + kUnknownRule = 0x00, + kStyleRule, + kImportRule = 0x03, + kMediaRule, + kFontFaceRule, + kPageRule, + kKeyframesRule, + kNamespaceRule = 0x0a, + kCounterStyleRule, + kSupportsRule, + kFontFeatureValuesRule = 0x0e, + }; - private: - std::weak_ptr parentRule_; - std::weak_ptr parentStyleSheet_; - }; + class CSSRule + { + public: + CSSRule() = default; + virtual ~CSSRule() = default; - class CSSRuleList : std::vector> - { - friend class CSSStyleSheet; + public: + std::string cssText; + inline std::shared_ptr parentRule() const + { + return parentRule_.lock(); + } + inline std::shared_ptr parentStyleSheet() const + { + return parentStyleSheet_.lock(); + } - public: - CSSRuleList() = default; + private: + std::weak_ptr parentRule_; + std::weak_ptr parentStyleSheet_; + }; - public: - size_t length() const + class CSSRuleList : std::vector> { - return size(); - } + friend class CSSStyleSheet; - public: - CSSRule &item(CSSRuleIndex index) const - { - return *at(index); - } - auto begin() - { - return std::vector>::begin(); - } - auto end() - { - return std::vector>::end(); - } - auto begin() const - { - return std::vector>::begin(); - } - auto end() const - { - return std::vector>::end(); - } + public: + CSSRuleList() = default; + + public: + size_t length() const + { + return size(); + } + + public: + CSSRule &item(CSSRuleIndex index) const + { + return *at(index); + } + auto begin() + { + return std::vector>::begin(); + } + auto end() + { + return std::vector>::end(); + } + auto begin() const + { + return std::vector>::begin(); + } + auto end() const + { + return std::vector>::end(); + } - private: - CSSRuleIndex insert(crates::css2::stylesheets::CssRule &inner); - }; -} + private: + CSSRuleIndex insert(crates::css2::stylesheets::CssRule &inner); + }; + } +} // namespace endor diff --git a/src/client/cssom/css_style_declaration.cpp b/src/client/cssom/css_style_declaration.cpp index 74513e2fe..c72cf18a5 100644 --- a/src/client/cssom/css_style_declaration.cpp +++ b/src/client/cssom/css_style_declaration.cpp @@ -2,70 +2,73 @@ #include #include "./css_style_declaration.hpp" -namespace client_cssom +namespace endor { - using namespace std; - - bool CSSStyleDeclaration::equals(const CSSStyleDeclaration &other) const + namespace client_cssom { - if (length() != other.length()) // quick check if the length is different - return false; - return cssText() == other.cssText(); // TODO: support more quick check? - } + using namespace std; - bool CSSStyleDeclaration::update(const CSSStyleDeclaration &other, bool omitIfPresent) - { - bool isChanged = false; - for (size_t i = 0; i < other.length(); i++) + bool CSSStyleDeclaration::equals(const CSSStyleDeclaration &other) const { - auto name = other.item(i); - if (hasProperty(name)) + if (length() != other.length()) // quick check if the length is different + return false; + return cssText() == other.cssText(); // TODO: support more quick check? + } + + bool CSSStyleDeclaration::update(const CSSStyleDeclaration &other, bool omitIfPresent) + { + bool isChanged = false; + for (size_t i = 0; i < other.length(); i++) { - bool isSelfPropImportant = getPropertyPriority(name) == CSSPropertyPriority::Important; - bool isOtherPropImportant = other.getPropertyPriority(name) == CSSPropertyPriority::Important; - if (isSelfPropImportant && isOtherPropImportant) + auto name = other.item(i); + if (hasProperty(name)) { - isSelfPropImportant = false; - isOtherPropImportant = true; - } + bool isSelfPropImportant = getPropertyPriority(name) == CSSPropertyPriority::Important; + bool isOtherPropImportant = other.getPropertyPriority(name) == CSSPropertyPriority::Important; + if (isSelfPropImportant && isOtherPropImportant) + { + isSelfPropImportant = false; + isOtherPropImportant = true; + } - // Omit this property if it is already present, or if the other one is not important - if (omitIfPresent && !isOtherPropImportant) - continue; + // Omit this property if it is already present, or if the other one is not important + if (omitIfPresent && !isOtherPropImportant) + continue; - // Skip the property if the self one is important - if (isSelfPropImportant) - continue; + // Skip the property if the self one is important + if (isSelfPropImportant) + continue; - auto value = other.getPropertyValue(name); - if (getPropertyValue(name) != value) + auto value = other.getPropertyValue(name); + if (getPropertyValue(name) != value) + { + setProperty(name, + value, + isOtherPropImportant ? CSSPropertyPriority::Important : CSSPropertyPriority::Normal); + isChanged = true; + } + } + else { - setProperty(name, - value, - isOtherPropImportant ? CSSPropertyPriority::Important : CSSPropertyPriority::Normal); + setProperty(name, other.getPropertyValue(name), other.getPropertyPriority(name)); isChanged = true; } } - else - { - setProperty(name, other.getPropertyValue(name), other.getPropertyPriority(name)); - isChanged = true; - } + return isChanged; } - return isChanged; - } - bool CSSStyleDeclaration::setCssText(const std::string &cssText) - { - auto pdb = PropertyDeclarationBlock::ParseStyleDeclaration(cssText); - if (pdb == nullptr) - return false; - pdb_ = std::move(pdb); - cachedCssText_ = pdb_->cssText(); + bool CSSStyleDeclaration::setCssText(const std::string &cssText) + { + auto pdb = PropertyDeclarationBlock::ParseStyleDeclaration(cssText); + if (pdb == nullptr) + return false; + pdb_ = std::move(pdb); + cachedCssText_ = pdb_->cssText(); - // Notify the property changed callback if it is set - if (propertyChangedCallback_) - propertyChangedCallback_(cachedCssText_.value()); - return true; + // Notify the property changed callback if it is set + if (propertyChangedCallback_) + propertyChangedCallback_(cachedCssText_.value()); + return true; + } } -} +} // namespace endor diff --git a/src/client/cssom/css_style_declaration.hpp b/src/client/cssom/css_style_declaration.hpp index e939c5794..fc70ad875 100644 --- a/src/client/cssom/css_style_declaration.hpp +++ b/src/client/cssom/css_style_declaration.hpp @@ -9,120 +9,122 @@ #include #include -namespace client_cssom +namespace endor { - template - concept is_property_value = std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v; - - enum class CSSPropertyPriority + namespace client_cssom { - Normal, - Important, - }; + template + concept is_property_value = std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; - struct CSSProperty - { - std::string name; - std::string value; - CSSPropertyPriority priority = CSSPropertyPriority::Normal; - }; + enum class CSSPropertyPriority + { + Normal, + Important, + }; + + struct CSSProperty + { + std::string name; + std::string value; + CSSPropertyPriority priority = CSSPropertyPriority::Normal; + }; - /** + /** * The `CSSStyleDeclaration` interface represents an object that is a CSS declaration block, and exposes style information * and various style-related methods and properties. */ - class CSSStyleDeclaration : public scripting_base::JSObjectHolder - { - using PropertyDeclarationBlock = crates::css2::properties::PropertyDeclarationBlock; - - public: - CSSStyleDeclaration() - : pdb_(std::make_shared()) - , cachedCssText_(std::nullopt) - { - } - CSSStyleDeclaration(const std::string &cssText) - : pdb_(PropertyDeclarationBlock::ParseStyleDeclaration(cssText)) - , cachedCssText_(std::nullopt) - { - } - CSSStyleDeclaration(std::unique_ptr pdb) - : pdb_(std::move(pdb)) - , cachedCssText_(std::nullopt) + class CSSStyleDeclaration : public scripting_base::JSObjectHolder { - } - CSSStyleDeclaration(const CSSStyleDeclaration &other) - : pdb_(other.pdb_) - , cachedCssText_(other.cachedCssText_) - { - } + using PropertyDeclarationBlock = crates::css2::properties::PropertyDeclarationBlock; - public: - /** + public: + CSSStyleDeclaration() + : pdb_(std::make_shared()) + , cachedCssText_(std::nullopt) + { + } + CSSStyleDeclaration(const std::string &cssText) + : pdb_(PropertyDeclarationBlock::ParseStyleDeclaration(cssText)) + , cachedCssText_(std::nullopt) + { + } + CSSStyleDeclaration(std::unique_ptr pdb) + : pdb_(std::move(pdb)) + , cachedCssText_(std::nullopt) + { + } + CSSStyleDeclaration(const CSSStyleDeclaration &other) + : pdb_(other.pdb_) + , cachedCssText_(other.cachedCssText_) + { + } + + public: + /** * Textual representation of the declaration block, if and only if it is exposed via `HTMLElement.style`. Setting this * attribute changes the inline style. * * @returns The textual representation of the declaration block. */ - inline std::string cssText() const - { - if (!cachedCssText_.has_value()) - cachedCssText_ = pdb_->cssText(); - return cachedCssText_.value(); - } - /** + inline std::string cssText() const + { + if (!cachedCssText_.has_value()) + cachedCssText_ = pdb_->cssText(); + return cachedCssText_.value(); + } + /** * @returns The number of properties. */ - size_t length() const - { - return pdb_->size(); - } + size_t length() const + { + return pdb_->size(); + } - public: - /** + public: + /** * Custom the conversion to `LayoutStyle`. */ - bool operator==(const CSSStyleDeclaration &other) const - { - return equals(other); - } - bool operator!=(const CSSStyleDeclaration &other) const - { - return !equals(other); - } - /** + bool operator==(const CSSStyleDeclaration &other) const + { + return equals(other); + } + bool operator!=(const CSSStyleDeclaration &other) const + { + return !equals(other); + } + /** * Custom the conversion to `std::string`. */ - friend std::ostream &operator<<(std::ostream &os, const CSSStyleDeclaration &style) - { - os << "CSSStyleDeclaration {" << style.cssText() << "}" << std::endl; - return os; - } + friend std::ostream &operator<<(std::ostream &os, const CSSStyleDeclaration &style) + { + os << "CSSStyleDeclaration {" << style.cssText() << "}" << std::endl; + return os; + } - public: - /** + public: + /** * Check if the declaration block is equal to another one. * * @param other The other CSSStyleDeclaration to compare. * @returns Whether the declaration block is equal to the other one. */ - bool equals(const CSSStyleDeclaration &other) const; + bool equals(const CSSStyleDeclaration &other) const; - /** + /** * Update the declaration block with another one. * * If the second argument `omitIfPresent` is set to `true`, it means that only these properties will be updated if they @@ -132,155 +134,157 @@ namespace client_cssom * @param omitIfPresent Whether to omit the property if it is already present. * @returns Whether the declaration block is updated. */ - bool update(const CSSStyleDeclaration &other, bool omitIfPresent = false); + bool update(const CSSStyleDeclaration &other, bool omitIfPresent = false); - /** + /** * Set the CSS text of this block. * * @param cssText The CSS text to set. * @returns Whether the CSS text is set successfully. */ - bool setCssText(const std::string &cssText); + bool setCssText(const std::string &cssText); - /** + /** * Get the property name at the given index. * * @param index The index of the CSS property. * @returns The property name. */ - inline std::string item(size_t index) const - { - return pdb_->item(index); - } + inline std::string item(size_t index) const + { + return pdb_->item(index); + } - /** + /** * Get the optional priority, "important". * * @param propertyName The name of the CSS property. * @returns The optional priority, "important". */ - inline CSSPropertyPriority getPropertyPriority(const std::string &propertyName) const - { - if (pdb_->isPropertyImportant(propertyName)) - return CSSPropertyPriority::Important; - else - return CSSPropertyPriority::Normal; - } + inline CSSPropertyPriority getPropertyPriority(const std::string &propertyName) const + { + if (pdb_->isPropertyImportant(propertyName)) + return CSSPropertyPriority::Important; + else + return CSSPropertyPriority::Normal; + } - /** + /** * Get the property value given a property name. * * @param propertyName The name of the CSS property. * @param defaultValue The default value to return if the property is not set. * @returns The property value. */ - inline std::string getPropertyValue(const std::string &propertyName, std::string defaultValue = "") const - { - auto value = pdb_->getProperty(propertyName); - return value != "" ? value : defaultValue; - } + inline std::string getPropertyValue(const std::string &propertyName, std::string defaultValue = "") const + { + auto value = pdb_->getProperty(propertyName); + return value != "" ? value : defaultValue; + } - /** + /** * Get the property value as a specific type given a property name. * * @param propertyName The name of the CSS property. * @returns The property value as a specific type such as `Dimension`. */ - template - requires is_property_value || - std::is_same_v || - std::is_integral_v - T getPropertyValueAs(const std::string &propertyName) const - { - using namespace crates::layout2::styles; + template + requires is_property_value || + std::is_same_v || + std::is_integral_v + T getPropertyValueAs(const std::string &propertyName) + const + { + using namespace crates::layout2::styles; - // float, int, etc. - const auto &value = getPropertyValue(propertyName); - if constexpr (std::is_same_v) - return value != "" ? std::stof(value) : 0.0f; - if constexpr (std::is_integral_v) - return value != "" ? std::stoi(value) : 0; + // float, int, etc. + const auto &value = getPropertyValue(propertyName); + if constexpr (std::is_same_v) + return value != "" ? std::stof(value) : 0.0f; + if constexpr (std::is_integral_v) + return value != "" ? std::stoi(value) : 0; - // CSSOM types - if constexpr (is_property_value) - return T(value); + // CSSOM types + if constexpr (is_property_value) + return T(value); - // NOTE: unreachable - assert(false); - return T(); - } + // NOTE: unreachable + assert(false); + return T(); + } - /** + /** * Check if a property is set. * * @param propertyName The name of the CSS property. * @returns Whether the property is set. */ - inline bool hasProperty(const std::string &propertyName) const - { - return pdb_->getProperty(propertyName) != ""; - } + inline bool hasProperty(const std::string &propertyName) const + { + return pdb_->getProperty(propertyName) != ""; + } - /** + /** * Set a property value and priority within the declaration block. * * @param propertyName The name of the CSS property. * @param value The new value of the property. * @param priority The optional priority, "important". */ - inline void setProperty(const std::string &propertyName, - const std::string &value, - CSSPropertyPriority priority = CSSPropertyPriority::Normal) - { - pdb_->setProperty(propertyName, value, priority == CSSPropertyPriority::Important); - cachedCssText_ = std::nullopt; - if (propertyChangedCallback_ != nullptr) - propertyChangedCallback_(propertyName); - } + inline void setProperty(const std::string &propertyName, + const std::string &value, + CSSPropertyPriority priority = CSSPropertyPriority::Normal) + { + pdb_->setProperty(propertyName, value, priority == CSSPropertyPriority::Important); + cachedCssText_ = std::nullopt; + if (propertyChangedCallback_ != nullptr) + propertyChangedCallback_(propertyName); + } - /** + /** * Set a property value and priority within the declaration block if the property is not already set. * * @param propertyName The name of the CSS property. * @param value The new value of the property. * @param priority The optional priority, "important". */ - inline void setPropertyIfNotPresent(const std::string &propertyName, - const std::string &value, - CSSPropertyPriority priority = CSSPropertyPriority::Normal) - { - if (!hasProperty(propertyName)) - setProperty(propertyName, value, priority); - } + inline void setPropertyIfNotPresent(const std::string &propertyName, + const std::string &value, + CSSPropertyPriority priority = CSSPropertyPriority::Normal) + { + if (!hasProperty(propertyName)) + setProperty(propertyName, value, priority); + } - /** + /** * Remove a property from the declaration block. * * @param propertyName The name of the CSS property. * @returns The removed property value. */ - std::string removeProperty(const std::string &propertyName) - { - auto value = pdb_->removeProperty(propertyName); - cachedCssText_ = std::nullopt; - if (propertyChangedCallback_ != nullptr) - propertyChangedCallback_(propertyName); - return value; - } + std::string removeProperty(const std::string &propertyName) + { + auto value = pdb_->removeProperty(propertyName); + cachedCssText_ = std::nullopt; + if (propertyChangedCallback_ != nullptr) + propertyChangedCallback_(propertyName); + return value; + } - /** + /** * Set the callback function when a property is changed. * * @param callback The callback function when a property is changed. */ - inline void setPropertyChangedCallback(const std::function &callback) - { - propertyChangedCallback_ = callback; - } + inline void setPropertyChangedCallback(const std::function &callback) + { + propertyChangedCallback_ = callback; + } - private: - std::shared_ptr pdb_; - std::function propertyChangedCallback_ = nullptr; - mutable std::optional cachedCssText_ = std::nullopt; - }; -} + private: + std::shared_ptr pdb_; + std::function propertyChangedCallback_ = nullptr; + mutable std::optional cachedCssText_ = std::nullopt; + }; + } +} // namespace endor diff --git a/src/client/cssom/css_stylesheet.cpp b/src/client/cssom/css_stylesheet.cpp index 56d22f3ba..b67fd3fcf 100644 --- a/src/client/cssom/css_stylesheet.cpp +++ b/src/client/cssom/css_stylesheet.cpp @@ -1,38 +1,41 @@ #include #include "./css_stylesheet.hpp" -namespace client_cssom +namespace endor { - using namespace std; - using namespace crates::css; - - CSSStyleSheet::CSSStyleSheet(optional init) - : StyleSheet() - , init_(init.value_or(CSSStyleSheetInit{})) - , cssRules_(make_unique()) + namespace client_cssom { - } + using namespace std; + using namespace crates::css; - void CSSStyleSheet::deleteRule(CSSRuleIndex index) - { - cssRules_->erase(cssRules_->begin() + index); - } + CSSStyleSheet::CSSStyleSheet(optional init) + : StyleSheet() + , init_(init.value_or(CSSStyleSheetInit{})) + , cssRules_(make_unique()) + { + } - CSSRuleIndex CSSStyleSheet::insertRule(const string &ruleText, CSSRuleIndex index) - { - return index; - } + void CSSStyleSheet::deleteRule(CSSRuleIndex index) + { + cssRules_->erase(cssRules_->begin() + index); + } - void CSSStyleSheet::replace(const string &cssText) - { - replaceSync(cssText); - } + CSSRuleIndex CSSStyleSheet::insertRule(const string &ruleText, CSSRuleIndex index) + { + return index; + } - void CSSStyleSheet::replaceSync(const string &cssText) - { - auto stylesheet = crates::css2::parsing::CSSParser().parseStylesheet(cssText, ""); - auto &cssRules = stylesheet.rules(); - for (auto cssRule : cssRules) - cssRules_->insert(*cssRule); + void CSSStyleSheet::replace(const string &cssText) + { + replaceSync(cssText); + } + + void CSSStyleSheet::replaceSync(const string &cssText) + { + auto stylesheet = crates::css2::parsing::CSSParser().parseStylesheet(cssText, ""); + auto &cssRules = stylesheet.rules(); + for (auto cssRule : cssRules) + cssRules_->insert(*cssRule); + } } -} +} // namespace endor diff --git a/src/client/cssom/css_stylesheet.hpp b/src/client/cssom/css_stylesheet.hpp index 1b94ef173..a38fdb2b1 100644 --- a/src/client/cssom/css_stylesheet.hpp +++ b/src/client/cssom/css_stylesheet.hpp @@ -7,38 +7,41 @@ #include "./css_rule.hpp" #include "./rules/all.hpp" -namespace client_cssom +namespace endor { - struct CSSStyleSheetInit + namespace client_cssom { - std::string baseURL; - bool disabled; - }; - - class CSSStyleSheet : public StyleSheet - { - public: - CSSStyleSheet(std::optional init = std::nullopt); - - public: - const CSSRuleList &cssRules() const + struct CSSStyleSheetInit { - return *cssRules_; - } - std::shared_ptr ownerRule() const + std::string baseURL; + bool disabled; + }; + + class CSSStyleSheet : public StyleSheet { - return ownerRule_.lock(); - } + public: + CSSStyleSheet(std::optional init = std::nullopt); + + public: + const CSSRuleList &cssRules() const + { + return *cssRules_; + } + std::shared_ptr ownerRule() const + { + return ownerRule_.lock(); + } - public: - void deleteRule(CSSRuleIndex index); - CSSRuleIndex insertRule(const std::string &rule, CSSRuleIndex index = 0); - void replace(const std::string &cssText); - void replaceSync(const std::string &cssText); + public: + void deleteRule(CSSRuleIndex index); + CSSRuleIndex insertRule(const std::string &rule, CSSRuleIndex index = 0); + void replace(const std::string &cssText); + void replaceSync(const std::string &cssText); - private: - CSSStyleSheetInit init_; - std::unique_ptr cssRules_; - std::weak_ptr ownerRule_; - }; -} + private: + CSSStyleSheetInit init_; + std::unique_ptr cssRules_; + std::weak_ptr ownerRule_; + }; + } +} // namespace endor diff --git a/src/client/cssom/layout.cpp b/src/client/cssom/layout.cpp index 766b1cfb9..9ee17c641 100644 --- a/src/client/cssom/layout.cpp +++ b/src/client/cssom/layout.cpp @@ -1,21 +1,24 @@ #include "./layout.hpp" -namespace client_cssom +namespace endor { - void Layout::merge(const Layout &layout) + namespace client_cssom { - // use child width and height - width_ = layout.width_; - height_ = layout.height_; + void Layout::merge(const Layout &layout) + { + // use child width and height + width_ = layout.width_; + height_ = layout.height_; - // add child x, y, and depth - x_ += layout.x_; - y_ += layout.y_; - depth_ += layout.depth_; - } + // add child x, y, and depth + x_ += layout.x_; + y_ += layout.y_; + depth_ += layout.depth_; + } - bool Layout::needsResize(float width, float height) const - { - return width_ != width || height_ != height; + bool Layout::needsResize(float width, float height) const + { + return width_ != width || height_ != height; + } } -} +} // namespace endor diff --git a/src/client/cssom/layout.hpp b/src/client/cssom/layout.hpp index 3aec8a732..06568cf1e 100644 --- a/src/client/cssom/layout.hpp +++ b/src/client/cssom/layout.hpp @@ -4,75 +4,78 @@ #include #include -namespace client_cssom +namespace endor { - class Layout : public crates::layout2::Layout + namespace client_cssom { - using crates::layout2::Layout::Layout; - - public: - static Layout Merge(const std::optional &parent, const Layout &child) + class Layout : public crates::layout2::Layout { - if (!parent.has_value()) - return child; - else + using crates::layout2::Layout::Layout; + + public: + static Layout Merge(const std::optional &parent, const Layout &child) { - Layout layout = parent.value(); - layout.merge(child); - return layout; + if (!parent.has_value()) + return child; + else + { + Layout layout = parent.value(); + layout.merge(child); + return layout; + } } - } - public: - Layout() - : crates::layout2::Layout() - , depth_(0.0f) - { - } - Layout(crates::layout2::Layout &&layout, float depth = 0.0f) - : crates::layout2::Layout(layout) - , depth_(depth) - { - } + public: + Layout() + : crates::layout2::Layout() + , depth_(0.0f) + { + } + Layout(crates::layout2::Layout &&layout, float depth = 0.0f) + : crates::layout2::Layout(layout) + , depth_(depth) + { + } - public: - inline float depth() const - { - return depth_; - } - inline glm::vec2 xy() const - { - return {left() + offsetX_, - top() + offsetY_}; - } - inline glm::vec3 xyz() const - { - return {left() + offsetX_, - top() + offsetY_, - depth_}; - } - inline void setOffset(float x, float y, float z = 0.0f) - { - offsetX_ = x; - offsetY_ = y; - offsetZ_ = z; - } + public: + inline float depth() const + { + return depth_; + } + inline glm::vec2 xy() const + { + return {left() + offsetX_, + top() + offsetY_}; + } + inline glm::vec3 xyz() const + { + return {left() + offsetX_, + top() + offsetY_, + depth_}; + } + inline void setOffset(float x, float y, float z = 0.0f) + { + offsetX_ = x; + offsetY_ = y; + offsetZ_ = z; + } - public: - void merge(const Layout &layout); - /** + public: + void merge(const Layout &layout); + /** * Check if the layout needs to be resized with the specified width and height. * * @param width The width to check. * @param height The height to check. * @returns Whether the layout needs to be resized. */ - bool needsResize(float width, float height) const; + bool needsResize(float width, float height) const; - private: - float depth_ = 0.0f; - float offsetX_ = 0.0f; - float offsetY_ = 0.0f; - float offsetZ_ = 0.0f; - }; -} + private: + float depth_ = 0.0f; + float offsetX_ = 0.0f; + float offsetY_ = 0.0f; + float offsetZ_ = 0.0f; + }; + } +} // namespace endor diff --git a/src/client/cssom/media.hpp b/src/client/cssom/media.hpp index b75df0025..3391edd31 100644 --- a/src/client/cssom/media.hpp +++ b/src/client/cssom/media.hpp @@ -7,82 +7,85 @@ #include "./style_traits.hpp" -namespace client_cssom +namespace endor { - class MediaType : public Parse + namespace client_cssom { - private: - enum Tag : uint8_t + class MediaType : public Parse { - kScreen, - kPrint, - }; + private: + enum Tag : uint8_t + { + kScreen, + kPrint, + }; - public: - static MediaType Screen() - { - return MediaType(kScreen); - } - static MediaType Print() - { - return MediaType(kPrint); - } + public: + static MediaType Screen() + { + return MediaType(kScreen); + } + static MediaType Print() + { + return MediaType(kPrint); + } - public: - MediaType() = default; + public: + MediaType() = default; - private: - MediaType(Tag tag) - : tag_(tag) - { - } + private: + MediaType(Tag tag) + : tag_(tag) + { + } - private: - bool parse(const std::string &input) override - { - if (input == "screen") - tag_ = kScreen; - else if (input == "print") - tag_ = kPrint; - return true; - } - - private: - Tag tag_ = kScreen; - }; + private: + bool parse(const std::string &input) override + { + if (input == "screen") + tag_ = kScreen; + else if (input == "print") + tag_ = kPrint; + return true; + } - class MediaList : std::vector - { - public: - MediaList() = default; + private: + Tag tag_ = kScreen; + }; - public: - inline std::string mediaText() const; - inline size_t length() const + class MediaList : std::vector { - return size(); - } + public: + MediaList() = default; - public: - inline std::optional operator[](size_t index) const - { - return item(index); - } + public: + inline std::string mediaText() const; + inline size_t length() const + { + return size(); + } - public: - inline std::optional item(size_t index) const - { - if (index < size()) - return at(index); - return std::nullopt; - } - inline void appendMedium(const std::string &medium) - { - push_back(medium); - } - inline void deleteMedium(const std::string &medium) - { - erase(std::remove(begin(), end(), medium), end()); - } - }; -} + public: + inline std::optional operator[](size_t index) const + { + return item(index); + } + + public: + inline std::optional item(size_t index) const + { + if (index < size()) + return at(index); + return std::nullopt; + } + inline void appendMedium(const std::string &medium) + { + push_back(medium); + } + inline void deleteMedium(const std::string &medium) + { + erase(std::remove(begin(), end(), medium), end()); + } + }; + } +} // namespace endor diff --git a/src/client/cssom/media_queries.hpp b/src/client/cssom/media_queries.hpp index fda088112..290fa2d77 100644 --- a/src/client/cssom/media_queries.hpp +++ b/src/client/cssom/media_queries.hpp @@ -6,137 +6,140 @@ #include "./media.hpp" #include "./units.hpp" -namespace client_cssom +namespace endor { - /** + namespace client_cssom + { + /** * The CSS device context. */ - class Device - { - public: - Device() = default; + class Device + { + public: + Device() = default; - // Static device dimensions for device-width/device-height support - static constexpr float DeviceWidth = ScreenWidth; - static constexpr float DeviceHeight = ScreenHeight; - static constexpr float DeviceDepth = VolumeDepth; + // Static device dimensions for device-width/device-height support + static constexpr float DeviceWidth = ScreenWidth; + static constexpr float DeviceHeight = ScreenHeight; + static constexpr float DeviceDepth = VolumeDepth; - public: - const MediaType &mediaType() const - { - return media_type_; - } - const glm::vec3 &viewportSize() const - { - return viewport_size_; - } - const float devicePixelRatio() const - { - return device_pixel_ratio_; - } - float &devicePixelRatio() - { - return device_pixel_ratio_; - } + public: + const MediaType &mediaType() const + { + return media_type_; + } + const glm::vec3 &viewportSize() const + { + return viewport_size_; + } + const float devicePixelRatio() const + { + return device_pixel_ratio_; + } + float &devicePixelRatio() + { + return device_pixel_ratio_; + } - float rootFontSize() const - { - return root_font_size_; - } - float rootLineHeight() const - { - return root_line_height_; - } + float rootFontSize() const + { + return root_font_size_; + } + float rootLineHeight() const + { + return root_line_height_; + } - // Viewport scaling properties - float initialScale() const - { - return initial_scale_; - } - float minimumScale() const - { - return minimum_scale_; - } - float maximumScale() const - { - return maximum_scale_; - } - bool userScalable() const - { - return user_scalable_; - } + // Viewport scaling properties + float initialScale() const + { + return initial_scale_; + } + float minimumScale() const + { + return minimum_scale_; + } + float maximumScale() const + { + return maximum_scale_; + } + bool userScalable() const + { + return user_scalable_; + } - void setInitialScale(float scale) - { - initial_scale_ = std::clamp(scale, 0.1f, 10.0f); - // Apply initial scale to device pixel ratio - device_pixel_ratio_ = initial_scale_; - } - void setMinimumScale(float scale) - { - minimum_scale_ = std::clamp(scale, 0.1f, 10.0f); - } - void setMaximumScale(float scale) - { - maximum_scale_ = std::clamp(scale, 0.1f, 10.0f); - } - void setUserScalable(bool scalable) - { - user_scalable_ = scalable; - } + void setInitialScale(float scale) + { + initial_scale_ = std::clamp(scale, 0.1f, 10.0f); + // Apply initial scale to device pixel ratio + device_pixel_ratio_ = initial_scale_; + } + void setMinimumScale(float scale) + { + minimum_scale_ = std::clamp(scale, 0.1f, 10.0f); + } + void setMaximumScale(float scale) + { + maximum_scale_ = std::clamp(scale, 0.1f, 10.0f); + } + void setUserScalable(bool scalable) + { + user_scalable_ = scalable; + } - /** + /** * Update viewport size from viewport meta tag */ - void setViewportSize(float width, float height) - { - viewport_size_.x = width; - viewport_size_.y = height; - // Keep depth unchanged - } + void setViewportSize(float width, float height) + { + viewport_size_.x = width; + viewport_size_.y = height; + // Keep depth unchanged + } - /** + /** * Update viewport width only, with device-width support */ - void setViewportWidth(float width, bool use_device_width = false) - { - if (use_device_width) + void setViewportWidth(float width, bool use_device_width = false) { - viewport_size_.x = DeviceWidth; + if (use_device_width) + { + viewport_size_.x = DeviceWidth; + } + else + { + viewport_size_.x = width; + } } - else - { - viewport_size_.x = width; - } - } - /** + /** * Update viewport height only, with device-height support */ - void setViewportHeight(float height, bool use_device_height = false) - { - if (use_device_height) + void setViewportHeight(float height, bool use_device_height = false) { - viewport_size_.y = DeviceHeight; - } - else - { - viewport_size_.y = height; + if (use_device_height) + { + viewport_size_.y = DeviceHeight; + } + else + { + viewport_size_.y = height; + } } - } - private: - MediaType media_type_ = MediaType::Screen(); - glm::vec3 viewport_size_ = {DeviceWidth, DeviceHeight, DeviceDepth}; - float device_pixel_ratio_ = DevicePixelRatio; + private: + MediaType media_type_ = MediaType::Screen(); + glm::vec3 viewport_size_ = {DeviceWidth, DeviceHeight, DeviceDepth}; + float device_pixel_ratio_ = DevicePixelRatio; - // Viewport scaling properties - float initial_scale_ = 1.0f; - float minimum_scale_ = 0.1f; - float maximum_scale_ = 10.0f; - bool user_scalable_ = true; + // Viewport scaling properties + float initial_scale_ = 1.0f; + float minimum_scale_ = 0.1f; + float maximum_scale_ = 10.0f; + bool user_scalable_ = true; - float root_font_size_ = 16.0f; - float root_line_height_ = 1.0f; - }; -} + float root_font_size_ = 16.0f; + float root_line_height_ = 1.0f; + }; + } +} // namespace endor diff --git a/src/client/cssom/parsers/css_filter_parser.cpp b/src/client/cssom/parsers/css_filter_parser.cpp index 904a918ae..d5ec1d12d 100644 --- a/src/client/cssom/parsers/css_filter_parser.cpp +++ b/src/client/cssom/parsers/css_filter_parser.cpp @@ -4,643 +4,646 @@ #include #include -namespace client_cssom::css_filter_parser +namespace endor { - using namespace std; - using namespace css_value_tokenizer; - - CSSFilterParser::CSSFilterParser(const string &input) - : input_(input) - , tokenizer_(input) - , current_token_index_(0) - , is_valid_(false) + namespace client_cssom::css_filter_parser { - tokens_ = tokenizer_.tokenize(); - } - - vector CSSFilterParser::parse() - { - vector functions; - current_token_index_ = 0; - is_valid_ = true; - error_message_.clear(); + using namespace std; + using namespace css_value_tokenizer; - // Handle 'none' case - if (tokens_.size() == 1 && tokens_[0].type == TokenType::kIdentifier && tokens_[0].value == "none") + CSSFilterParser::CSSFilterParser(const string &input) + : input_(input) + , tokenizer_(input) + , current_token_index_(0) + , is_valid_(false) { - return functions; // Return empty list for 'none' + tokens_ = tokenizer_.tokenize(); } - // Parse filter functions - while (!isAtEnd()) + vector CSSFilterParser::parse() { - auto func = parseFilterFunction(); - if (!func.has_value()) + vector functions; + current_token_index_ = 0; + is_valid_ = true; + error_message_.clear(); + + // Handle 'none' case + if (tokens_.size() == 1 && tokens_[0].type == TokenType::kIdentifier && tokens_[0].value == "none") { - is_valid_ = false; - return functions; + return functions; // Return empty list for 'none' } - functions.push_back(func.value()); - skipSeparators(); - } + // Parse filter functions + while (!isAtEnd()) + { + auto func = parseFilterFunction(); + if (!func.has_value()) + { + is_valid_ = false; + return functions; + } + functions.push_back(func.value()); - return functions; - } + skipSeparators(); + } - optional CSSFilterParser::parseFilterFunction() - { - if (isAtEnd() || currentToken().type != TokenType::kFunction) - { - setError("Expected filter function"); - return nullopt; + return functions; } - string function_name = currentToken().value; - FilterFunctionType type = getFunctionType(function_name); - - if (type == FilterFunctionType::kNone) + optional CSSFilterParser::parseFilterFunction() { - setError("Unknown filter function: " + function_name); - return nullopt; - } + if (isAtEnd() || currentToken().type != TokenType::kFunction) + { + setError("Expected filter function"); + return nullopt; + } - advance(); // Consume function token + string function_name = currentToken().value; + FilterFunctionType type = getFunctionType(function_name); - // Parse based on function type - switch (type) - { - case FilterFunctionType::kBlur: - return parseBlur(); - case FilterFunctionType::kBrightness: - return parseBrightness(); - case FilterFunctionType::kContrast: - return parseContrast(); - case FilterFunctionType::kDropShadow: - return parseDropShadow(); - case FilterFunctionType::kGrayscale: - return parseGrayscale(); - case FilterFunctionType::kHueRotate: - return parseHueRotate(); - case FilterFunctionType::kInvert: - return parseInvert(); - case FilterFunctionType::kOpacity: - return parseOpacity(); - case FilterFunctionType::kSaturate: - return parseSaturate(); - case FilterFunctionType::kSepia: - return parseSepia(); - default: - setError("Unsupported filter function"); - return nullopt; - } - } + if (type == FilterFunctionType::kNone) + { + setError("Unknown filter function: " + function_name); + return nullopt; + } - optional CSSFilterParser::parseBlur() - { - FilterFunction func(FilterFunctionType::kBlur); + advance(); // Consume function token - // blur() with no arguments defaults to 0px - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(0.0); - func.units.push_back("px"); - return func; + // Parse based on function type + switch (type) + { + case FilterFunctionType::kBlur: + return parseBlur(); + case FilterFunctionType::kBrightness: + return parseBrightness(); + case FilterFunctionType::kContrast: + return parseContrast(); + case FilterFunctionType::kDropShadow: + return parseDropShadow(); + case FilterFunctionType::kGrayscale: + return parseGrayscale(); + case FilterFunctionType::kHueRotate: + return parseHueRotate(); + case FilterFunctionType::kInvert: + return parseInvert(); + case FilterFunctionType::kOpacity: + return parseOpacity(); + case FilterFunctionType::kSaturate: + return parseSaturate(); + case FilterFunctionType::kSepia: + return parseSepia(); + default: + setError("Unsupported filter function"); + return nullopt; + } } - // Parse length value - double value; - string unit; - if (!consumeLength(value, unit)) + optional CSSFilterParser::parseBlur() { - setError("Expected length value for blur()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kBlur); - func.values.push_back(value); - func.units.push_back(unit); + // blur() with no arguments defaults to 0px + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(0.0); + func.units.push_back("px"); + return func; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for blur()"); - return nullopt; - } + // Parse length value + double value; + string unit; + if (!consumeLength(value, unit)) + { + setError("Expected length value for blur()"); + return nullopt; + } - return func; - } + func.values.push_back(value); + func.units.push_back(unit); - optional CSSFilterParser::parseBrightness() - { - FilterFunction func(FilterFunctionType::kBrightness); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for blur()"); + return nullopt; + } - // brightness() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) + optional CSSFilterParser::parseBrightness() { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) - { - func.values.push_back(value); - func.units.push_back(unit); - } - else - { - setError("Expected number or percentage for brightness()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kBrightness); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for brightness()"); - return nullopt; - } + // brightness() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; + } - return func; - } + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) + { + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + setError("Expected number or percentage for brightness()"); + return nullopt; + } - optional CSSFilterParser::parseContrast() - { - FilterFunction func(FilterFunctionType::kContrast); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for brightness()"); + return nullopt; + } - // contrast() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) - { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) + optional CSSFilterParser::parseContrast() { - func.values.push_back(value); - func.units.push_back(unit); - } - else - { - setError("Expected number or percentage for contrast()"); - return nullopt; + FilterFunction func(FilterFunctionType::kContrast); + + // contrast() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; + } + + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) + { + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + setError("Expected number or percentage for contrast()"); + return nullopt; + } + + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for contrast()"); + return nullopt; + } + + return func; } - if (!consumeToken(TokenType::kRightParen)) + optional CSSFilterParser::parseDropShadow() { - setError("Expected closing parenthesis for contrast()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kDropShadow); - return func; - } + // For drop-shadow, we'll store the raw value since it's complex + // drop-shadow(offset-x offset-y blur-radius color) + string raw_params; + int paren_depth = 0; - optional CSSFilterParser::parseDropShadow() - { - FilterFunction func(FilterFunctionType::kDropShadow); + while (!isAtEnd()) + { + const auto &token = currentToken(); + if (token.type == TokenType::kLeftParen) + { + paren_depth++; + raw_params += token.value; + } + else if (token.type == TokenType::kRightParen) + { + if (paren_depth == 0) + { + advance(); // Consume the closing paren + break; + } + paren_depth--; + raw_params += token.value; + } + else + { + if (!raw_params.empty()) + raw_params += " "; + raw_params += token.value; + } + advance(); + } - // For drop-shadow, we'll store the raw value since it's complex - // drop-shadow(offset-x offset-y blur-radius color) - string raw_params; - int paren_depth = 0; + func.raw_value = raw_params; + return func; + } - while (!isAtEnd()) + optional CSSFilterParser::parseGrayscale() { - const auto &token = currentToken(); - if (token.type == TokenType::kLeftParen) + FilterFunction func(FilterFunctionType::kGrayscale); + + // grayscale() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) { - paren_depth++; - raw_params += token.value; + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; } - else if (token.type == TokenType::kRightParen) + + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) { - if (paren_depth == 0) - { - advance(); // Consume the closing paren - break; - } - paren_depth--; - raw_params += token.value; + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); } else { - if (!raw_params.empty()) - raw_params += " "; - raw_params += token.value; + setError("Expected number or percentage for grayscale()"); + return nullopt; } - advance(); - } - func.raw_value = raw_params; - return func; - } - - optional CSSFilterParser::parseGrayscale() - { - FilterFunction func(FilterFunctionType::kGrayscale); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for grayscale()"); + return nullopt; + } - // grayscale() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) - { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) - { - func.values.push_back(value); - func.units.push_back(unit); - } - else + optional CSSFilterParser::parseHueRotate() { - setError("Expected number or percentage for grayscale()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kHueRotate); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for grayscale()"); - return nullopt; - } + // hue-rotate() with no arguments defaults to 0deg + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(0.0); + func.units.push_back("deg"); + return func; + } - return func; - } + // Parse angle value + double value; + string unit; + if (!consumeAngle(value, unit)) + { + setError("Expected angle value for hue-rotate()"); + return nullopt; + } - optional CSSFilterParser::parseHueRotate() - { - FilterFunction func(FilterFunctionType::kHueRotate); + func.values.push_back(value); + func.units.push_back(unit); + + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for hue-rotate()"); + return nullopt; + } - // hue-rotate() with no arguments defaults to 0deg - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(0.0); - func.units.push_back("deg"); return func; } - // Parse angle value - double value; - string unit; - if (!consumeAngle(value, unit)) + optional CSSFilterParser::parseInvert() { - setError("Expected angle value for hue-rotate()"); - return nullopt; - } - - func.values.push_back(value); - func.units.push_back(unit); + FilterFunction func(FilterFunctionType::kInvert); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for hue-rotate()"); - return nullopt; - } + // invert() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; + } - return func; - } + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) + { + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + setError("Expected number or percentage for invert()"); + return nullopt; + } - optional CSSFilterParser::parseInvert() - { - FilterFunction func(FilterFunctionType::kInvert); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for invert()"); + return nullopt; + } - // invert() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) - { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) - { - func.values.push_back(value); - func.units.push_back(unit); - } - else + optional CSSFilterParser::parseOpacity() { - setError("Expected number or percentage for invert()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kOpacity); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for invert()"); - return nullopt; - } + // opacity() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; + } - return func; - } + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) + { + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + setError("Expected number or percentage for opacity()"); + return nullopt; + } - optional CSSFilterParser::parseOpacity() - { - FilterFunction func(FilterFunctionType::kOpacity); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for opacity()"); + return nullopt; + } - // opacity() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) + optional CSSFilterParser::parseSaturate() { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) - { - func.values.push_back(value); - func.units.push_back(unit); - } - else - { - setError("Expected number or percentage for opacity()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kSaturate); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for opacity()"); - return nullopt; - } + // saturate() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; + } - return func; - } + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) + { + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + setError("Expected number or percentage for saturate()"); + return nullopt; + } - optional CSSFilterParser::parseSaturate() - { - FilterFunction func(FilterFunctionType::kSaturate); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for saturate()"); + return nullopt; + } - // saturate() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) - { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) + optional CSSFilterParser::parseSepia() { - func.values.push_back(value); - func.units.push_back(unit); - } - else - { - setError("Expected number or percentage for saturate()"); - return nullopt; - } + FilterFunction func(FilterFunctionType::kSepia); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis for saturate()"); - return nullopt; - } + // sepia() with no arguments defaults to 1 + if (!isAtEnd() && currentToken().type == TokenType::kRightParen) + { + advance(); // Consume closing paren + func.values.push_back(1.0); + func.units.push_back(""); + return func; + } - return func; - } + // Parse number or percentage + double value; + string unit; + if (consumePercentage(value)) + { + func.values.push_back(value / 100.0); // Convert percentage to decimal + func.units.push_back("%"); + } + else if (consumeNumber(value, unit)) + { + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + setError("Expected number or percentage for sepia()"); + return nullopt; + } - optional CSSFilterParser::parseSepia() - { - FilterFunction func(FilterFunctionType::kSepia); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis for sepia()"); + return nullopt; + } - // sepia() with no arguments defaults to 1 - if (!isAtEnd() && currentToken().type == TokenType::kRightParen) - { - advance(); // Consume closing paren - func.values.push_back(1.0); - func.units.push_back(""); return func; } - // Parse number or percentage - double value; - string unit; - if (consumePercentage(value)) - { - func.values.push_back(value / 100.0); // Convert percentage to decimal - func.units.push_back("%"); - } - else if (consumeNumber(value, unit)) + bool CSSFilterParser::consumeToken(TokenType expected_type) { - func.values.push_back(value); - func.units.push_back(unit); + if (isAtEnd() || currentToken().type != expected_type) + { + return false; + } + advance(); + return true; } - else + + bool CSSFilterParser::consumeComma() { - setError("Expected number or percentage for sepia()"); - return nullopt; + return consumeToken(TokenType::kComma); } - if (!consumeToken(TokenType::kRightParen)) + bool CSSFilterParser::consumeNumber(double &value, string &unit) { - setError("Expected closing parenthesis for sepia()"); - return nullopt; - } + if (isAtEnd()) + return false; - return func; - } + const auto &token = currentToken(); + if (token.type == TokenType::kNumber) + { + value = token.numeric_value; + unit = ""; + advance(); + return true; + } + else if (token.type == TokenType::kDimension) + { + value = token.numeric_value; + unit = token.unit; + advance(); + return true; + } - bool CSSFilterParser::consumeToken(TokenType expected_type) - { - if (isAtEnd() || currentToken().type != expected_type) - { return false; } - advance(); - return true; - } - bool CSSFilterParser::consumeComma() - { - return consumeToken(TokenType::kComma); - } + bool CSSFilterParser::consumeLength(double &value, string &unit) + { + if (!consumeNumber(value, unit)) + return false; - bool CSSFilterParser::consumeNumber(double &value, string &unit) - { - if (isAtEnd()) - return false; + // Validate length units + if (unit.empty() && value == 0.0) + { + unit = "px"; // Zero length can be unitless + return true; + } - const auto &token = currentToken(); - if (token.type == TokenType::kNumber) - { - value = token.numeric_value; - unit = ""; - advance(); - return true; + if (unit == "px" || unit == "em" || unit == "rem" || unit == "vh" || unit == "vw" || + unit == "pt" || unit == "pc" || unit == "in" || unit == "cm" || unit == "mm") + { + return true; + } + + setError("Invalid length unit: " + unit); + return false; } - else if (token.type == TokenType::kDimension) + + bool CSSFilterParser::consumeAngle(double &value, string &unit) { - value = token.numeric_value; - unit = token.unit; - advance(); - return true; - } + if (!consumeNumber(value, unit)) + return false; - return false; - } + // Validate angle units + if (unit.empty() && value == 0.0) + { + unit = "deg"; // Zero angle can be unitless + return true; + } - bool CSSFilterParser::consumeLength(double &value, string &unit) - { - if (!consumeNumber(value, unit)) + if (unit == "deg" || unit == "rad" || unit == "grad" || unit == "turn") + { + return true; + } + + setError("Invalid angle unit: " + unit); return false; + } - // Validate length units - if (unit.empty() && value == 0.0) + bool CSSFilterParser::consumePercentage(double &value) { - unit = "px"; // Zero length can be unitless + if (isAtEnd() || currentToken().type != TokenType::kPercentage) + return false; + + value = currentToken().numeric_value; + advance(); return true; } - if (unit == "px" || unit == "em" || unit == "rem" || unit == "vh" || unit == "vw" || - unit == "pt" || unit == "pc" || unit == "in" || unit == "cm" || unit == "mm") + bool CSSFilterParser::isAtEnd() const { - return true; + return current_token_index_ >= tokens_.size(); } - setError("Invalid length unit: " + unit); - return false; - } - - bool CSSFilterParser::consumeAngle(double &value, string &unit) - { - if (!consumeNumber(value, unit)) - return false; - - // Validate angle units - if (unit.empty() && value == 0.0) + const Token &CSSFilterParser::currentToken() const { - unit = "deg"; // Zero angle can be unitless - return true; + static Token dummy_token(TokenType::kWhitespace); + if (isAtEnd()) + return dummy_token; + return tokens_[current_token_index_]; } - if (unit == "deg" || unit == "rad" || unit == "grad" || unit == "turn") + void CSSFilterParser::advance() { - return true; + if (current_token_index_ < tokens_.size()) + { + current_token_index_++; + } } - setError("Invalid angle unit: " + unit); - return false; - } - - bool CSSFilterParser::consumePercentage(double &value) - { - if (isAtEnd() || currentToken().type != TokenType::kPercentage) - return false; - - value = currentToken().numeric_value; - advance(); - return true; - } - - bool CSSFilterParser::isAtEnd() const - { - return current_token_index_ >= tokens_.size(); - } - - const Token &CSSFilterParser::currentToken() const - { - static Token dummy_token(TokenType::kWhitespace); - if (isAtEnd()) - return dummy_token; - return tokens_[current_token_index_]; - } - - void CSSFilterParser::advance() - { - if (current_token_index_ < tokens_.size()) + void CSSFilterParser::setError(const string &message) { - current_token_index_++; + is_valid_ = false; + error_message_ = message; } - } - void CSSFilterParser::setError(const string &message) - { - is_valid_ = false; - error_message_ = message; - } + FilterFunctionType CSSFilterParser::getFunctionType(const string &name) + { + static const unordered_map function_map = { + {"blur", FilterFunctionType::kBlur}, + {"brightness", FilterFunctionType::kBrightness}, + {"contrast", FilterFunctionType::kContrast}, + {"drop-shadow", FilterFunctionType::kDropShadow}, + {"grayscale", FilterFunctionType::kGrayscale}, + {"hue-rotate", FilterFunctionType::kHueRotate}, + {"invert", FilterFunctionType::kInvert}, + {"opacity", FilterFunctionType::kOpacity}, + {"saturate", FilterFunctionType::kSaturate}, + {"sepia", FilterFunctionType::kSepia}}; - FilterFunctionType CSSFilterParser::getFunctionType(const string &name) - { - static const unordered_map function_map = { - {"blur", FilterFunctionType::kBlur}, - {"brightness", FilterFunctionType::kBrightness}, - {"contrast", FilterFunctionType::kContrast}, - {"drop-shadow", FilterFunctionType::kDropShadow}, - {"grayscale", FilterFunctionType::kGrayscale}, - {"hue-rotate", FilterFunctionType::kHueRotate}, - {"invert", FilterFunctionType::kInvert}, - {"opacity", FilterFunctionType::kOpacity}, - {"saturate", FilterFunctionType::kSaturate}, - {"sepia", FilterFunctionType::kSepia}}; - - auto it = function_map.find(name); - return (it != function_map.end()) ? it->second : FilterFunctionType::kNone; - } + auto it = function_map.find(name); + return (it != function_map.end()) ? it->second : FilterFunctionType::kNone; + } - void CSSFilterParser::skipSeparators() - { - while (!isAtEnd()) + void CSSFilterParser::skipSeparators() { - const auto &token = currentToken(); - if (token.type == TokenType::kWhitespace || token.type == TokenType::kComma) + while (!isAtEnd()) { - advance(); - } - else - { - break; + const auto &token = currentToken(); + if (token.type == TokenType::kWhitespace || token.type == TokenType::kComma) + { + advance(); + } + else + { + break; + } } } } -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/cssom/parsers/css_filter_parser.hpp b/src/client/cssom/parsers/css_filter_parser.hpp index a477afb3f..50c99bf9d 100644 --- a/src/client/cssom/parsers/css_filter_parser.hpp +++ b/src/client/cssom/parsers/css_filter_parser.hpp @@ -6,96 +6,99 @@ #include #include "./css_value_tokenizer.hpp" -namespace client_cssom::css_filter_parser +namespace endor { - // Enum for different filter function types - enum class FilterFunctionType + namespace client_cssom::css_filter_parser { - kNone, - kBlur, - kBrightness, - kContrast, - kDropShadow, - kGrayscale, - kHueRotate, - kInvert, - kOpacity, - kSaturate, - kSepia - }; - - // Structure to hold parsed filter function data - struct FilterFunction - { - FilterFunctionType type; - std::vector values; // Numeric values - std::vector units; // Units for each value - std::string raw_value; // Original string for complex values like drop-shadow - - FilterFunction(FilterFunctionType t) - : type(t) + // Enum for different filter function types + enum class FilterFunctionType { - } - }; + kNone, + kBlur, + kBrightness, + kContrast, + kDropShadow, + kGrayscale, + kHueRotate, + kInvert, + kOpacity, + kSaturate, + kSepia + }; - // Main parser class - class CSSFilterParser - { - public: - explicit CSSFilterParser(const std::string &input); + // Structure to hold parsed filter function data + struct FilterFunction + { + FilterFunctionType type; + std::vector values; // Numeric values + std::vector units; // Units for each value + std::string raw_value; // Original string for complex values like drop-shadow - // Parse the filter string and return list of functions - std::vector parse(); + FilterFunction(FilterFunctionType t) + : type(t) + { + } + }; - // Check if parsing was successful - bool isValid() const + // Main parser class + class CSSFilterParser { - return is_valid_; - } + public: + explicit CSSFilterParser(const std::string &input); - // Get error message if parsing failed - const std::string &getError() const - { - return error_message_; - } + // Parse the filter string and return list of functions + std::vector parse(); + + // Check if parsing was successful + bool isValid() const + { + return is_valid_; + } + + // Get error message if parsing failed + const std::string &getError() const + { + return error_message_; + } - private: - std::string input_; - css_value_tokenizer::CSSValueTokenizer tokenizer_; - std::vector tokens_; - size_t current_token_index_; - bool is_valid_; - std::string error_message_; + private: + std::string input_; + css_value_tokenizer::CSSValueTokenizer tokenizer_; + std::vector tokens_; + size_t current_token_index_; + bool is_valid_; + std::string error_message_; - // Parse individual filter functions - std::optional parseFilterFunction(); - std::optional parseBlur(); - std::optional parseBrightness(); - std::optional parseContrast(); - std::optional parseDropShadow(); - std::optional parseGrayscale(); - std::optional parseHueRotate(); - std::optional parseInvert(); - std::optional parseOpacity(); - std::optional parseSaturate(); - std::optional parseSepia(); + // Parse individual filter functions + std::optional parseFilterFunction(); + std::optional parseBlur(); + std::optional parseBrightness(); + std::optional parseContrast(); + std::optional parseDropShadow(); + std::optional parseGrayscale(); + std::optional parseHueRotate(); + std::optional parseInvert(); + std::optional parseOpacity(); + std::optional parseSaturate(); + std::optional parseSepia(); - // Helper methods - bool consumeToken(css_value_tokenizer::TokenType expected_type); - bool consumeComma(); - bool consumeNumber(double &value, std::string &unit); - bool consumeLength(double &value, std::string &unit); - bool consumeAngle(double &value, std::string &unit); - bool consumePercentage(double &value); - bool isAtEnd() const; - const css_value_tokenizer::Token ¤tToken() const; - void advance(); - void setError(const std::string &message); + // Helper methods + bool consumeToken(css_value_tokenizer::TokenType expected_type); + bool consumeComma(); + bool consumeNumber(double &value, std::string &unit); + bool consumeLength(double &value, std::string &unit); + bool consumeAngle(double &value, std::string &unit); + bool consumePercentage(double &value); + bool isAtEnd() const; + const css_value_tokenizer::Token ¤tToken() const; + void advance(); + void setError(const std::string &message); - // Filter function name to type mapping - static FilterFunctionType getFunctionType(const std::string &name); + // Filter function name to type mapping + static FilterFunctionType getFunctionType(const std::string &name); - // Skip whitespace and commas between functions - void skipSeparators(); - }; -} \ No newline at end of file + // Skip whitespace and commas between functions + void skipSeparators(); + }; + } +} // namespace endor \ No newline at end of file diff --git a/src/client/cssom/parsers/css_image_parser.cpp b/src/client/cssom/parsers/css_image_parser.cpp index fa0d33153..d19624c99 100644 --- a/src/client/cssom/parsers/css_image_parser.cpp +++ b/src/client/cssom/parsers/css_image_parser.cpp @@ -2,646 +2,649 @@ #include #include "./css_image_parser.hpp" -namespace client_cssom::css_parser +namespace endor { - using namespace std; - using namespace values; - - specified::Image CSSImageParser::parseImage(const string &input) - { - css_value_tokenizer::CSSValueTokenizer tokenizer(input); - vector tokens = tokenizer.tokenize(); - - CSSImageParser parser(tokens); - return parser.parse(); - } - - CSSImageParser::CSSImageParser(const vector &tokens) - : tokens_(tokens) - , position_(0) - { - } - - specified::Image CSSImageParser::parse() + namespace client_cssom::css_parser { - skipWhitespace(); + using namespace std; + using namespace values; - if (!hasNext()) + specified::Image CSSImageParser::parseImage(const string &input) { - return specified::Image::None(); - } - - const auto &token = currentToken(); + css_value_tokenizer::CSSValueTokenizer tokenizer(input); + vector tokens = tokenizer.tokenize(); - // Handle 'none' keyword - if (token.type == css_value_tokenizer::TokenType::kIdentifier && token.value == "none") - { - advance(); - return specified::Image::None(); + CSSImageParser parser(tokens); + return parser.parse(); } - // Handle url() function - if (token.type == css_value_tokenizer::TokenType::kUrl) + CSSImageParser::CSSImageParser(const vector &tokens) + : tokens_(tokens) + , position_(0) { - return parseUrl(); } - // Handle gradient functions and other image functions - if (token.type == css_value_tokenizer::TokenType::kFunction) + specified::Image CSSImageParser::parse() { - if (token.value == "src") + skipWhitespace(); + + if (!hasNext()) { - return parseSrc(); + return specified::Image::None(); } - else if (token.value == "image-set") + + const auto &token = currentToken(); + + // Handle 'none' keyword + if (token.type == css_value_tokenizer::TokenType::kIdentifier && token.value == "none") { - return parseImageSet(); + advance(); + return specified::Image::None(); } - else + + // Handle url() function + if (token.type == css_value_tokenizer::TokenType::kUrl) { - return parseGradient(token.value); + return parseUrl(); } - } - // Default to none for unrecognized input - return specified::Image::None(); - } - - specified::Image CSSImageParser::parseUrl() - { - const auto &token = currentToken(); - if (token.type != css_value_tokenizer::TokenType::kUrl) - { - return specified::Image::None(); - } - - advance(); - return specified::Image::Url(token.value); - } + // Handle gradient functions and other image functions + if (token.type == css_value_tokenizer::TokenType::kFunction) + { + if (token.value == "src") + { + return parseSrc(); + } + else if (token.value == "image-set") + { + return parseImageSet(); + } + else + { + return parseGradient(token.value); + } + } - specified::Image CSSImageParser::parseSrc() - { - if (!consumeFunction("src")) - { + // Default to none for unrecognized input return specified::Image::None(); } - skipWhitespace(); - - // src() function expects a URL string - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kString) + specified::Image CSSImageParser::parseUrl() { - string url = currentToken().value; - advance(); - - skipWhitespace(); - - // Consume closing parenthesis - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kRightParen) + const auto &token = currentToken(); + if (token.type != css_value_tokenizer::TokenType::kUrl) { - advance(); - return specified::Image::Url(url); + return specified::Image::None(); } - } - - return specified::Image::None(); - } - specified::Image CSSImageParser::parseImageSet() - { - if (!consumeFunction("image-set")) - { - return specified::Image::None(); + advance(); + return specified::Image::Url(token.value); } - skipWhitespace(); - - // For now, just parse the first image option in the image-set - // A full implementation would parse all options and resolutions - if (hasNext()) + specified::Image CSSImageParser::parseSrc() { - const auto &token = currentToken(); + if (!consumeFunction("src")) + { + return specified::Image::None(); + } + + skipWhitespace(); - // Parse src() function inside image-set - if (token.type == css_value_tokenizer::TokenType::kFunction && token.value == "src") + // src() function expects a URL string + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kString) { - auto image = parseSrc(); + string url = currentToken().value; + advance(); - // Skip any resolution descriptors and additional options for now - while (hasNext() && currentToken().type != css_value_tokenizer::TokenType::kRightParen) - { - advance(); - } + skipWhitespace(); // Consume closing parenthesis if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kRightParen) { advance(); + return specified::Image::Url(url); } + } + + return specified::Image::None(); + } - return image; + specified::Image CSSImageParser::parseImageSet() + { + if (!consumeFunction("image-set")) + { + return specified::Image::None(); } - // Parse url() function inside image-set - if (token.type == css_value_tokenizer::TokenType::kUrl) + skipWhitespace(); + + // For now, just parse the first image option in the image-set + // A full implementation would parse all options and resolutions + if (hasNext()) { - auto image = parseUrl(); + const auto &token = currentToken(); - // Skip any resolution descriptors and additional options for now - while (hasNext() && currentToken().type != css_value_tokenizer::TokenType::kRightParen) + // Parse src() function inside image-set + if (token.type == css_value_tokenizer::TokenType::kFunction && token.value == "src") { - advance(); + auto image = parseSrc(); + + // Skip any resolution descriptors and additional options for now + while (hasNext() && currentToken().type != css_value_tokenizer::TokenType::kRightParen) + { + advance(); + } + + // Consume closing parenthesis + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kRightParen) + { + advance(); + } + + return image; } - // Consume closing parenthesis - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kRightParen) + // Parse url() function inside image-set + if (token.type == css_value_tokenizer::TokenType::kUrl) { - advance(); - } + auto image = parseUrl(); - return image; - } - } + // Skip any resolution descriptors and additional options for now + while (hasNext() && currentToken().type != css_value_tokenizer::TokenType::kRightParen) + { + advance(); + } - return specified::Image::None(); - } + // Consume closing parenthesis + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kRightParen) + { + advance(); + } - specified::Image CSSImageParser::parseGradient(const string &function_name) - { - if (function_name == "linear-gradient") - { - return parseLinearGradient(false); - } - else if (function_name == "repeating-linear-gradient") - { - return parseLinearGradient(true); - } - else if (function_name == "radial-gradient") - { - return parseRadialGradient(false); - } - else if (function_name == "repeating-radial-gradient") - { - return parseRadialGradient(true); - } + return image; + } + } - return specified::Image::None(); - } + return specified::Image::None(); + } - specified::Image CSSImageParser::parseLinearGradient(bool repeating) - { - if (!consumeFunction("linear-gradient") && !consumeFunction("repeating-linear-gradient")) + specified::Image CSSImageParser::parseGradient(const string &function_name) { + if (function_name == "linear-gradient") + { + return parseLinearGradient(false); + } + else if (function_name == "repeating-linear-gradient") + { + return parseLinearGradient(true); + } + else if (function_name == "radial-gradient") + { + return parseRadialGradient(false); + } + else if (function_name == "repeating-radial-gradient") + { + return parseRadialGradient(true); + } + return specified::Image::None(); } - specified::Gradient::LinearGradient linearGrad; - linearGrad.direction = generics::LineDirection::kToBottom; // Default direction (180deg) + specified::Image CSSImageParser::parseLinearGradient(bool repeating) + { + if (!consumeFunction("linear-gradient") && !consumeFunction("repeating-linear-gradient")) + { + return specified::Image::None(); + } - skipWhitespace(); + specified::Gradient::LinearGradient linearGrad; + linearGrad.direction = generics::LineDirection::kToBottom; // Default direction (180deg) - // Parse optional direction - bool hasDirection = false; - if (hasNext()) - { - // Try to parse direction keywords - const auto &token = currentToken(); - if (token.type == css_value_tokenizer::TokenType::kIdentifier) + skipWhitespace(); + + // Parse optional direction + bool hasDirection = false; + if (hasNext()) { - if (token.value == "to") + // Try to parse direction keywords + const auto &token = currentToken(); + if (token.type == css_value_tokenizer::TokenType::kIdentifier) { - advance(); - skipWhitespace(); - - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier) + if (token.value == "to") { - string direction_keyword = currentToken().value; advance(); + skipWhitespace(); - // Check for compound directions (e.g., "to top left") if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier) { - direction_keyword += " " + currentToken().value; + string direction_keyword = currentToken().value; advance(); - } - // Map direction keywords - if (direction_keyword == "right") - { - linearGrad.direction = generics::LineDirection::kToRight; - hasDirection = true; - } - else if (direction_keyword == "left") - { - linearGrad.direction = generics::LineDirection::kToLeft; - hasDirection = true; - } - else if (direction_keyword == "top") - { - linearGrad.direction = generics::LineDirection::kToTop; - hasDirection = true; - } - else if (direction_keyword == "bottom") - { - linearGrad.direction = generics::LineDirection::kToBottom; - hasDirection = true; - } - else if (direction_keyword == "top left") - { - linearGrad.direction = generics::LineDirection::kToTopLeft; - hasDirection = true; - } - else if (direction_keyword == "top right") - { - linearGrad.direction = generics::LineDirection::kToTopRight; - hasDirection = true; - } - else if (direction_keyword == "bottom left") - { - linearGrad.direction = generics::LineDirection::kToBottomLeft; - hasDirection = true; - } - else if (direction_keyword == "bottom right") - { - linearGrad.direction = generics::LineDirection::kToBottomRight; - hasDirection = true; + // Check for compound directions (e.g., "to top left") + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier) + { + direction_keyword += " " + currentToken().value; + advance(); + } + + // Map direction keywords + if (direction_keyword == "right") + { + linearGrad.direction = generics::LineDirection::kToRight; + hasDirection = true; + } + else if (direction_keyword == "left") + { + linearGrad.direction = generics::LineDirection::kToLeft; + hasDirection = true; + } + else if (direction_keyword == "top") + { + linearGrad.direction = generics::LineDirection::kToTop; + hasDirection = true; + } + else if (direction_keyword == "bottom") + { + linearGrad.direction = generics::LineDirection::kToBottom; + hasDirection = true; + } + else if (direction_keyword == "top left") + { + linearGrad.direction = generics::LineDirection::kToTopLeft; + hasDirection = true; + } + else if (direction_keyword == "top right") + { + linearGrad.direction = generics::LineDirection::kToTopRight; + hasDirection = true; + } + else if (direction_keyword == "bottom left") + { + linearGrad.direction = generics::LineDirection::kToBottomLeft; + hasDirection = true; + } + else if (direction_keyword == "bottom right") + { + linearGrad.direction = generics::LineDirection::kToBottomRight; + hasDirection = true; + } } } + // TODO: Parse angle values (e.g., "45deg", "0.25turn", "1.5708rad") } - // TODO: Parse angle values (e.g., "45deg", "0.25turn", "1.5708rad") - } - // If we parsed a direction, expect a comma - if (hasDirection) - { - skipWhitespace(); - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kComma) + // If we parsed a direction, expect a comma + if (hasDirection) { - advance(); skipWhitespace(); + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kComma) + { + advance(); + skipWhitespace(); + } } } - } - // Parse color stops - linearGrad.items = parseColorStops(); + // Parse color stops + linearGrad.items = parseColorStops(); - // Create the gradient - specified::Gradient gradient(linearGrad); - gradient.repeating = repeating; + // Create the gradient + specified::Gradient gradient(linearGrad); + gradient.repeating = repeating; - specified::Image image; - image.emplace(gradient); - return image; - } + specified::Image image; + image.emplace(gradient); + return image; + } - specified::Image CSSImageParser::parseRadialGradient(bool repeating) - { - if (!consumeFunction("radial-gradient") && !consumeFunction("repeating-radial-gradient")) + specified::Image CSSImageParser::parseRadialGradient(bool repeating) { - return specified::Image::None(); - } + if (!consumeFunction("radial-gradient") && !consumeFunction("repeating-radial-gradient")) + { + return specified::Image::None(); + } - specified::Gradient::RadialGradient radialGrad; - radialGrad.shape = generics::RadialGradientShape::kEllipse; - radialGrad.size = generics::RadialGradientSize::kFarthestCorner; + specified::Gradient::RadialGradient radialGrad; + radialGrad.shape = generics::RadialGradientShape::kEllipse; + radialGrad.size = generics::RadialGradientSize::kFarthestCorner; - skipWhitespace(); + skipWhitespace(); - // Parse optional shape/size - bool hasShapeSize = parseRadialGradientShape(radialGrad); + // Parse optional shape/size + bool hasShapeSize = parseRadialGradientShape(radialGrad); - if (hasShapeSize) - { - skipWhitespace(); - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kComma) + if (hasShapeSize) { - advance(); skipWhitespace(); + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kComma) + { + advance(); + skipWhitespace(); + } } - } - - // Parse color stops - radialGrad.items = parseColorStops(); - // Create the gradient - specified::Gradient gradient(radialGrad); - gradient.repeating = repeating; + // Parse color stops + radialGrad.items = parseColorStops(); - specified::Image image; - image.emplace(gradient); - return image; - } + // Create the gradient + specified::Gradient gradient(radialGrad); + gradient.repeating = repeating; - bool CSSImageParser::parseRadialGradientShape(generics::GenericGradient::RadialGradient &radial) - { - bool modified = false; + specified::Image image; + image.emplace(gradient); + return image; + } - while (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier) + bool CSSImageParser::parseRadialGradientShape(generics::GenericGradient::RadialGradient &radial) { - const string &keyword = currentToken().value; + bool modified = false; - if (keyword == "circle") - { - radial.shape = generics::RadialGradientShape::kCircle; - advance(); - modified = true; - } - else if (keyword == "ellipse") - { - radial.shape = generics::RadialGradientShape::kEllipse; - advance(); - modified = true; - } - else if (keyword == "closest-side") - { - radial.size = generics::RadialGradientSize::kClosestSide; - advance(); - modified = true; - } - else if (keyword == "closest-corner") - { - radial.size = generics::RadialGradientSize::kClosestCorner; - advance(); - modified = true; - } - else if (keyword == "farthest-side") - { - radial.size = generics::RadialGradientSize::kFarthestSide; - advance(); - modified = true; - } - else if (keyword == "farthest-corner") - { - radial.size = generics::RadialGradientSize::kFarthestCorner; - advance(); - modified = true; - } - else + while (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier) { - break; // Not a shape/size keyword - } + const string &keyword = currentToken().value; - skipWhitespace(); - } + if (keyword == "circle") + { + radial.shape = generics::RadialGradientShape::kCircle; + advance(); + modified = true; + } + else if (keyword == "ellipse") + { + radial.shape = generics::RadialGradientShape::kEllipse; + advance(); + modified = true; + } + else if (keyword == "closest-side") + { + radial.size = generics::RadialGradientSize::kClosestSide; + advance(); + modified = true; + } + else if (keyword == "closest-corner") + { + radial.size = generics::RadialGradientSize::kClosestCorner; + advance(); + modified = true; + } + else if (keyword == "farthest-side") + { + radial.size = generics::RadialGradientSize::kFarthestSide; + advance(); + modified = true; + } + else if (keyword == "farthest-corner") + { + radial.size = generics::RadialGradientSize::kFarthestCorner; + advance(); + modified = true; + } + else + { + break; // Not a shape/size keyword + } - return modified; - } + skipWhitespace(); + } - vector CSSImageParser::parseColorStops() - { - vector colorStops; + return modified; + } - while (hasNext()) + vector CSSImageParser::parseColorStops() { - auto colorStop = parseColorStop(); - colorStops.push_back(colorStop); - - skipWhitespace(); + vector colorStops; - // Check for comma separator - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kComma) + while (hasNext()) { - advance(); + auto colorStop = parseColorStop(); + colorStops.push_back(colorStop); + skipWhitespace(); - } - else - { - break; - } - } - return colorStops; - } + // Check for comma separator + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kComma) + { + advance(); + skipWhitespace(); + } + else + { + break; + } + } - specified::GradientItem CSSImageParser::parseColorStop() - { - specified::GradientItem colorStop; + return colorStops; + } - if (!hasNext()) + specified::GradientItem CSSImageParser::parseColorStop() { - // Default to transparent if no color available - colorStop.type = generics::GenericGradientItemBase::kSimpleColorStop; + specified::GradientItem colorStop; - specified::Color color = Parse::ParseSingleValue("transparent"); - colorStop.value = specified::GradientItem::SimpleColorStop{color}; - return colorStop; - } + if (!hasNext()) + { + // Default to transparent if no color available + colorStop.type = generics::GenericGradientItemBase::kSimpleColorStop; - // Parse color - collect tokens that represent the color - std::string colorString; - const auto &token = currentToken(); + specified::Color color = Parse::ParseSingleValue("transparent"); + colorStop.value = specified::GradientItem::SimpleColorStop{color}; + return colorStop; + } - if (token.type == css_value_tokenizer::TokenType::kIdentifier) - { - // Named color or keyword - colorString = token.value; - advance(); - } - else if (token.type == css_value_tokenizer::TokenType::kFunction) - { - // Color function like rgb(), rgba(), hsl(), etc. - colorString = token.value + "("; - advance(); + // Parse color - collect tokens that represent the color + std::string colorString; + const auto &token = currentToken(); - // Collect function content until closing parenthesis - int parenDepth = 1; - while (hasNext() && parenDepth > 0) + if (token.type == css_value_tokenizer::TokenType::kIdentifier) { - const auto &funcToken = currentToken(); - if (funcToken.type == css_value_tokenizer::TokenType::kLeftParen) - { - parenDepth++; - } - else if (funcToken.type == css_value_tokenizer::TokenType::kRightParen) - { - parenDepth--; - } + // Named color or keyword + colorString = token.value; + advance(); + } + else if (token.type == css_value_tokenizer::TokenType::kFunction) + { + // Color function like rgb(), rgba(), hsl(), etc. + colorString = token.value + "("; + advance(); - if (parenDepth > 0) + // Collect function content until closing parenthesis + int parenDepth = 1; + while (hasNext() && parenDepth > 0) { - if (funcToken.type == css_value_tokenizer::TokenType::kComma) + const auto &funcToken = currentToken(); + if (funcToken.type == css_value_tokenizer::TokenType::kLeftParen) { - colorString += ", "; + parenDepth++; } - else if (funcToken.type == css_value_tokenizer::TokenType::kWhitespace) + else if (funcToken.type == css_value_tokenizer::TokenType::kRightParen) { - colorString += " "; + parenDepth--; + } + + if (parenDepth > 0) + { + if (funcToken.type == css_value_tokenizer::TokenType::kComma) + { + colorString += ", "; + } + else if (funcToken.type == css_value_tokenizer::TokenType::kWhitespace) + { + colorString += " "; + } + else + { + colorString += funcToken.value; + } } else { - colorString += funcToken.value; + colorString += ")"; } + advance(); } - else - { - colorString += ")"; - } + } + else if (token.type == css_value_tokenizer::TokenType::kHash) + { + // Hex color like #ff0000 or #ff0000aa - convert to rgb/rgba format + colorString = convertHexToRgb(token.value); advance(); } - } - else if (token.type == css_value_tokenizer::TokenType::kHash) - { - // Hex color like #ff0000 or #ff0000aa - convert to rgb/rgba format - colorString = convertHexToRgb(token.value); - advance(); - } - else - { - // Unsupported color format, default to transparent - // TODO: This should be reached rarely if hex parsing works correctly - colorStop.type = generics::GenericGradientItemBase::kSimpleColorStop; + else + { + // Unsupported color format, default to transparent + // TODO: This should be reached rarely if hex parsing works correctly + colorStop.type = generics::GenericGradientItemBase::kSimpleColorStop; - specified::Color color = Parse::ParseSingleValue("transparent"); - colorStop.value = specified::GradientItem::SimpleColorStop{color}; - return colorStop; - } + specified::Color color = Parse::ParseSingleValue("transparent"); + colorStop.value = specified::GradientItem::SimpleColorStop{color}; + return colorStop; + } - // Parse the color using the Color class - specified::Color color = Parse::ParseSingleValue(colorString); + // Parse the color using the Color class + specified::Color color = Parse::ParseSingleValue(colorString); - skipWhitespace(); + skipWhitespace(); - // Check if there's a position following the color - if (hasNext()) - { - const auto &posToken = currentToken(); - if (posToken.type == css_value_tokenizer::TokenType::kPercentage || - posToken.type == css_value_tokenizer::TokenType::kDimension) + // Check if there's a position following the color + if (hasNext()) { - // Complex color stop with position - std::string positionString = posToken.value; - if (posToken.type == css_value_tokenizer::TokenType::kPercentage) + const auto &posToken = currentToken(); + if (posToken.type == css_value_tokenizer::TokenType::kPercentage || + posToken.type == css_value_tokenizer::TokenType::kDimension) { - positionString += "%"; - } - else if (posToken.type == css_value_tokenizer::TokenType::kDimension) - { - positionString += posToken.unit; + // Complex color stop with position + std::string positionString = posToken.value; + if (posToken.type == css_value_tokenizer::TokenType::kPercentage) + { + positionString += "%"; + } + else if (posToken.type == css_value_tokenizer::TokenType::kDimension) + { + positionString += posToken.unit; + } + + specified::LengthPercentage position = Parse::ParseSingleValue(positionString); + colorStop.type = generics::GenericGradientItemBase::kComplexColorStop; + colorStop.value = specified::GradientItem::ComplexColorStop{color, position}; + advance(); + return colorStop; } + } - specified::LengthPercentage position = Parse::ParseSingleValue(positionString); - colorStop.type = generics::GenericGradientItemBase::kComplexColorStop; - colorStop.value = specified::GradientItem::ComplexColorStop{color, position}; + // Simple color stop without position + colorStop.type = generics::GenericGradientItemBase::kSimpleColorStop; + colorStop.value = specified::GradientItem::SimpleColorStop{color}; + return colorStop; + } + + bool CSSImageParser::consumeToken(css_value_tokenizer::TokenType expected_type) + { + if (hasNext() && currentToken().type == expected_type) + { advance(); - return colorStop; + return true; } + return false; } - // Simple color stop without position - colorStop.type = generics::GenericGradientItemBase::kSimpleColorStop; - colorStop.value = specified::GradientItem::SimpleColorStop{color}; - return colorStop; - } - - bool CSSImageParser::consumeToken(css_value_tokenizer::TokenType expected_type) - { - if (hasNext() && currentToken().type == expected_type) + bool CSSImageParser::consumeIdentifier(const string &expected_value) { - advance(); - return true; + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier && + currentToken().value == expected_value) + { + advance(); + return true; + } + return false; } - return false; - } - bool CSSImageParser::consumeIdentifier(const string &expected_value) - { - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kIdentifier && - currentToken().value == expected_value) + bool CSSImageParser::consumeFunction(const string &expected_name) { - advance(); - return true; + if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kFunction && + currentToken().value == expected_name) + { + advance(); + return true; + } + return false; } - return false; - } - bool CSSImageParser::consumeFunction(const string &expected_name) - { - if (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kFunction && - currentToken().value == expected_name) + bool CSSImageParser::hasNext() const { - advance(); - return true; + return position_ < tokens_.size(); } - return false; - } - bool CSSImageParser::hasNext() const - { - return position_ < tokens_.size(); - } - - const css_value_tokenizer::Token &CSSImageParser::currentToken() const - { - static css_value_tokenizer::Token dummy_token(css_value_tokenizer::TokenType::kWhitespace); - return hasNext() ? tokens_[position_] : dummy_token; - } - - const css_value_tokenizer::Token &CSSImageParser::peekToken(size_t offset) const - { - static css_value_tokenizer::Token dummy_token(css_value_tokenizer::TokenType::kWhitespace); - size_t peek_pos = position_ + offset; - return peek_pos < tokens_.size() ? tokens_[peek_pos] : dummy_token; - } - - void CSSImageParser::advance() - { - if (hasNext()) + const css_value_tokenizer::Token &CSSImageParser::currentToken() const { - position_++; + static css_value_tokenizer::Token dummy_token(css_value_tokenizer::TokenType::kWhitespace); + return hasNext() ? tokens_[position_] : dummy_token; } - } - void CSSImageParser::skipWhitespace() - { - while (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kWhitespace) + const css_value_tokenizer::Token &CSSImageParser::peekToken(size_t offset) const { - advance(); + static css_value_tokenizer::Token dummy_token(css_value_tokenizer::TokenType::kWhitespace); + size_t peek_pos = position_ + offset; + return peek_pos < tokens_.size() ? tokens_[peek_pos] : dummy_token; } - } - - std::string CSSImageParser::convertHexToRgb(const std::string &hexValue) - { - // Convert hex color values to rgb/rgba format - // Supports: 3-digit (#f00), 4-digit (#f008), 6-digit (#ff0000), 8-digit (#ff000080) - std::string hex = hexValue; - - // Expand 3-digit to 6-digit (f00 -> ff0000) - if (hex.length() == 3) + void CSSImageParser::advance() { - hex = std::string(2, hex[0]) + std::string(2, hex[1]) + std::string(2, hex[2]); + if (hasNext()) + { + position_++; + } } - // Expand 4-digit to 8-digit (f008 -> ff000088) - else if (hex.length() == 4) + + void CSSImageParser::skipWhitespace() { - hex = std::string(2, hex[0]) + std::string(2, hex[1]) + std::string(2, hex[2]) + std::string(2, hex[3]); + while (hasNext() && currentToken().type == css_value_tokenizer::TokenType::kWhitespace) + { + advance(); + } } - // Parse RGB values - if (hex.length() >= 6) + std::string CSSImageParser::convertHexToRgb(const std::string &hexValue) { - int r = std::stoi(hex.substr(0, 2), nullptr, 16); - int g = std::stoi(hex.substr(2, 2), nullptr, 16); - int b = std::stoi(hex.substr(4, 2), nullptr, 16); + // Convert hex color values to rgb/rgba format + // Supports: 3-digit (#f00), 4-digit (#f008), 6-digit (#ff0000), 8-digit (#ff000080) + + std::string hex = hexValue; - // Check if alpha channel is present - if (hex.length() == 8) + // Expand 3-digit to 6-digit (f00 -> ff0000) + if (hex.length() == 3) { - int a = std::stoi(hex.substr(6, 2), nullptr, 16); - double alpha = a / 255.0; - return "rgba(" + std::to_string(r) + ", " + std::to_string(g) + ", " + - std::to_string(b) + ", " + std::to_string(alpha) + ")"; + hex = std::string(2, hex[0]) + std::string(2, hex[1]) + std::string(2, hex[2]); } - else + // Expand 4-digit to 8-digit (f008 -> ff000088) + else if (hex.length() == 4) { - return "rgb(" + std::to_string(r) + ", " + std::to_string(g) + ", " + std::to_string(b) + ")"; + hex = std::string(2, hex[0]) + std::string(2, hex[1]) + std::string(2, hex[2]) + std::string(2, hex[3]); } - } - // Fallback for invalid hex values - return "rgb(0, 0, 0)"; + // Parse RGB values + if (hex.length() >= 6) + { + int r = std::stoi(hex.substr(0, 2), nullptr, 16); + int g = std::stoi(hex.substr(2, 2), nullptr, 16); + int b = std::stoi(hex.substr(4, 2), nullptr, 16); + + // Check if alpha channel is present + if (hex.length() == 8) + { + int a = std::stoi(hex.substr(6, 2), nullptr, 16); + double alpha = a / 255.0; + return "rgba(" + std::to_string(r) + ", " + std::to_string(g) + ", " + + std::to_string(b) + ", " + std::to_string(alpha) + ")"; + } + else + { + return "rgb(" + std::to_string(r) + ", " + std::to_string(g) + ", " + std::to_string(b) + ")"; + } + } + + // Fallback for invalid hex values + return "rgb(0, 0, 0)"; + } } -} +} // namespace endor diff --git a/src/client/cssom/parsers/css_image_parser.hpp b/src/client/cssom/parsers/css_image_parser.hpp index 0453c17ab..220ea64bc 100644 --- a/src/client/cssom/parsers/css_image_parser.hpp +++ b/src/client/cssom/parsers/css_image_parser.hpp @@ -6,49 +6,52 @@ #include "../values/specified/length.hpp" #include "../values/specified/angle.hpp" -namespace client_cssom::css_parser +namespace endor { - class CSSImageParser + namespace client_cssom::css_parser { - public: - static values::specified::Image parseImage(const std::string &input); + class CSSImageParser + { + public: + static values::specified::Image parseImage(const std::string &input); - private: - explicit CSSImageParser(const std::vector &tokens); + private: + explicit CSSImageParser(const std::vector &tokens); - values::specified::Image parse(); - values::specified::Image parseUrl(); - values::specified::Image parseSrc(); - values::specified::Image parseImageSet(); - values::specified::Image parseGradient(const std::string &function_name); - values::specified::Image parseLinearGradient(bool repeating = false); - values::specified::Image parseRadialGradient(bool repeating = false); + values::specified::Image parse(); + values::specified::Image parseUrl(); + values::specified::Image parseSrc(); + values::specified::Image parseImageSet(); + values::specified::Image parseGradient(const std::string &function_name); + values::specified::Image parseLinearGradient(bool repeating = false); + values::specified::Image parseRadialGradient(bool repeating = false); - // Gradient component parsing - bool parseLinearGradientDirection(values::generics::LineDirection &direction); - bool parseAngle(values::specified::Angle &angle); - bool parseRadialGradientShape(values::generics::GenericGradient::RadialGradient &radial); - std::vector> - parseColorStops(); - values::generics::GenericGradientItem - parseColorStop(); + // Gradient component parsing + bool parseLinearGradientDirection(values::generics::LineDirection &direction); + bool parseAngle(values::specified::Angle &angle); + bool parseRadialGradientShape(values::generics::GenericGradient::RadialGradient &radial); + std::vector> + parseColorStops(); + values::generics::GenericGradientItem + parseColorStop(); - // Helper methods - bool consumeToken(css_value_tokenizer::TokenType expected_type); - bool consumeIdentifier(const std::string &expected_value); - bool consumeFunction(const std::string &expected_name); - std::string convertHexToRgb(const std::string &hexValue); - bool hasNext() const; - const css_value_tokenizer::Token ¤tToken() const; - const css_value_tokenizer::Token &peekToken(size_t offset = 1) const; - void advance(); - void skipWhitespace(); + // Helper methods + bool consumeToken(css_value_tokenizer::TokenType expected_type); + bool consumeIdentifier(const std::string &expected_value); + bool consumeFunction(const std::string &expected_name); + std::string convertHexToRgb(const std::string &hexValue); + bool hasNext() const; + const css_value_tokenizer::Token ¤tToken() const; + const css_value_tokenizer::Token &peekToken(size_t offset = 1) const; + void advance(); + void skipWhitespace(); - std::vector tokens_; - size_t position_; - }; -} + std::vector tokens_; + size_t position_; + }; + } +} // namespace endor diff --git a/src/client/cssom/parsers/css_transform_parser.cpp b/src/client/cssom/parsers/css_transform_parser.cpp index 31f982566..a797cf1a9 100644 --- a/src/client/cssom/parsers/css_transform_parser.cpp +++ b/src/client/cssom/parsers/css_transform_parser.cpp @@ -4,1076 +4,1079 @@ #include #include -namespace client_cssom::css_transform_parser +namespace endor { - using namespace std; - using namespace css_value_tokenizer; - - CSSTransformParser::CSSTransformParser(const string &input) - : input_(input) - , tokenizer_(input) - , current_token_index_(0) - , is_valid_(false) + namespace client_cssom::css_transform_parser { - tokens_ = tokenizer_.tokenize(); - } - - vector CSSTransformParser::parse() - { - vector functions; - current_token_index_ = 0; - is_valid_ = true; - error_message_.clear(); + using namespace std; + using namespace css_value_tokenizer; - // Handle 'none' case - if (tokens_.size() == 1 && tokens_[0].type == TokenType::kIdentifier && tokens_[0].value == "none") + CSSTransformParser::CSSTransformParser(const string &input) + : input_(input) + , tokenizer_(input) + , current_token_index_(0) + , is_valid_(false) { - return functions; // Return empty list for 'none' + tokens_ = tokenizer_.tokenize(); } - // Parse transform functions - while (!isAtEnd()) + vector CSSTransformParser::parse() { - auto func = parseTransformFunction(); - if (!func.has_value()) - { - is_valid_ = false; - return functions; - } - functions.push_back(func.value()); + vector functions; + current_token_index_ = 0; + is_valid_ = true; + error_message_.clear(); - // Skip whitespace between functions - while (!isAtEnd() && currentToken().type == TokenType::kWhitespace) + // Handle 'none' case + if (tokens_.size() == 1 && tokens_[0].type == TokenType::kIdentifier && tokens_[0].value == "none") { - advance(); + return functions; // Return empty list for 'none' } - } - - return functions; - } - - optional CSSTransformParser::parseTransformFunction() - { - if (isAtEnd() || currentToken().type != TokenType::kFunction) - { - setError("Expected transform function"); - return nullopt; - } - const string &function_name = currentToken().value; - TransformFunctionType type = getFunctionType(function_name); + // Parse transform functions + while (!isAtEnd()) + { + auto func = parseTransformFunction(); + if (!func.has_value()) + { + is_valid_ = false; + return functions; + } + functions.push_back(func.value()); - advance(); // Skip function name + // Skip whitespace between functions + while (!isAtEnd() && currentToken().type == TokenType::kWhitespace) + { + advance(); + } + } - switch (type) - { - case TransformFunctionType::kMatrix: - return parseMatrix(); - case TransformFunctionType::kMatrix3D: - return parseMatrix3D(); - case TransformFunctionType::kTranslate: - return parseTranslate(); - case TransformFunctionType::kTranslateX: - return parseTranslateX(); - case TransformFunctionType::kTranslateY: - return parseTranslateY(); - case TransformFunctionType::kTranslateZ: - return parseTranslateZ(); - case TransformFunctionType::kTranslate3D: - return parseTranslate3D(); - case TransformFunctionType::kScale: - return parseScale(); - case TransformFunctionType::kScaleX: - return parseScaleX(); - case TransformFunctionType::kScaleY: - return parseScaleY(); - case TransformFunctionType::kScaleZ: - return parseScaleZ(); - case TransformFunctionType::kScale3D: - return parseScale3D(); - case TransformFunctionType::kRotate: - return parseRotate(); - case TransformFunctionType::kRotateX: - return parseRotateX(); - case TransformFunctionType::kRotateY: - return parseRotateY(); - case TransformFunctionType::kRotateZ: - return parseRotateZ(); - case TransformFunctionType::kRotate3D: - return parseRotate3D(); - case TransformFunctionType::kSkew: - return parseSkew(); - case TransformFunctionType::kSkewX: - return parseSkewX(); - case TransformFunctionType::kSkewY: - return parseSkewY(); - case TransformFunctionType::kPerspective: - return parsePerspective(); - default: - setError("Unknown transform function: " + function_name); - return nullopt; + return functions; } - } - - optional CSSTransformParser::parseMatrix() - { - TransformFunction func(TransformFunctionType::kMatrix); - // matrix(a, b, c, d, e, f) - 6 numbers - for (int i = 0; i < 6; ++i) + optional CSSTransformParser::parseTransformFunction() { - double value; - string unit; - - if (!consumeNumber(value, unit)) + if (isAtEnd() || currentToken().type != TokenType::kFunction) { - setError("Expected number in matrix()"); + setError("Expected transform function"); return nullopt; } - func.values.push_back(value); - func.units.push_back(unit); + const string &function_name = currentToken().value; + TransformFunctionType type = getFunctionType(function_name); - if (i < 5 && !consumeComma()) + advance(); // Skip function name + + switch (type) { - setError("Expected comma in matrix()"); + case TransformFunctionType::kMatrix: + return parseMatrix(); + case TransformFunctionType::kMatrix3D: + return parseMatrix3D(); + case TransformFunctionType::kTranslate: + return parseTranslate(); + case TransformFunctionType::kTranslateX: + return parseTranslateX(); + case TransformFunctionType::kTranslateY: + return parseTranslateY(); + case TransformFunctionType::kTranslateZ: + return parseTranslateZ(); + case TransformFunctionType::kTranslate3D: + return parseTranslate3D(); + case TransformFunctionType::kScale: + return parseScale(); + case TransformFunctionType::kScaleX: + return parseScaleX(); + case TransformFunctionType::kScaleY: + return parseScaleY(); + case TransformFunctionType::kScaleZ: + return parseScaleZ(); + case TransformFunctionType::kScale3D: + return parseScale3D(); + case TransformFunctionType::kRotate: + return parseRotate(); + case TransformFunctionType::kRotateX: + return parseRotateX(); + case TransformFunctionType::kRotateY: + return parseRotateY(); + case TransformFunctionType::kRotateZ: + return parseRotateZ(); + case TransformFunctionType::kRotate3D: + return parseRotate3D(); + case TransformFunctionType::kSkew: + return parseSkew(); + case TransformFunctionType::kSkewX: + return parseSkewX(); + case TransformFunctionType::kSkewY: + return parseSkewY(); + case TransformFunctionType::kPerspective: + return parsePerspective(); + default: + setError("Unknown transform function: " + function_name); return nullopt; } } - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseMatrix() { - setError("Expected closing parenthesis in matrix()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kMatrix); - return func; - } + // matrix(a, b, c, d, e, f) - 6 numbers + for (int i = 0; i < 6; ++i) + { + double value; + string unit; - optional CSSTransformParser::parseMatrix3D() - { - TransformFunction func(TransformFunctionType::kMatrix3D); + if (!consumeNumber(value, unit)) + { + setError("Expected number in matrix()"); + return nullopt; + } - // matrix3d(m11, m12, ..., m44) - 16 numbers - for (int i = 0; i < 16; ++i) - { - double value; - string unit; + func.values.push_back(value); + func.units.push_back(unit); - if (!consumeNumber(value, unit)) - { - setError("Expected number in matrix3d()"); - return nullopt; + if (i < 5 && !consumeComma()) + { + setError("Expected comma in matrix()"); + return nullopt; + } } - func.values.push_back(value); - func.units.push_back(unit); - - if (i < 15 && !consumeComma()) + if (!consumeToken(TokenType::kRightParen)) { - setError("Expected comma in matrix3d()"); + setError("Expected closing parenthesis in matrix()"); return nullopt; } + + return func; } - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseMatrix3D() { - setError("Expected closing parenthesis in matrix3d()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kMatrix3D); - return func; - } + // matrix3d(m11, m12, ..., m44) - 16 numbers + for (int i = 0; i < 16; ++i) + { + double value; + string unit; - optional CSSTransformParser::parseTranslate() - { - TransformFunction func(TransformFunctionType::kTranslate); + if (!consumeNumber(value, unit)) + { + setError("Expected number in matrix3d()"); + return nullopt; + } - // translate(x, y?) - 1 or 2 length/percentage values - double value; - string unit; + func.values.push_back(value); + func.units.push_back(unit); - // X value (required) - if (!consumeLength(value, unit)) - { - setError("Expected length/percentage in translate()"); - return nullopt; + if (i < 15 && !consumeComma()) + { + setError("Expected comma in matrix3d()"); + return nullopt; + } + } + + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in matrix3d()"); + return nullopt; + } + + return func; } - func.values.push_back(value); - func.units.push_back(unit); - // Y value (optional, defaults to 0) - if (!isAtEnd() && currentToken().type == TokenType::kComma) + optional CSSTransformParser::parseTranslate() { - advance(); // Skip comma + TransformFunction func(TransformFunctionType::kTranslate); + // translate(x, y?) - 1 or 2 length/percentage values + double value; + string unit; + + // X value (required) if (!consumeLength(value, unit)) { - setError("Expected length/percentage for Y in translate()"); + setError("Expected length/percentage in translate()"); return nullopt; } func.values.push_back(value); func.units.push_back(unit); - } - else - { - // Default Y to 0 - func.values.push_back(0.0); - func.units.push_back("px"); - } - - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in translate()"); - return nullopt; - } - - return func; - } - - optional CSSTransformParser::parseTranslateX() - { - TransformFunction func(TransformFunctionType::kTranslateX); - double value; - string unit; + // Y value (optional, defaults to 0) + if (!isAtEnd() && currentToken().type == TokenType::kComma) + { + advance(); // Skip comma - if (!consumeLength(value, unit)) - { - setError("Expected length/percentage in translateX()"); - return nullopt; - } + if (!consumeLength(value, unit)) + { + setError("Expected length/percentage for Y in translate()"); + return nullopt; + } + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + // Default Y to 0 + func.values.push_back(0.0); + func.units.push_back("px"); + } - func.values.push_back(value); - func.units.push_back(unit); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in translate()"); + return nullopt; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in translateX()"); - return nullopt; + return func; } - return func; - } - - optional CSSTransformParser::parseTranslateY() - { - TransformFunction func(TransformFunctionType::kTranslateY); - - double value; - string unit; - - if (!consumeLength(value, unit)) + optional CSSTransformParser::parseTranslateX() { - setError("Expected length/percentage in translateY()"); - return nullopt; - } - - func.values.push_back(value); - func.units.push_back(unit); + TransformFunction func(TransformFunctionType::kTranslateX); - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in translateY()"); - return nullopt; - } + double value; + string unit; - return func; - } + if (!consumeLength(value, unit)) + { + setError("Expected length/percentage in translateX()"); + return nullopt; + } - optional CSSTransformParser::parseTranslateZ() - { - TransformFunction func(TransformFunctionType::kTranslateZ); + func.values.push_back(value); + func.units.push_back(unit); - double value; - string unit; + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in translateX()"); + return nullopt; + } - if (!consumeLength(value, unit)) - { - setError("Expected length in translateZ()"); - return nullopt; + return func; } - func.values.push_back(value); - func.units.push_back(unit); - - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseTranslateY() { - setError("Expected closing parenthesis in translateZ()"); - return nullopt; - } - - return func; - } + TransformFunction func(TransformFunctionType::kTranslateY); - optional CSSTransformParser::parseTranslate3D() - { - TransformFunction func(TransformFunctionType::kTranslate3D); - - // translate3d(x, y, z) - 3 length values - for (int i = 0; i < 3; ++i) - { double value; string unit; if (!consumeLength(value, unit)) { - setError("Expected length in translate3d()"); + setError("Expected length/percentage in translateY()"); return nullopt; } func.values.push_back(value); func.units.push_back(unit); - if (i < 2 && !consumeComma()) + if (!consumeToken(TokenType::kRightParen)) { - setError("Expected comma in translate3d()"); + setError("Expected closing parenthesis in translateY()"); return nullopt; } - } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in translate3d()"); - return nullopt; + return func; } - return func; - } - - optional CSSTransformParser::parseScale() - { - TransformFunction func(TransformFunctionType::kScale); - - // scale(x, y?) - 1 or 2 numbers - double value; - string unit; - - // X value (required) - if (!consumeNumber(value, unit)) + optional CSSTransformParser::parseTranslateZ() { - setError("Expected number in scale()"); - return nullopt; - } - func.values.push_back(value); - func.units.push_back(unit); + TransformFunction func(TransformFunctionType::kTranslateZ); - // Y value (optional, defaults to x) - if (!isAtEnd() && currentToken().type == TokenType::kComma) - { - advance(); // Skip comma + double value; + string unit; - if (!consumeNumber(value, unit)) + if (!consumeLength(value, unit)) { - setError("Expected number for Y in scale()"); + setError("Expected length in translateZ()"); return nullopt; } + func.values.push_back(value); func.units.push_back(unit); - } - else - { - // Default Y to X value - func.values.push_back(func.values[0]); - func.units.push_back(func.units[0]); - } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in scale()"); - return nullopt; - } - - return func; - } - - optional CSSTransformParser::parseScaleX() - { - TransformFunction func(TransformFunctionType::kScaleX); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in translateZ()"); + return nullopt; + } - double value; - string unit; + return func; + } - if (!consumeNumber(value, unit)) + optional CSSTransformParser::parseTranslate3D() { - setError("Expected number in scaleX()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kTranslate3D); - func.values.push_back(value); - func.units.push_back(unit); + // translate3d(x, y, z) - 3 length values + for (int i = 0; i < 3; ++i) + { + double value; + string unit; - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in scaleX()"); - return nullopt; - } + if (!consumeLength(value, unit)) + { + setError("Expected length in translate3d()"); + return nullopt; + } - return func; - } + func.values.push_back(value); + func.units.push_back(unit); - optional CSSTransformParser::parseScaleY() - { - TransformFunction func(TransformFunctionType::kScaleY); + if (i < 2 && !consumeComma()) + { + setError("Expected comma in translate3d()"); + return nullopt; + } + } - double value; - string unit; + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in translate3d()"); + return nullopt; + } - if (!consumeNumber(value, unit)) - { - setError("Expected number in scaleY()"); - return nullopt; + return func; } - func.values.push_back(value); - func.units.push_back(unit); - - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseScale() { - setError("Expected closing parenthesis in scaleY()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kScale); - return func; - } + // scale(x, y?) - 1 or 2 numbers + double value; + string unit; - optional CSSTransformParser::parseScaleZ() - { - TransformFunction func(TransformFunctionType::kScaleZ); + // X value (required) + if (!consumeNumber(value, unit)) + { + setError("Expected number in scale()"); + return nullopt; + } + func.values.push_back(value); + func.units.push_back(unit); - double value; - string unit; + // Y value (optional, defaults to x) + if (!isAtEnd() && currentToken().type == TokenType::kComma) + { + advance(); // Skip comma - if (!consumeNumber(value, unit)) - { - setError("Expected number in scaleZ()"); - return nullopt; - } + if (!consumeNumber(value, unit)) + { + setError("Expected number for Y in scale()"); + return nullopt; + } + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + // Default Y to X value + func.values.push_back(func.values[0]); + func.units.push_back(func.units[0]); + } - func.values.push_back(value); - func.units.push_back(unit); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in scale()"); + return nullopt; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in scaleZ()"); - return nullopt; + return func; } - return func; - } - - optional CSSTransformParser::parseScale3D() - { - TransformFunction func(TransformFunctionType::kScale3D); - - // scale3d(x, y, z) - 3 numbers - for (int i = 0; i < 3; ++i) + optional CSSTransformParser::parseScaleX() { + TransformFunction func(TransformFunctionType::kScaleX); + double value; string unit; if (!consumeNumber(value, unit)) { - setError("Expected number in scale3d()"); + setError("Expected number in scaleX()"); return nullopt; } func.values.push_back(value); func.units.push_back(unit); - if (i < 2 && !consumeComma()) + if (!consumeToken(TokenType::kRightParen)) { - setError("Expected comma in scale3d()"); + setError("Expected closing parenthesis in scaleX()"); return nullopt; } - } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in scale3d()"); - return nullopt; + return func; } - return func; - } - - optional CSSTransformParser::parseRotate() - { - TransformFunction func(TransformFunctionType::kRotate); - - double value; - string unit; - - if (!consumeAngle(value, unit)) + optional CSSTransformParser::parseScaleY() { - setError("Expected angle in rotate()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kScaleY); - func.values.push_back(value); - func.units.push_back(unit); - - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in rotate()"); - return nullopt; - } + double value; + string unit; - return func; - } + if (!consumeNumber(value, unit)) + { + setError("Expected number in scaleY()"); + return nullopt; + } - optional CSSTransformParser::parseRotateX() - { - TransformFunction func(TransformFunctionType::kRotateX); + func.values.push_back(value); + func.units.push_back(unit); - double value; - string unit; + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in scaleY()"); + return nullopt; + } - if (!consumeAngle(value, unit)) - { - setError("Expected angle in rotateX()"); - return nullopt; + return func; } - func.values.push_back(value); - func.units.push_back(unit); - - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseScaleZ() { - setError("Expected closing parenthesis in rotateX()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kScaleZ); - return func; - } + double value; + string unit; - optional CSSTransformParser::parseRotateY() - { - TransformFunction func(TransformFunctionType::kRotateY); + if (!consumeNumber(value, unit)) + { + setError("Expected number in scaleZ()"); + return nullopt; + } - double value; - string unit; + func.values.push_back(value); + func.units.push_back(unit); - if (!consumeAngle(value, unit)) - { - setError("Expected angle in rotateY()"); - return nullopt; - } + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in scaleZ()"); + return nullopt; + } - func.values.push_back(value); - func.units.push_back(unit); + return func; + } - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseScale3D() { - setError("Expected closing parenthesis in rotateY()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kScale3D); - return func; - } + // scale3d(x, y, z) - 3 numbers + for (int i = 0; i < 3; ++i) + { + double value; + string unit; - optional CSSTransformParser::parseRotateZ() - { - TransformFunction func(TransformFunctionType::kRotateZ); + if (!consumeNumber(value, unit)) + { + setError("Expected number in scale3d()"); + return nullopt; + } - double value; - string unit; + func.values.push_back(value); + func.units.push_back(unit); - if (!consumeAngle(value, unit)) - { - setError("Expected angle in rotateZ()"); - return nullopt; - } + if (i < 2 && !consumeComma()) + { + setError("Expected comma in scale3d()"); + return nullopt; + } + } - func.values.push_back(value); - func.units.push_back(unit); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in scale3d()"); + return nullopt; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in rotateZ()"); - return nullopt; + return func; } - return func; - } - - optional CSSTransformParser::parseRotate3D() - { - TransformFunction func(TransformFunctionType::kRotate3D); - - // rotate3d(x, y, z, angle) - 3 numbers + 1 angle - for (int i = 0; i < 3; ++i) + optional CSSTransformParser::parseRotate() { + TransformFunction func(TransformFunctionType::kRotate); + double value; string unit; - if (!consumeNumber(value, unit)) + if (!consumeAngle(value, unit)) { - setError("Expected number in rotate3d()"); + setError("Expected angle in rotate()"); return nullopt; } func.values.push_back(value); func.units.push_back(unit); - if (!consumeComma()) + if (!consumeToken(TokenType::kRightParen)) { - setError("Expected comma in rotate3d()"); + setError("Expected closing parenthesis in rotate()"); return nullopt; } + + return func; } - // Angle parameter - double value; - string unit; - if (!consumeAngle(value, unit)) + optional CSSTransformParser::parseRotateX() { - setError("Expected angle in rotate3d()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kRotateX); - func.values.push_back(value); - func.units.push_back(unit); + double value; + string unit; - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in rotate3d()"); - return nullopt; - } + if (!consumeAngle(value, unit)) + { + setError("Expected angle in rotateX()"); + return nullopt; + } - return func; - } + func.values.push_back(value); + func.units.push_back(unit); - optional CSSTransformParser::parseSkew() - { - TransformFunction func(TransformFunctionType::kSkew); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in rotateX()"); + return nullopt; + } - // skew(x, y?) - 1 or 2 angles - double value; - string unit; + return func; + } - // X angle (required) - if (!consumeAngle(value, unit)) + optional CSSTransformParser::parseRotateY() { - setError("Expected angle in skew()"); - return nullopt; + TransformFunction func(TransformFunctionType::kRotateY); + + double value; + string unit; + + if (!consumeAngle(value, unit)) + { + setError("Expected angle in rotateY()"); + return nullopt; + } + + func.values.push_back(value); + func.units.push_back(unit); + + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in rotateY()"); + return nullopt; + } + + return func; } - func.values.push_back(value); - func.units.push_back(unit); - // Y angle (optional, defaults to 0) - if (!isAtEnd() && currentToken().type == TokenType::kComma) + optional CSSTransformParser::parseRotateZ() { - advance(); // Skip comma + TransformFunction func(TransformFunctionType::kRotateZ); + + double value; + string unit; if (!consumeAngle(value, unit)) { - setError("Expected angle for Y in skew()"); + setError("Expected angle in rotateZ()"); return nullopt; } + func.values.push_back(value); func.units.push_back(unit); + + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in rotateZ()"); + return nullopt; + } + + return func; } - else + + optional CSSTransformParser::parseRotate3D() { - // Default Y to 0 - func.values.push_back(0.0); - func.units.push_back("deg"); + TransformFunction func(TransformFunctionType::kRotate3D); + + // rotate3d(x, y, z, angle) - 3 numbers + 1 angle + for (int i = 0; i < 3; ++i) + { + double value; + string unit; + + if (!consumeNumber(value, unit)) + { + setError("Expected number in rotate3d()"); + return nullopt; + } + + func.values.push_back(value); + func.units.push_back(unit); + + if (!consumeComma()) + { + setError("Expected comma in rotate3d()"); + return nullopt; + } + } + + // Angle parameter + double value; + string unit; + if (!consumeAngle(value, unit)) + { + setError("Expected angle in rotate3d()"); + return nullopt; + } + + func.values.push_back(value); + func.units.push_back(unit); + + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in rotate3d()"); + return nullopt; + } + + return func; } - if (!consumeToken(TokenType::kRightParen)) + optional CSSTransformParser::parseSkew() { - setError("Expected closing parenthesis in skew()"); - return nullopt; - } + TransformFunction func(TransformFunctionType::kSkew); - return func; - } + // skew(x, y?) - 1 or 2 angles + double value; + string unit; - optional CSSTransformParser::parseSkewX() - { - TransformFunction func(TransformFunctionType::kSkewX); + // X angle (required) + if (!consumeAngle(value, unit)) + { + setError("Expected angle in skew()"); + return nullopt; + } + func.values.push_back(value); + func.units.push_back(unit); - double value; - string unit; + // Y angle (optional, defaults to 0) + if (!isAtEnd() && currentToken().type == TokenType::kComma) + { + advance(); // Skip comma - if (!consumeAngle(value, unit)) - { - setError("Expected angle in skewX()"); - return nullopt; - } + if (!consumeAngle(value, unit)) + { + setError("Expected angle for Y in skew()"); + return nullopt; + } + func.values.push_back(value); + func.units.push_back(unit); + } + else + { + // Default Y to 0 + func.values.push_back(0.0); + func.units.push_back("deg"); + } - func.values.push_back(value); - func.units.push_back(unit); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in skew()"); + return nullopt; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in skewX()"); - return nullopt; + return func; } - return func; - } + optional CSSTransformParser::parseSkewX() + { + TransformFunction func(TransformFunctionType::kSkewX); - optional CSSTransformParser::parseSkewY() - { - TransformFunction func(TransformFunctionType::kSkewY); + double value; + string unit; - double value; - string unit; + if (!consumeAngle(value, unit)) + { + setError("Expected angle in skewX()"); + return nullopt; + } - if (!consumeAngle(value, unit)) - { - setError("Expected angle in skewY()"); - return nullopt; - } + func.values.push_back(value); + func.units.push_back(unit); - func.values.push_back(value); - func.units.push_back(unit); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in skewX()"); + return nullopt; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in skewY()"); - return nullopt; + return func; } - return func; - } + optional CSSTransformParser::parseSkewY() + { + TransformFunction func(TransformFunctionType::kSkewY); - optional CSSTransformParser::parsePerspective() - { - TransformFunction func(TransformFunctionType::kPerspective); + double value; + string unit; - double value; - string unit; + if (!consumeAngle(value, unit)) + { + setError("Expected angle in skewY()"); + return nullopt; + } - if (!consumeLength(value, unit)) - { - setError("Expected length in perspective()"); - return nullopt; - } + func.values.push_back(value); + func.units.push_back(unit); - func.values.push_back(value); - func.units.push_back(unit); + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in skewY()"); + return nullopt; + } - if (!consumeToken(TokenType::kRightParen)) - { - setError("Expected closing parenthesis in perspective()"); - return nullopt; + return func; } - return func; - } - - // Helper methods - bool CSSTransformParser::consumeToken(TokenType expected_type) - { - if (isAtEnd() || currentToken().type != expected_type) + optional CSSTransformParser::parsePerspective() { - return false; - } - advance(); - return true; - } + TransformFunction func(TransformFunctionType::kPerspective); - bool CSSTransformParser::consumeComma() - { - return consumeToken(TokenType::kComma); - } + double value; + string unit; - bool CSSTransformParser::consumeNumber(double &value, string &unit) - { - if (isAtEnd()) - { - return false; - } + if (!consumeLength(value, unit)) + { + setError("Expected length in perspective()"); + return nullopt; + } - const Token &token = currentToken(); + func.values.push_back(value); + func.units.push_back(unit); - if (token.type == TokenType::kNumber) - { - value = token.numeric_value; - unit = ""; - advance(); - return true; + if (!consumeToken(TokenType::kRightParen)) + { + setError("Expected closing parenthesis in perspective()"); + return nullopt; + } + + return func; } - else if (token.type == TokenType::kDimension) + + // Helper methods + bool CSSTransformParser::consumeToken(TokenType expected_type) { - value = token.numeric_value; - unit = token.unit; + if (isAtEnd() || currentToken().type != expected_type) + { + return false; + } advance(); return true; } - else if (token.type == TokenType::kPercentage) + + bool CSSTransformParser::consumeComma() { - value = token.numeric_value; - unit = "%"; - advance(); - return true; + return consumeToken(TokenType::kComma); } - else if (token.type == TokenType::kIdentifier) + + bool CSSTransformParser::consumeNumber(double &value, string &unit) { - // Handle negative numbers tokenized as identifiers (e.g., "-10", "-1") - const string &str = token.value; - if (!str.empty() && str[0] == '-') + if (isAtEnd()) { - try - { - // Try to parse as a pure number - double parsed_value = stod(str); - value = parsed_value; - unit = ""; - advance(); + return false; + } - // Check if next token is a decimal part (e.g., ".5" after "-0") - if (!isAtEnd() && currentToken().type == TokenType::kNumber) + const Token &token = currentToken(); + + if (token.type == TokenType::kNumber) + { + value = token.numeric_value; + unit = ""; + advance(); + return true; + } + else if (token.type == TokenType::kDimension) + { + value = token.numeric_value; + unit = token.unit; + advance(); + return true; + } + else if (token.type == TokenType::kPercentage) + { + value = token.numeric_value; + unit = "%"; + advance(); + return true; + } + else if (token.type == TokenType::kIdentifier) + { + // Handle negative numbers tokenized as identifiers (e.g., "-10", "-1") + const string &str = token.value; + if (!str.empty() && str[0] == '-') + { + try { - const string &next_str = currentToken().value; - if (!next_str.empty() && next_str[0] == '.') + // Try to parse as a pure number + double parsed_value = stod(str); + value = parsed_value; + unit = ""; + advance(); + + // Check if next token is a decimal part (e.g., ".5" after "-0") + if (!isAtEnd() && currentToken().type == TokenType::kNumber) { - // Combine the integer and decimal parts - double decimal_part = currentToken().numeric_value; - // For negative identifiers starting with "-", always treat as negative - if (str[0] == '-') - { - value = parsed_value - decimal_part; - } - else + const string &next_str = currentToken().value; + if (!next_str.empty() && next_str[0] == '.') { - value = parsed_value + decimal_part; + // Combine the integer and decimal parts + double decimal_part = currentToken().numeric_value; + // For negative identifiers starting with "-", always treat as negative + if (str[0] == '-') + { + value = parsed_value - decimal_part; + } + else + { + value = parsed_value + decimal_part; + } + advance(); // Consume the decimal part } - advance(); // Consume the decimal part } - } - return true; - } - catch (...) - { - // Not a valid number, fall through + return true; + } + catch (...) + { + // Not a valid number, fall through + } } } - } - return false; - } - - bool CSSTransformParser::consumeLength(double &value, string &unit) - { - if (isAtEnd()) - { return false; } - const Token &token = currentToken(); - - if (token.type == TokenType::kNumber && token.numeric_value == 0.0) + bool CSSTransformParser::consumeLength(double &value, string &unit) { - // Zero is allowed without unit for lengths - value = 0.0; - unit = "px"; - advance(); - return true; - } - else if (token.type == TokenType::kDimension) - { - // Check if unit is a valid length unit - const string &u = token.unit; - if (u == "px" || u == "em" || u == "rem" || u == "vh" || u == "vw" || - u == "vmin" || u == "vmax" || u == "%" || u == "cm" || u == "mm" || - u == "in" || u == "pt" || u == "pc") + if (isAtEnd()) { - value = token.numeric_value; - unit = u; + return false; + } + + const Token &token = currentToken(); + + if (token.type == TokenType::kNumber && token.numeric_value == 0.0) + { + // Zero is allowed without unit for lengths + value = 0.0; + unit = "px"; advance(); return true; } - } - else if (token.type == TokenType::kPercentage) - { - value = token.numeric_value; - unit = "%"; - advance(); - return true; - } - else if (token.type == TokenType::kIdentifier) - { - // Handle negative lengths tokenized as identifiers (e.g., "-10px", "-5em") - const string &str = token.value; - if (!str.empty() && str[0] == '-') - { - // Try to extract number and unit - size_t unit_start = 1; // Start after the minus sign - while (unit_start < str.length() && - (isdigit(str[unit_start]) || str[unit_start] == '.')) + else if (token.type == TokenType::kDimension) + { + // Check if unit is a valid length unit + const string &u = token.unit; + if (u == "px" || u == "em" || u == "rem" || u == "vh" || u == "vw" || + u == "vmin" || u == "vmax" || u == "%" || u == "cm" || u == "mm" || + u == "in" || u == "pt" || u == "pc") { - unit_start++; + value = token.numeric_value; + unit = u; + advance(); + return true; } - - if (unit_start > 1 && unit_start < str.length()) + } + else if (token.type == TokenType::kPercentage) + { + value = token.numeric_value; + unit = "%"; + advance(); + return true; + } + else if (token.type == TokenType::kIdentifier) + { + // Handle negative lengths tokenized as identifiers (e.g., "-10px", "-5em") + const string &str = token.value; + if (!str.empty() && str[0] == '-') { - try + // Try to extract number and unit + size_t unit_start = 1; // Start after the minus sign + while (unit_start < str.length() && + (isdigit(str[unit_start]) || str[unit_start] == '.')) { - string number_part = str.substr(0, unit_start); - string unit_part = str.substr(unit_start); - - // Check if unit is valid - if (unit_part == "px" || unit_part == "em" || unit_part == "rem" || - unit_part == "vh" || unit_part == "vw" || unit_part == "vmin" || - unit_part == "vmax" || unit_part == "%" || unit_part == "cm" || - unit_part == "mm" || unit_part == "in" || unit_part == "pt" || - unit_part == "pc") - { - double parsed_value = stod(number_part); - value = parsed_value; - unit = unit_part; - advance(); - return true; - } + unit_start++; } - catch (...) + + if (unit_start > 1 && unit_start < str.length()) { - // Not a valid number, fall through + try + { + string number_part = str.substr(0, unit_start); + string unit_part = str.substr(unit_start); + + // Check if unit is valid + if (unit_part == "px" || unit_part == "em" || unit_part == "rem" || + unit_part == "vh" || unit_part == "vw" || unit_part == "vmin" || + unit_part == "vmax" || unit_part == "%" || unit_part == "cm" || + unit_part == "mm" || unit_part == "in" || unit_part == "pt" || + unit_part == "pc") + { + double parsed_value = stod(number_part); + value = parsed_value; + unit = unit_part; + advance(); + return true; + } + } + catch (...) + { + // Not a valid number, fall through + } } } } - } - - return false; - } - bool CSSTransformParser::consumeAngle(double &value, string &unit) - { - if (isAtEnd()) - { return false; } - const Token &token = currentToken(); - - if (token.type == TokenType::kNumber && token.numeric_value == 0.0) + bool CSSTransformParser::consumeAngle(double &value, string &unit) { - // Zero is allowed without unit for angles - value = 0.0; - unit = "deg"; - advance(); - return true; - } - else if (token.type == TokenType::kDimension) - { - // Check if unit is a valid angle unit - const string &u = token.unit; - if (u == "deg" || u == "rad" || u == "grad" || u == "turn") + if (isAtEnd()) { - value = token.numeric_value; - unit = u; + return false; + } + + const Token &token = currentToken(); + + if (token.type == TokenType::kNumber && token.numeric_value == 0.0) + { + // Zero is allowed without unit for angles + value = 0.0; + unit = "deg"; advance(); return true; } - } - else if (token.type == TokenType::kIdentifier) - { - // Handle negative angles tokenized as identifiers (e.g., "-45deg", "-1.5rad") - const string &str = token.value; - if (!str.empty() && str[0] == '-') - { - // Try to extract number and unit - size_t unit_start = 1; // Start after the minus sign - while (unit_start < str.length() && - (isdigit(str[unit_start]) || str[unit_start] == '.')) + else if (token.type == TokenType::kDimension) + { + // Check if unit is a valid angle unit + const string &u = token.unit; + if (u == "deg" || u == "rad" || u == "grad" || u == "turn") { - unit_start++; + value = token.numeric_value; + unit = u; + advance(); + return true; } - - if (unit_start > 1 && unit_start < str.length()) + } + else if (token.type == TokenType::kIdentifier) + { + // Handle negative angles tokenized as identifiers (e.g., "-45deg", "-1.5rad") + const string &str = token.value; + if (!str.empty() && str[0] == '-') { - try + // Try to extract number and unit + size_t unit_start = 1; // Start after the minus sign + while (unit_start < str.length() && + (isdigit(str[unit_start]) || str[unit_start] == '.')) { - string number_part = str.substr(0, unit_start); - string unit_part = str.substr(unit_start); + unit_start++; + } - // Check if unit is valid - if (unit_part == "deg" || unit_part == "rad" || unit_part == "grad" || unit_part == "turn") + if (unit_start > 1 && unit_start < str.length()) + { + try { - double parsed_value = stod(number_part); - value = parsed_value; - unit = unit_part; - advance(); - return true; + string number_part = str.substr(0, unit_start); + string unit_part = str.substr(unit_start); + + // Check if unit is valid + if (unit_part == "deg" || unit_part == "rad" || unit_part == "grad" || unit_part == "turn") + { + double parsed_value = stod(number_part); + value = parsed_value; + unit = unit_part; + advance(); + return true; + } + } + catch (...) + { + // Not a valid number, fall through } - } - catch (...) - { - // Not a valid number, fall through } } } - } - return false; - } - - bool CSSTransformParser::isAtEnd() const - { - return current_token_index_ >= tokens_.size(); - } + return false; + } - const Token &CSSTransformParser::currentToken() const - { - static Token dummy_token(TokenType::kWhitespace); - if (isAtEnd()) + bool CSSTransformParser::isAtEnd() const { - return dummy_token; + return current_token_index_ >= tokens_.size(); } - return tokens_[current_token_index_]; - } - void CSSTransformParser::advance() - { - if (!isAtEnd()) + const Token &CSSTransformParser::currentToken() const { - current_token_index_++; + static Token dummy_token(TokenType::kWhitespace); + if (isAtEnd()) + { + return dummy_token; + } + return tokens_[current_token_index_]; } - } - - void CSSTransformParser::setError(const string &message) - { - error_message_ = message; - is_valid_ = false; - } - TransformFunctionType CSSTransformParser::getFunctionType(const string &name) - { - static const unordered_map function_map = { - {"matrix", TransformFunctionType::kMatrix}, - {"matrix3d", TransformFunctionType::kMatrix3D}, - {"translate", TransformFunctionType::kTranslate}, - {"translateX", TransformFunctionType::kTranslateX}, - {"translateY", TransformFunctionType::kTranslateY}, - {"translateZ", TransformFunctionType::kTranslateZ}, - {"translate3d", TransformFunctionType::kTranslate3D}, - {"scale", TransformFunctionType::kScale}, - {"scaleX", TransformFunctionType::kScaleX}, - {"scaleY", TransformFunctionType::kScaleY}, - {"scaleZ", TransformFunctionType::kScaleZ}, - {"scale3d", TransformFunctionType::kScale3D}, - {"rotate", TransformFunctionType::kRotate}, - {"rotateX", TransformFunctionType::kRotateX}, - {"rotateY", TransformFunctionType::kRotateY}, - {"rotateZ", TransformFunctionType::kRotateZ}, - {"rotate3d", TransformFunctionType::kRotate3D}, - {"skew", TransformFunctionType::kSkew}, - {"skewX", TransformFunctionType::kSkewX}, - {"skewY", TransformFunctionType::kSkewY}, - {"perspective", TransformFunctionType::kPerspective}}; - - auto it = function_map.find(name); - if (it != function_map.end()) + void CSSTransformParser::advance() { - return it->second; + if (!isAtEnd()) + { + current_token_index_++; + } } - // Return a default, error will be handled by caller - return TransformFunctionType::kMatrix; + void CSSTransformParser::setError(const string &message) + { + error_message_ = message; + is_valid_ = false; + } + + TransformFunctionType CSSTransformParser::getFunctionType(const string &name) + { + static const unordered_map function_map = { + {"matrix", TransformFunctionType::kMatrix}, + {"matrix3d", TransformFunctionType::kMatrix3D}, + {"translate", TransformFunctionType::kTranslate}, + {"translateX", TransformFunctionType::kTranslateX}, + {"translateY", TransformFunctionType::kTranslateY}, + {"translateZ", TransformFunctionType::kTranslateZ}, + {"translate3d", TransformFunctionType::kTranslate3D}, + {"scale", TransformFunctionType::kScale}, + {"scaleX", TransformFunctionType::kScaleX}, + {"scaleY", TransformFunctionType::kScaleY}, + {"scaleZ", TransformFunctionType::kScaleZ}, + {"scale3d", TransformFunctionType::kScale3D}, + {"rotate", TransformFunctionType::kRotate}, + {"rotateX", TransformFunctionType::kRotateX}, + {"rotateY", TransformFunctionType::kRotateY}, + {"rotateZ", TransformFunctionType::kRotateZ}, + {"rotate3d", TransformFunctionType::kRotate3D}, + {"skew", TransformFunctionType::kSkew}, + {"skewX", TransformFunctionType::kSkewX}, + {"skewY", TransformFunctionType::kSkewY}, + {"perspective", TransformFunctionType::kPerspective}}; + + auto it = function_map.find(name); + if (it != function_map.end()) + { + return it->second; + } + + // Return a default, error will be handled by caller + return TransformFunctionType::kMatrix; + } } -} +} // namespace endor diff --git a/src/client/cssom/parsers/css_transform_parser.hpp b/src/client/cssom/parsers/css_transform_parser.hpp index ba2d0085c..4d8d48ba1 100644 --- a/src/client/cssom/parsers/css_transform_parser.hpp +++ b/src/client/cssom/parsers/css_transform_parser.hpp @@ -6,112 +6,115 @@ #include #include "./css_value_tokenizer.hpp" -namespace client_cssom::css_transform_parser +namespace endor { - // Enum for different transform function types - enum class TransformFunctionType + namespace client_cssom::css_transform_parser { - kMatrix, - kMatrix3D, - kTranslate, - kTranslateX, - kTranslateY, - kTranslateZ, - kTranslate3D, - kScale, - kScaleX, - kScaleY, - kScaleZ, - kScale3D, - kRotate, - kRotateX, - kRotateY, - kRotateZ, - kRotate3D, - kSkew, - kSkewX, - kSkewY, - kPerspective - }; - - // Structure to hold parsed transform function data - struct TransformFunction - { - TransformFunctionType type; - std::vector values; // Numeric values - std::vector units; // Units for each value - - TransformFunction(TransformFunctionType t) - : type(t) + // Enum for different transform function types + enum class TransformFunctionType { - } - }; + kMatrix, + kMatrix3D, + kTranslate, + kTranslateX, + kTranslateY, + kTranslateZ, + kTranslate3D, + kScale, + kScaleX, + kScaleY, + kScaleZ, + kScale3D, + kRotate, + kRotateX, + kRotateY, + kRotateZ, + kRotate3D, + kSkew, + kSkewX, + kSkewY, + kPerspective + }; - // Main parser class - class CSSTransformParser - { - public: - explicit CSSTransformParser(const std::string &input); + // Structure to hold parsed transform function data + struct TransformFunction + { + TransformFunctionType type; + std::vector values; // Numeric values + std::vector units; // Units for each value - // Parse the transform string and return list of functions - std::vector parse(); + TransformFunction(TransformFunctionType t) + : type(t) + { + } + }; - // Check if parsing was successful - bool isValid() const + // Main parser class + class CSSTransformParser { - return is_valid_; - } + public: + explicit CSSTransformParser(const std::string &input); - // Get error message if parsing failed - const std::string &getError() const - { - return error_message_; - } + // Parse the transform string and return list of functions + std::vector parse(); + + // Check if parsing was successful + bool isValid() const + { + return is_valid_; + } + + // Get error message if parsing failed + const std::string &getError() const + { + return error_message_; + } - private: - std::string input_; - css_value_tokenizer::CSSValueTokenizer tokenizer_; - std::vector tokens_; - size_t current_token_index_; - bool is_valid_; - std::string error_message_; + private: + std::string input_; + css_value_tokenizer::CSSValueTokenizer tokenizer_; + std::vector tokens_; + size_t current_token_index_; + bool is_valid_; + std::string error_message_; - // Parse individual transform functions - std::optional parseTransformFunction(); - std::optional parseMatrix(); - std::optional parseMatrix3D(); - std::optional parseTranslate(); - std::optional parseTranslateX(); - std::optional parseTranslateY(); - std::optional parseTranslateZ(); - std::optional parseTranslate3D(); - std::optional parseScale(); - std::optional parseScaleX(); - std::optional parseScaleY(); - std::optional parseScaleZ(); - std::optional parseScale3D(); - std::optional parseRotate(); - std::optional parseRotateX(); - std::optional parseRotateY(); - std::optional parseRotateZ(); - std::optional parseRotate3D(); - std::optional parseSkew(); - std::optional parseSkewX(); - std::optional parseSkewY(); - std::optional parsePerspective(); + // Parse individual transform functions + std::optional parseTransformFunction(); + std::optional parseMatrix(); + std::optional parseMatrix3D(); + std::optional parseTranslate(); + std::optional parseTranslateX(); + std::optional parseTranslateY(); + std::optional parseTranslateZ(); + std::optional parseTranslate3D(); + std::optional parseScale(); + std::optional parseScaleX(); + std::optional parseScaleY(); + std::optional parseScaleZ(); + std::optional parseScale3D(); + std::optional parseRotate(); + std::optional parseRotateX(); + std::optional parseRotateY(); + std::optional parseRotateZ(); + std::optional parseRotate3D(); + std::optional parseSkew(); + std::optional parseSkewX(); + std::optional parseSkewY(); + std::optional parsePerspective(); - // Helper methods - bool consumeToken(css_value_tokenizer::TokenType expected_type); - bool consumeComma(); - bool consumeNumber(double &value, std::string &unit); - bool consumeLength(double &value, std::string &unit); - bool consumeAngle(double &value, std::string &unit); - bool isAtEnd() const; - const css_value_tokenizer::Token ¤tToken() const; - void advance(); - void setError(const std::string &message); + // Helper methods + bool consumeToken(css_value_tokenizer::TokenType expected_type); + bool consumeComma(); + bool consumeNumber(double &value, std::string &unit); + bool consumeLength(double &value, std::string &unit); + bool consumeAngle(double &value, std::string &unit); + bool isAtEnd() const; + const css_value_tokenizer::Token ¤tToken() const; + void advance(); + void setError(const std::string &message); - // Transform function name to type mapping - static TransformFunctionType getFunctionType(const std::string &name); - }; -} + // Transform function name to type mapping + static TransformFunctionType getFunctionType(const std::string &name); + }; + } +} // namespace endor diff --git a/src/client/cssom/parsers/css_value_tokenizer.cpp b/src/client/cssom/parsers/css_value_tokenizer.cpp index a2b00a2dd..42a8cc81f 100644 --- a/src/client/cssom/parsers/css_value_tokenizer.cpp +++ b/src/client/cssom/parsers/css_value_tokenizer.cpp @@ -3,434 +3,437 @@ #include "./css_value_tokenizer.hpp" -namespace client_cssom::css_value_tokenizer +namespace endor { - using namespace std; - - CSSValueTokenizer::CSSValueTokenizer(const string &input) - : input_(input) - , position_(0) - , length_(input.length()) - { - } - - vector CSSValueTokenizer::tokenize() - { - vector tokens; - reset(); - - while (hasNext()) - { - Token token = nextToken(); - if (token.type != TokenType::kWhitespace) - { - tokens.push_back(token); - } - } - - return tokens; - } - - Token CSSValueTokenizer::nextToken() + namespace client_cssom::css_value_tokenizer { - skip_whitespace(); + using namespace std; - if (position_ >= length_) + CSSValueTokenizer::CSSValueTokenizer(const string &input) + : input_(input) + , position_(0) + , length_(input.length()) { - return Token(TokenType::kWhitespace); // Return dummy token when done } - char c = current_char(); - size_t token_start = position_; - - // String literals - if (c == '"' || c == '\'') - { - auto token = consume_string(c); - token.start_position = token_start; - token.end_position = position_; - return token; - } - - // Numbers (including negative numbers) - if (is_digit(c) || (c == '.' && is_digit(peek_char())) || - (c == '-' && (is_digit(peek_char()) || (peek_char() == '.' && position_ + 2 < length_ && is_digit(peek_char(2)))))) - { - auto token = consume_number(); - token.start_position = token_start; - token.end_position = position_; - return token; - } - - // Identifiers and functions - if (is_identifier_start(c)) + vector CSSValueTokenizer::tokenize() { - string identifier = consume_identifier_sequence(); + vector tokens; + reset(); - // Check if it's a function - if (position_ < length_ && current_char() == '(') + while (hasNext()) { - if (identifier == "url" || identifier == "src") + Token token = nextToken(); + if (token.type != TokenType::kWhitespace) { - auto token = consume_url(); - token.start_position = token_start; - token.end_position = position_; - return token; - } - else - { - auto token = consume_function(identifier); - token.start_position = token_start; - token.end_position = position_; - return token; + tokens.push_back(token); } } - return Token(TokenType::kIdentifier, identifier); - } - - // Single character tokens - switch (c) - { - case '(': - advance(); - return Token(TokenType::kLeftParen, "("); - case ')': - advance(); - return Token(TokenType::kRightParen, ")"); - case ',': - advance(); - return Token(TokenType::kComma, ","); - case '#': - { - auto token = consume_hash(); - token.start_position = token_start; - token.end_position = position_; - return token; - } - default: - advance(); - return Token(TokenType::kDelimiter, string(1, c)); - } - } - - bool CSSValueTokenizer::hasNext() const - { - return position_ < length_; - } - - void CSSValueTokenizer::reset() - { - position_ = 0; - } - - char CSSValueTokenizer::current_char() const - { - return position_ < length_ ? input_[position_] : '\0'; - } - - char CSSValueTokenizer::peek_char(size_t offset) const - { - size_t peek_pos = position_ + offset; - return peek_pos < length_ ? input_[peek_pos] : '\0'; - } - - void CSSValueTokenizer::advance() - { - if (position_ < length_) - { - position_++; + return tokens; } - } - void CSSValueTokenizer::skip_whitespace() - { - while (position_ < length_ && is_whitespace(current_char())) + Token CSSValueTokenizer::nextToken() { - advance(); - } - } - - bool CSSValueTokenizer::is_whitespace(char c) const - { - return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; - } + skip_whitespace(); - bool CSSValueTokenizer::is_letter(char c) const - { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); - } + if (position_ >= length_) + { + return Token(TokenType::kWhitespace); // Return dummy token when done + } - bool CSSValueTokenizer::is_digit(char c) const - { - return c >= '0' && c <= '9'; - } + char c = current_char(); + size_t token_start = position_; - bool CSSValueTokenizer::is_hex_digit(char c) const - { - return is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); - } + // String literals + if (c == '"' || c == '\'') + { + auto token = consume_string(c); + token.start_position = token_start; + token.end_position = position_; + return token; + } - bool CSSValueTokenizer::is_identifier_start(char c) const - { - return is_letter(c) || c == '_' || c == '-' || static_cast(c) >= 0x80; - } + // Numbers (including negative numbers) + if (is_digit(c) || (c == '.' && is_digit(peek_char())) || + (c == '-' && (is_digit(peek_char()) || (peek_char() == '.' && position_ + 2 < length_ && is_digit(peek_char(2)))))) + { + auto token = consume_number(); + token.start_position = token_start; + token.end_position = position_; + return token; + } - bool CSSValueTokenizer::is_identifier_char(char c) const - { - return is_identifier_start(c) || is_digit(c); - } + // Identifiers and functions + if (is_identifier_start(c)) + { + string identifier = consume_identifier_sequence(); - Token CSSValueTokenizer::consume_identifier() - { - return Token(TokenType::kIdentifier, consume_identifier_sequence()); - } + // Check if it's a function + if (position_ < length_ && current_char() == '(') + { + if (identifier == "url" || identifier == "src") + { + auto token = consume_url(); + token.start_position = token_start; + token.end_position = position_; + return token; + } + else + { + auto token = consume_function(identifier); + token.start_position = token_start; + token.end_position = position_; + return token; + } + } - Token CSSValueTokenizer::consume_string(char quote_char) - { - advance(); // Skip opening quote - string value; + return Token(TokenType::kIdentifier, identifier); + } - while (position_ < length_ && current_char() != quote_char) - { - char c = current_char(); - if (c == '\\') + // Single character tokens + switch (c) { - consume_escape_sequence(value); - } - else if (c == '\n' || c == '\r' || c == '\f') + case '(': + advance(); + return Token(TokenType::kLeftParen, "("); + case ')': + advance(); + return Token(TokenType::kRightParen, ")"); + case ',': + advance(); + return Token(TokenType::kComma, ","); + case '#': { - // Bad string - return Token(TokenType::kBadString, value); + auto token = consume_hash(); + token.start_position = token_start; + token.end_position = position_; + return token; } - else - { - value += c; + default: advance(); + return Token(TokenType::kDelimiter, string(1, c)); } } - if (position_ < length_ && current_char() == quote_char) + bool CSSValueTokenizer::hasNext() const { - advance(); // Skip closing quote + return position_ < length_; } - return Token(TokenType::kString, value); - } + void CSSValueTokenizer::reset() + { + position_ = 0; + } - Token CSSValueTokenizer::consume_number() - { - string number_str; + char CSSValueTokenizer::current_char() const + { + return position_ < length_ ? input_[position_] : '\0'; + } - // Handle negative sign - if (position_ < length_ && current_char() == '-') + char CSSValueTokenizer::peek_char(size_t offset) const { - number_str += current_char(); - advance(); + size_t peek_pos = position_ + offset; + return peek_pos < length_ ? input_[peek_pos] : '\0'; } - // Consume integer part - while (position_ < length_ && is_digit(current_char())) + void CSSValueTokenizer::advance() { - number_str += current_char(); - advance(); + if (position_ < length_) + { + position_++; + } } - // Consume decimal part - if (position_ < length_ && current_char() == '.' && - position_ + 1 < length_ && is_digit(peek_char())) + void CSSValueTokenizer::skip_whitespace() { - number_str += current_char(); - advance(); - while (position_ < length_ && is_digit(current_char())) + while (position_ < length_ && is_whitespace(current_char())) { - number_str += current_char(); advance(); } } - // Convert to number - double numeric_value = stod(number_str); + bool CSSValueTokenizer::is_whitespace(char c) const + { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; + } - // Check for percentage - if (position_ < length_ && current_char() == '%') + bool CSSValueTokenizer::is_letter(char c) const { - advance(); - return Token(TokenType::kPercentage, number_str + "%", numeric_value); + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } - // Check for dimension (unit) - if (position_ < length_ && is_identifier_start(current_char())) + bool CSSValueTokenizer::is_digit(char c) const { - string unit = consume_identifier_sequence(); - return Token(TokenType::kDimension, number_str + unit, unit, numeric_value); + return c >= '0' && c <= '9'; } - return Token(TokenType::kNumber, number_str, numeric_value); - } + bool CSSValueTokenizer::is_hex_digit(char c) const + { + return is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } - Token CSSValueTokenizer::consume_url() - { - advance(); // Skip '(' - skip_whitespace(); + bool CSSValueTokenizer::is_identifier_start(char c) const + { + return is_letter(c) || c == '_' || c == '-' || static_cast(c) >= 0x80; + } - string url_value; + bool CSSValueTokenizer::is_identifier_char(char c) const + { + return is_identifier_start(c) || is_digit(c); + } - // Check if it starts with a quote - if (position_ < length_ && (current_char() == '"' || current_char() == '\'')) + Token CSSValueTokenizer::consume_identifier() { - char quote_char = current_char(); - Token string_token = consume_string(quote_char); - if (string_token.type == TokenType::kBadString) - { - return Token(TokenType::kBadUrl, url_value); - } - url_value = string_token.value; + return Token(TokenType::kIdentifier, consume_identifier_sequence()); } - else + + Token CSSValueTokenizer::consume_string(char quote_char) { - // Unquoted URL - while (position_ < length_ && current_char() != ')' && !is_whitespace(current_char())) + advance(); // Skip opening quote + string value; + + while (position_ < length_ && current_char() != quote_char) { char c = current_char(); if (c == '\\') { - consume_escape_sequence(url_value); + consume_escape_sequence(value); } - else if (c == '"' || c == '\'' || c == '(' || static_cast(c) < 0x20) + else if (c == '\n' || c == '\r' || c == '\f') { - // Bad URL - return Token(TokenType::kBadUrl, url_value); + // Bad string + return Token(TokenType::kBadString, value); } else { - url_value += c; + value += c; advance(); } } - } - skip_whitespace(); + if (position_ < length_ && current_char() == quote_char) + { + advance(); // Skip closing quote + } - if (position_ < length_ && current_char() == ')') - { - advance(); // Skip ')' - return Token(TokenType::kUrl, url_value); + return Token(TokenType::kString, value); } - return Token(TokenType::kBadUrl, url_value); - } + Token CSSValueTokenizer::consume_number() + { + string number_str; - Token CSSValueTokenizer::consume_function(const string &name) - { - advance(); // Skip '(' - return Token(TokenType::kFunction, name); - } + // Handle negative sign + if (position_ < length_ && current_char() == '-') + { + number_str += current_char(); + advance(); + } - Token CSSValueTokenizer::consume_hash() - { - advance(); // Skip '#' - string hash_value; + // Consume integer part + while (position_ < length_ && is_digit(current_char())) + { + number_str += current_char(); + advance(); + } - // Consume hex digits for hex color format - while (position_ < length_ && is_hex_digit(current_char())) - { - hash_value += current_char(); - advance(); - } + // Consume decimal part + if (position_ < length_ && current_char() == '.' && + position_ + 1 < length_ && is_digit(peek_char())) + { + number_str += current_char(); + advance(); + while (position_ < length_ && is_digit(current_char())) + { + number_str += current_char(); + advance(); + } + } - // If no hex digits found or invalid length, treat as identifier - if (hash_value.empty() || (hash_value.length() != 3 && hash_value.length() != 4 && - hash_value.length() != 6 && hash_value.length() != 8)) - { - // Continue consuming as identifier characters - while (position_ < length_ && is_identifier_char(current_char())) + // Convert to number + double numeric_value = stod(number_str); + + // Check for percentage + if (position_ < length_ && current_char() == '%') { - hash_value += current_char(); advance(); + return Token(TokenType::kPercentage, number_str + "%", numeric_value); } - } - return Token(TokenType::kHash, hash_value); - } + // Check for dimension (unit) + if (position_ < length_ && is_identifier_start(current_char())) + { + string unit = consume_identifier_sequence(); + return Token(TokenType::kDimension, number_str + unit, unit, numeric_value); + } - string CSSValueTokenizer::consume_identifier_sequence() - { - string identifier; + return Token(TokenType::kNumber, number_str, numeric_value); + } - while (position_ < length_ && is_identifier_char(current_char())) + Token CSSValueTokenizer::consume_url() { - char c = current_char(); - if (c == '\\') + advance(); // Skip '(' + skip_whitespace(); + + string url_value; + + // Check if it starts with a quote + if (position_ < length_ && (current_char() == '"' || current_char() == '\'')) { - consume_escape_sequence(identifier); + char quote_char = current_char(); + Token string_token = consume_string(quote_char); + if (string_token.type == TokenType::kBadString) + { + return Token(TokenType::kBadUrl, url_value); + } + url_value = string_token.value; } else { - identifier += c; - advance(); + // Unquoted URL + while (position_ < length_ && current_char() != ')' && !is_whitespace(current_char())) + { + char c = current_char(); + if (c == '\\') + { + consume_escape_sequence(url_value); + } + else if (c == '"' || c == '\'' || c == '(' || static_cast(c) < 0x20) + { + // Bad URL + return Token(TokenType::kBadUrl, url_value); + } + else + { + url_value += c; + advance(); + } + } } - } - return identifier; - } + skip_whitespace(); - void CSSValueTokenizer::consume_escape_sequence(string &result) - { - advance(); // Skip '\' + if (position_ < length_ && current_char() == ')') + { + advance(); // Skip ')' + return Token(TokenType::kUrl, url_value); + } + + return Token(TokenType::kBadUrl, url_value); + } - if (position_ >= length_) + Token CSSValueTokenizer::consume_function(const string &name) { - return; + advance(); // Skip '(' + return Token(TokenType::kFunction, name); } - char c = current_char(); - if (is_hex_digit(c)) + Token CSSValueTokenizer::consume_hash() { - // Hex escape sequence - string hex_digits; - for (int i = 0; i < 6 && position_ < length_ && is_hex_digit(current_char()); i++) - { - hex_digits += current_char(); - advance(); - } + advance(); // Skip '#' + string hash_value; - // Skip optional whitespace after hex digits - if (position_ < length_ && is_whitespace(current_char())) + // Consume hex digits for hex color format + while (position_ < length_ && is_hex_digit(current_char())) { + hash_value += current_char(); advance(); } - // Convert hex to character - if (!hex_digits.empty()) + // If no hex digits found or invalid length, treat as identifier + if (hash_value.empty() || (hash_value.length() != 3 && hash_value.length() != 4 && + hash_value.length() != 6 && hash_value.length() != 8)) { - int codepoint = stoi(hex_digits, nullptr, 16); - if (codepoint == 0 || codepoint > 0x10FFFF) + // Continue consuming as identifier characters + while (position_ < length_ && is_identifier_char(current_char())) { - result += "�"; // Replacement character + hash_value += current_char(); + advance(); } - else if (codepoint < 0x80) + } + + return Token(TokenType::kHash, hash_value); + } + + string CSSValueTokenizer::consume_identifier_sequence() + { + string identifier; + + while (position_ < length_ && is_identifier_char(current_char())) + { + char c = current_char(); + if (c == '\\') { - // Simple ASCII handling - result += static_cast(codepoint); + consume_escape_sequence(identifier); } else { - // For non-ASCII, just add the replacement character for now - result += "�"; + identifier += c; + advance(); } } + + return identifier; } - else if (c == '\n' || c == '\r' || c == '\f') - { - // Invalid escape sequence - return; - } - else + + void CSSValueTokenizer::consume_escape_sequence(string &result) { - // Any other character - result += c; - advance(); + advance(); // Skip '\' + + if (position_ >= length_) + { + return; + } + + char c = current_char(); + if (is_hex_digit(c)) + { + // Hex escape sequence + string hex_digits; + for (int i = 0; i < 6 && position_ < length_ && is_hex_digit(current_char()); i++) + { + hex_digits += current_char(); + advance(); + } + + // Skip optional whitespace after hex digits + if (position_ < length_ && is_whitespace(current_char())) + { + advance(); + } + + // Convert hex to character + if (!hex_digits.empty()) + { + int codepoint = stoi(hex_digits, nullptr, 16); + if (codepoint == 0 || codepoint > 0x10FFFF) + { + result += "�"; // Replacement character + } + else if (codepoint < 0x80) + { + // Simple ASCII handling + result += static_cast(codepoint); + } + else + { + // For non-ASCII, just add the replacement character for now + result += "�"; + } + } + } + else if (c == '\n' || c == '\r' || c == '\f') + { + // Invalid escape sequence + return; + } + else + { + // Any other character + result += c; + advance(); + } } } -} +} // namespace endor diff --git a/src/client/cssom/parsers/css_value_tokenizer.hpp b/src/client/cssom/parsers/css_value_tokenizer.hpp index fcb7d5dd3..359040764 100644 --- a/src/client/cssom/parsers/css_value_tokenizer.hpp +++ b/src/client/cssom/parsers/css_value_tokenizer.hpp @@ -4,89 +4,92 @@ #include #include -namespace client_cssom::css_value_tokenizer +namespace endor { - enum class TokenType + namespace client_cssom::css_value_tokenizer { - kIdentifier, - kFunction, - kString, - kUrl, - kNumber, - kPercentage, - kDimension, - kHash, - kDelimiter, - kComma, - kLeftParen, - kRightParen, - kWhitespace, - kBadString, - kBadUrl - }; - - struct Token - { - TokenType type; - std::string value; - std::string unit; // For dimension tokens - double numeric_value = 0.0; // For number/percentage/dimension tokens - size_t start_position = 0; - size_t end_position = 0; - - Token(TokenType t, const std::string &v = "") - : type(t) - , value(v) - { - } - Token(TokenType t, const std::string &v, double num) - : type(t) - , value(v) - , numeric_value(num) + enum class TokenType { - } - Token(TokenType t, const std::string &v, const std::string &u, double num) - : type(t) - , value(v) - , unit(u) - , numeric_value(num) + kIdentifier, + kFunction, + kString, + kUrl, + kNumber, + kPercentage, + kDimension, + kHash, + kDelimiter, + kComma, + kLeftParen, + kRightParen, + kWhitespace, + kBadString, + kBadUrl + }; + + struct Token { - } - }; + TokenType type; + std::string value; + std::string unit; // For dimension tokens + double numeric_value = 0.0; // For number/percentage/dimension tokens + size_t start_position = 0; + size_t end_position = 0; - class CSSValueTokenizer - { - public: - explicit CSSValueTokenizer(const std::string &input); + Token(TokenType t, const std::string &v = "") + : type(t) + , value(v) + { + } + Token(TokenType t, const std::string &v, double num) + : type(t) + , value(v) + , numeric_value(num) + { + } + Token(TokenType t, const std::string &v, const std::string &u, double num) + : type(t) + , value(v) + , unit(u) + , numeric_value(num) + { + } + }; + + class CSSValueTokenizer + { + public: + explicit CSSValueTokenizer(const std::string &input); - std::vector tokenize(); - Token nextToken(); - bool hasNext() const; - void reset(); + std::vector tokenize(); + Token nextToken(); + bool hasNext() const; + void reset(); - private: - std::string input_; - size_t position_; - size_t length_; + private: + std::string input_; + size_t position_; + size_t length_; - char current_char() const; - char peek_char(size_t offset = 1) const; - void advance(); - void skip_whitespace(); - bool is_whitespace(char c) const; - bool is_letter(char c) const; - bool is_digit(char c) const; - bool is_hex_digit(char c) const; - bool is_identifier_start(char c) const; - bool is_identifier_char(char c) const; + char current_char() const; + char peek_char(size_t offset = 1) const; + void advance(); + void skip_whitespace(); + bool is_whitespace(char c) const; + bool is_letter(char c) const; + bool is_digit(char c) const; + bool is_hex_digit(char c) const; + bool is_identifier_start(char c) const; + bool is_identifier_char(char c) const; - Token consume_identifier(); - Token consume_string(char quote_char); - Token consume_number(); - Token consume_url(); - Token consume_function(const std::string &name); - Token consume_hash(); - std::string consume_identifier_sequence(); - void consume_escape_sequence(std::string &result); - }; -} + Token consume_identifier(); + Token consume_string(char quote_char); + Token consume_number(); + Token consume_url(); + Token consume_function(const std::string &name); + Token consume_hash(); + std::string consume_identifier_sequence(); + void consume_escape_sequence(std::string &result); + }; + } +} // namespace endor diff --git a/src/client/cssom/parsers/css_variable_parser.cpp b/src/client/cssom/parsers/css_variable_parser.cpp index 702e69901..d6d6b803d 100644 --- a/src/client/cssom/parsers/css_variable_parser.cpp +++ b/src/client/cssom/parsers/css_variable_parser.cpp @@ -2,236 +2,191 @@ #include #include -namespace client_cssom::css_variable_parser +namespace endor { - using namespace std; - - CSSVariableParser::CSSVariableParser(const string &input) - : input_(input) - , is_valid_(true) + namespace client_cssom::css_variable_parser { - } + using namespace std; - string CSSVariableParser::resolveVariables(const VariableResolver &resolver) - { - if (input_.empty()) + CSSVariableParser::CSSVariableParser(const string &input) + : input_(input) + , is_valid_(true) { - return input_; } - return resolveVariablesRecursive(input_, resolver, 0); - } - - string CSSVariableParser::resolveVariablesRecursive(const string &value, const VariableResolver &resolver, int depth) - { - // Prevent infinite recursion - if (depth > MAX_RECURSION_DEPTH) + string CSSVariableParser::resolveVariables(const VariableResolver &resolver) { - is_valid_ = false; - error_message_ = "Maximum recursion depth exceeded while resolving CSS variables"; - return value; - } + if (input_.empty()) + { + return input_; + } - string result = value; + return resolveVariablesRecursive(input_, resolver, 0); + } - // Keep processing until no more variables are found - bool found_variable = true; - while (found_variable) + string CSSVariableParser::resolveVariablesRecursive(const string &value, const VariableResolver &resolver, int depth) { - found_variable = false; - size_t pos = 0; + // Prevent infinite recursion + if (depth > MAX_RECURSION_DEPTH) + { + is_valid_ = false; + error_message_ = "Maximum recursion depth exceeded while resolving CSS variables"; + return value; + } - // Find the first var() in the current result string - while (pos < result.length()) + string result = value; + + // Keep processing until no more variables are found + bool found_variable = true; + while (found_variable) { - size_t var_pos = result.find("var(", pos); - if (var_pos == string::npos) - { - break; // No more variables found - } + found_variable = false; + size_t pos = 0; - // Parse this variable - CSSVariableParser temp_parser(result.substr(var_pos)); - auto var_ref = temp_parser.findNextVariable(0); - if (!var_ref.has_value()) + // Find the first var() in the current result string + while (pos < result.length()) { - pos = var_pos + 4; // Skip this "var(" and continue - continue; - } + size_t var_pos = result.find("var(", pos); + if (var_pos == string::npos) + { + break; // No more variables found + } - const auto &var = var_ref.value(); + // Parse this variable + CSSVariableParser temp_parser(result.substr(var_pos)); + auto var_ref = temp_parser.findNextVariable(0); + if (!var_ref.has_value()) + { + pos = var_pos + 4; // Skip this "var(" and continue + continue; + } - // Adjust positions to be relative to the result string - size_t actual_start = var_pos + var.start_position; - size_t actual_end = var_pos + var.end_position; + const auto &var = var_ref.value(); - // Try to resolve the variable - auto resolved_value = resolver(var.variable_name); + // Adjust positions to be relative to the result string + size_t actual_start = var_pos + var.start_position; + size_t actual_end = var_pos + var.end_position; - string replacement; - if (resolved_value.has_value()) - { - // Variable found, use its value (don't recurse yet, we'll handle it in the next iteration) - replacement = resolved_value.value(); - } - else if (var.fallback_value.has_value()) - { - // Variable not found, use fallback (don't recurse yet, we'll handle it in the next iteration) - replacement = var.fallback_value.value(); - } - else - { - // No variable found and no fallback, keep the original var() function - pos = actual_end + 1; - continue; - } + // Try to resolve the variable + auto resolved_value = resolver(var.variable_name); - // Replace the var() function with the resolved value - result.replace(actual_start, actual_end - actual_start + 1, replacement); + string replacement; + if (resolved_value.has_value()) + { + // Variable found, use its value (don't recurse yet, we'll handle it in the next iteration) + replacement = resolved_value.value(); + } + else if (var.fallback_value.has_value()) + { + // Variable not found, use fallback (don't recurse yet, we'll handle it in the next iteration) + replacement = var.fallback_value.value(); + } + else + { + // No variable found and no fallback, keep the original var() function + pos = actual_end + 1; + continue; + } - // Mark that we found and replaced a variable - found_variable = true; - break; // Start over from the beginning after each replacement - } - } + // Replace the var() function with the resolved value + result.replace(actual_start, actual_end - actual_start + 1, replacement); - return result; - } + // Mark that we found and replaced a variable + found_variable = true; + break; // Start over from the beginning after each replacement + } + } - vector CSSVariableParser::getVariableReferences() - { - vector references; - size_t pos = 0; + return result; + } - while (pos < input_.length()) + vector CSSVariableParser::getVariableReferences() { - auto var_ref = findNextVariable(pos); - if (!var_ref.has_value()) + vector references; + size_t pos = 0; + + while (pos < input_.length()) { - break; + auto var_ref = findNextVariable(pos); + if (!var_ref.has_value()) + { + break; + } + + references.push_back(var_ref.value()); + pos = var_ref.value().end_position + 1; } - references.push_back(var_ref.value()); - pos = var_ref.value().end_position + 1; + return references; } - return references; - } - - optional CSSVariableParser::findNextVariable(size_t start_pos) - { - // Look for "var(" pattern - size_t var_pos = input_.find("var(", start_pos); - if (var_pos == string::npos) + optional CSSVariableParser::findNextVariable(size_t start_pos) { - return nullopt; - } - - return parseVariableContent(var_pos); - } + // Look for "var(" pattern + size_t var_pos = input_.find("var(", start_pos); + if (var_pos == string::npos) + { + return nullopt; + } - optional CSSVariableParser::parseVariableContent(size_t start_pos) - { - // start_pos should point to the 'v' in "var(" - if (start_pos + 4 >= input_.length() || input_.substr(start_pos, 4) != "var(") - { - return nullopt; + return parseVariableContent(var_pos); } - size_t content_start = start_pos + 4; // Skip "var(" - size_t paren_end = findMatchingParen(content_start - 1); // -1 to point to the opening paren - - if (paren_end == string::npos) + optional CSSVariableParser::parseVariableContent(size_t start_pos) { - is_valid_ = false; - error_message_ = "Unclosed var() function"; - return nullopt; - } - - // Extract content between parentheses - string content = input_.substr(content_start, paren_end - content_start); - - // Parse variable name and fallback - auto [var_name, fallback] = parseVariableNameAndFallback(content); + // start_pos should point to the 'v' in "var(" + if (start_pos + 4 >= input_.length() || input_.substr(start_pos, 4) != "var(") + { + return nullopt; + } - if (var_name.empty()) - { - is_valid_ = false; - error_message_ = "Empty variable name in var() function"; - return nullopt; - } + size_t content_start = start_pos + 4; // Skip "var(" + size_t paren_end = findMatchingParen(content_start - 1); // -1 to point to the opening paren - if (fallback.has_value()) - { - return VariableReference(var_name, fallback.value(), start_pos, paren_end); - } - else - { - return VariableReference(var_name, start_pos, paren_end); - } - } + if (paren_end == string::npos) + { + is_valid_ = false; + error_message_ = "Unclosed var() function"; + return nullopt; + } - size_t CSSVariableParser::findMatchingParen(size_t start_pos) - { - if (start_pos >= input_.length() || input_[start_pos] != '(') - { - return string::npos; - } + // Extract content between parentheses + string content = input_.substr(content_start, paren_end - content_start); - int paren_count = 1; - size_t pos = start_pos + 1; + // Parse variable name and fallback + auto [var_name, fallback] = parseVariableNameAndFallback(content); - while (pos < input_.length() && paren_count > 0) - { - char c = input_[pos]; - if (c == '(') + if (var_name.empty()) { - paren_count++; + is_valid_ = false; + error_message_ = "Empty variable name in var() function"; + return nullopt; } - else if (c == ')') + + if (fallback.has_value()) { - paren_count--; + return VariableReference(var_name, fallback.value(), start_pos, paren_end); } - else if (c == '"' || c == '\'') + else { - // Skip quoted strings to avoid counting parentheses inside them - char quote = c; - pos++; - while (pos < input_.length() && input_[pos] != quote) - { - if (input_[pos] == '\\') - { - pos++; // Skip escaped character - } - pos++; - } + return VariableReference(var_name, start_pos, paren_end); } - pos++; } - return (paren_count == 0) ? pos - 1 : string::npos; - } - - pair> CSSVariableParser::parseVariableNameAndFallback(const string &content) - { - // Find the first comma that's not inside parentheses or quotes - size_t comma_pos = string::npos; - int paren_count = 0; - bool in_quotes = false; - char quote_char = '\0'; - - for (size_t i = 0; i < content.length(); i++) + size_t CSSVariableParser::findMatchingParen(size_t start_pos) { - char c = content[i]; + if (start_pos >= input_.length() || input_[start_pos] != '(') + { + return string::npos; + } - if (!in_quotes) + int paren_count = 1; + size_t pos = start_pos + 1; + + while (pos < input_.length() && paren_count > 0) { - if (c == '"' || c == '\'') - { - in_quotes = true; - quote_char = c; - } - else if (c == '(') + char c = input_[pos]; + if (c == '(') { paren_count++; } @@ -239,47 +194,95 @@ namespace client_cssom::css_variable_parser { paren_count--; } - else if (c == ',' && paren_count == 0) + else if (c == '"' || c == '\'') { - comma_pos = i; - break; + // Skip quoted strings to avoid counting parentheses inside them + char quote = c; + pos++; + while (pos < input_.length() && input_[pos] != quote) + { + if (input_[pos] == '\\') + { + pos++; // Skip escaped character + } + pos++; + } } + pos++; } - else + + return (paren_count == 0) ? pos - 1 : string::npos; + } + + pair> CSSVariableParser::parseVariableNameAndFallback(const string &content) + { + // Find the first comma that's not inside parentheses or quotes + size_t comma_pos = string::npos; + int paren_count = 0; + bool in_quotes = false; + char quote_char = '\0'; + + for (size_t i = 0; i < content.length(); i++) { - if (c == quote_char) + char c = content[i]; + + if (!in_quotes) { - in_quotes = false; + if (c == '"' || c == '\'') + { + in_quotes = true; + quote_char = c; + } + else if (c == '(') + { + paren_count++; + } + else if (c == ')') + { + paren_count--; + } + else if (c == ',' && paren_count == 0) + { + comma_pos = i; + break; + } } - else if (c == '\\') + else { - i++; // Skip escaped character + if (c == quote_char) + { + in_quotes = false; + } + else if (c == '\\') + { + i++; // Skip escaped character + } } } - } - if (comma_pos == string::npos) - { - // No fallback - return {trim(content), nullopt}; - } - else - { - // Has fallback - string var_name = trim(content.substr(0, comma_pos)); - string fallback = trim(content.substr(comma_pos + 1)); - return {var_name, fallback}; + if (comma_pos == string::npos) + { + // No fallback + return {trim(content), nullopt}; + } + else + { + // Has fallback + string var_name = trim(content.substr(0, comma_pos)); + string fallback = trim(content.substr(comma_pos + 1)); + return {var_name, fallback}; + } } - } - string CSSVariableParser::trim(const string &str) - { - auto start = str.find_first_not_of(" \t\n\r\f"); - if (start == string::npos) + string CSSVariableParser::trim(const string &str) { - return ""; + auto start = str.find_first_not_of(" \t\n\r\f"); + if (start == string::npos) + { + return ""; + } + auto end = str.find_last_not_of(" \t\n\r\f"); + return str.substr(start, end - start + 1); } - auto end = str.find_last_not_of(" \t\n\r\f"); - return str.substr(start, end - start + 1); } -} +} // namespace endor diff --git a/src/client/cssom/parsers/css_variable_parser.hpp b/src/client/cssom/parsers/css_variable_parser.hpp index 0953e04d2..5ca152457 100644 --- a/src/client/cssom/parsers/css_variable_parser.hpp +++ b/src/client/cssom/parsers/css_variable_parser.hpp @@ -6,84 +6,87 @@ #include #include "./css_value_tokenizer.hpp" -namespace client_cssom::css_variable_parser +namespace endor { - // Structure to hold parsed variable information - struct VariableReference + namespace client_cssom::css_variable_parser { - std::string variable_name; - std::optional fallback_value; - size_t start_position; - size_t end_position; - - VariableReference(const std::string &name, size_t start, size_t end) - : variable_name(name) - , start_position(start) - , end_position(end) + // Structure to hold parsed variable information + struct VariableReference { - } - - VariableReference(const std::string &name, const std::string &fallback, size_t start, size_t end) - : variable_name(name) - , fallback_value(fallback) - , start_position(start) - , end_position(end) + std::string variable_name; + std::optional fallback_value; + size_t start_position; + size_t end_position; + + VariableReference(const std::string &name, size_t start, size_t end) + : variable_name(name) + , start_position(start) + , end_position(end) + { + } + + VariableReference(const std::string &name, const std::string &fallback, size_t start, size_t end) + : variable_name(name) + , fallback_value(fallback) + , start_position(start) + , end_position(end) + { + } + }; + + // Variable resolver function type + // Takes variable name and returns resolved value, or empty optional if not found + using VariableResolver = std::function(const std::string &)>; + + // Main parser class + class CSSVariableParser { - } - }; - - // Variable resolver function type - // Takes variable name and returns resolved value, or empty optional if not found - using VariableResolver = std::function(const std::string &)>; - - // Main parser class - class CSSVariableParser - { - public: - explicit CSSVariableParser(const std::string &input); + public: + explicit CSSVariableParser(const std::string &input); - // Parse and resolve all CSS variables in the input string - std::string resolveVariables(const VariableResolver &resolver); + // Parse and resolve all CSS variables in the input string + std::string resolveVariables(const VariableResolver &resolver); - // Get all variable references found in the input (without resolving) - std::vector getVariableReferences(); + // Get all variable references found in the input (without resolving) + std::vector getVariableReferences(); - // Check if parsing was successful - bool isValid() const - { - return is_valid_; - } + // Check if parsing was successful + bool isValid() const + { + return is_valid_; + } - // Get error message if parsing failed - const std::string &getError() const - { - return error_message_; - } + // Get error message if parsing failed + const std::string &getError() const + { + return error_message_; + } - private: - std::string input_; - bool is_valid_; - std::string error_message_; + private: + std::string input_; + bool is_valid_; + std::string error_message_; - // Find the next var() function in the string starting from the given position - std::optional findNextVariable(size_t start_pos); + // Find the next var() function in the string starting from the given position + std::optional findNextVariable(size_t start_pos); - // Parse the content inside var() parentheses - std::optional parseVariableContent(size_t start_pos); + // Parse the content inside var() parentheses + std::optional parseVariableContent(size_t start_pos); - // Find matching closing parenthesis, handling nested parentheses - size_t findMatchingParen(size_t start_pos); + // Find matching closing parenthesis, handling nested parentheses + size_t findMatchingParen(size_t start_pos); - // Parse variable name and optional fallback from var() content - std::pair> parseVariableNameAndFallback(const std::string &content); + // Parse variable name and optional fallback from var() content + std::pair> parseVariableNameAndFallback(const std::string &content); - // Trim whitespace from string - std::string trim(const std::string &str); + // Trim whitespace from string + std::string trim(const std::string &str); - // Recursively resolve variables, handling nested var() calls - std::string resolveVariablesRecursive(const std::string &value, const VariableResolver &resolver, int depth = 0); + // Recursively resolve variables, handling nested var() calls + std::string resolveVariablesRecursive(const std::string &value, const VariableResolver &resolver, int depth = 0); - // Maximum recursion depth to prevent infinite loops - static const int MAX_RECURSION_DEPTH = 10; - }; -} + // Maximum recursion depth to prevent infinite loops + static const int MAX_RECURSION_DEPTH = 10; + }; + } +} // namespace endor diff --git a/src/client/cssom/properties.hpp b/src/client/cssom/properties.hpp index 9ee9fc052..f6ff18251 100644 --- a/src/client/cssom/properties.hpp +++ b/src/client/cssom/properties.hpp @@ -8,8 +8,10 @@ #include #include -namespace client_cssom +namespace endor { + namespace client_cssom + { #define SHORTHAND_PROPS_MAP(XX) \ XX(All, "all") \ /* Box Model */ \ @@ -280,224 +282,225 @@ namespace client_cssom XX(WebkitTextStrokeWidth, "-webkit-text-stroke-width") \ XX(WebkitTextStrokeColor, "-webkit-text-stroke-color") - enum class ShorthandId : uint16_t - { + enum class ShorthandId : uint16_t + { #define XX(ID, _) k##ID, - SHORTHAND_PROPS_MAP(XX) + SHORTHAND_PROPS_MAP(XX) #undef XX - }; + }; - inline std::string to_string(const ShorthandId &id) - { - switch (id) + inline std::string to_string(const ShorthandId &id) { + switch (id) + { #define XX(ID, STR) \ case ShorthandId::k##ID: \ return STR; - SHORTHAND_PROPS_MAP(XX) + SHORTHAND_PROPS_MAP(XX) #undef XX + } + return "Unknown ShorthandId"; } - return "Unknown ShorthandId"; - } - inline std::optional parse_shorthand(const std::string &str) - { + inline std::optional parse_shorthand(const std::string &str) + { #define XX(ID, STR) \ if (str == STR) \ return ShorthandId::k##ID; - SHORTHAND_PROPS_MAP(XX) + SHORTHAND_PROPS_MAP(XX) #undef XX - return std::nullopt; // Return nullopt if no match found - } + return std::nullopt; // Return nullopt if no match found + } - enum class LonghandId : uint16_t - { + enum class LonghandId : uint16_t + { #define XX(ID, _) k##ID, - LONGHAND_PROPS_MAP(XX) + LONGHAND_PROPS_MAP(XX) #undef XX - }; + }; - inline std::string to_string(const LonghandId &id) - { - switch (id) + inline std::string to_string(const LonghandId &id) { + switch (id) + { #define XX(ID, STR) \ case LonghandId::k##ID: \ return STR; - LONGHAND_PROPS_MAP(XX) + LONGHAND_PROPS_MAP(XX) #undef XX + } + return "Unknown LonghandId"; } - return "Unknown LonghandId"; - } - inline std::optional parse_longhand(const std::string &str) - { + inline std::optional parse_longhand(const std::string &str) + { #define XX(ID, STR) \ if (str == STR) \ return LonghandId::k##ID; - LONGHAND_PROPS_MAP(XX) + LONGHAND_PROPS_MAP(XX) #undef XX - return std::nullopt; // Return nullopt if no match found - } -#undef SHORTHAND_PROPS_MAP -#undef LONGHAND_PROPS_MAP - - class NonCustomPropertyId - { - public: - static NonCustomPropertyId FromShorthand(ShorthandId shorthand_id) - { - return NonCustomPropertyId(shorthand_id); - } - static NonCustomPropertyId FromLonghand(LonghandId longhand_id) - { - return NonCustomPropertyId(longhand_id); - } - static std::optional Parse(const std::string &str) - { - if (auto shorthand_id = parse_shorthand(str)) - return FromShorthand(*shorthand_id); - if (auto longhand_id = parse_longhand(str)) - return FromLonghand(*longhand_id); return std::nullopt; // Return nullopt if no match found } +#undef SHORTHAND_PROPS_MAP +#undef LONGHAND_PROPS_MAP - private: - NonCustomPropertyId(ShorthandId shorthand_id) - : bit_(static_cast(shorthand_id)) - , is_shorthand_(true) - { - } - NonCustomPropertyId(LonghandId longhand_id) - : bit_(static_cast(longhand_id)) - , is_shorthand_(false) - { - } - - public: - uint32_t bit() const + class NonCustomPropertyId { - return bit_; - } + public: + static NonCustomPropertyId FromShorthand(ShorthandId shorthand_id) + { + return NonCustomPropertyId(shorthand_id); + } + static NonCustomPropertyId FromLonghand(LonghandId longhand_id) + { + return NonCustomPropertyId(longhand_id); + } + static std::optional Parse(const std::string &str) + { + if (auto shorthand_id = parse_shorthand(str)) + return FromShorthand(*shorthand_id); + if (auto longhand_id = parse_longhand(str)) + return FromLonghand(*longhand_id); + return std::nullopt; // Return nullopt if no match found + } - bool operator==(const NonCustomPropertyId &other) const - { - return bit_ == other.bit_; - } - bool operator!=(const NonCustomPropertyId &other) const - { - return !(*this == other); - } - operator std::string() const - { - if (is_shorthand_) - return to_string(static_cast(bit_)); - else - return to_string(static_cast(bit_)); - } - friend std::ostream &operator<<(std::ostream &os, const NonCustomPropertyId &id) - { - os << "NonCustomPropertyId(" << std::string(id) << ")"; - return os; - } + private: + NonCustomPropertyId(ShorthandId shorthand_id) + : bit_(static_cast(shorthand_id)) + , is_shorthand_(true) + { + } + NonCustomPropertyId(LonghandId longhand_id) + : bit_(static_cast(longhand_id)) + , is_shorthand_(false) + { + } - private: - uint16_t bit_; - bool is_shorthand_ = false; - }; + public: + uint32_t bit() const + { + return bit_; + } - class CustomPropertyId - { - public: - CustomPropertyId(const std::string &css) - : css_(css) - , custom_bit_(ComputeUniqueHash(css)) - { - } + bool operator==(const NonCustomPropertyId &other) const + { + return bit_ == other.bit_; + } + bool operator!=(const NonCustomPropertyId &other) const + { + return !(*this == other); + } + operator std::string() const + { + if (is_shorthand_) + return to_string(static_cast(bit_)); + else + return to_string(static_cast(bit_)); + } + friend std::ostream &operator<<(std::ostream &os, const NonCustomPropertyId &id) + { + os << "NonCustomPropertyId(" << std::string(id) << ")"; + return os; + } - public: - uint32_t bit() const - { - return custom_bit_; - } + private: + uint16_t bit_; + bool is_shorthand_ = false; + }; - bool operator==(const CustomPropertyId &other) const - { - return custom_bit_ == other.custom_bit_ && css_ == other.css_; - } - bool operator!=(const CustomPropertyId &other) const - { - return !(*this == other); - } - operator std::string() const + class CustomPropertyId { - return css_; - } - friend std::ostream &operator<<(std::ostream &os, const CustomPropertyId &id) - { - os << "CustomPropertyId(" << std::string(id) << ")"; - return os; - } + public: + CustomPropertyId(const std::string &css) + : css_(css) + , custom_bit_(ComputeUniqueHash(css)) + { + } - private: - static uint32_t ComputeUniqueHash(const std::string &str) - { - const uint32_t prime = 0x01000193; // 16777619 - uint32_t hash = 0x811C9DC5; // 2166136261 + public: + uint32_t bit() const + { + return custom_bit_; + } - // compute hash based on the ASCII values of the characters - for (char c : str) + bool operator==(const CustomPropertyId &other) const + { + return custom_bit_ == other.custom_bit_ && css_ == other.css_; + } + bool operator!=(const CustomPropertyId &other) const + { + return !(*this == other); + } + operator std::string() const + { + return css_; + } + friend std::ostream &operator<<(std::ostream &os, const CustomPropertyId &id) { - hash ^= static_cast(c); - hash *= prime; + os << "CustomPropertyId(" << std::string(id) << ")"; + return os; } - // mixing the characters in a more complex way - for (size_t i = 0; i < str.size(); ++i) - hash += (str[i] << (i % 24)) ^ (str[str.size() - 1 - i] >> (i % 8)); + private: + static uint32_t ComputeUniqueHash(const std::string &str) + { + const uint32_t prime = 0x01000193; // 16777619 + uint32_t hash = 0x811C9DC5; // 2166136261 - return hash; - } + // compute hash based on the ASCII values of the characters + for (char c : str) + { + hash ^= static_cast(c); + hash *= prime; + } - private: - uint32_t custom_bit_; - std::string css_; - }; + // mixing the characters in a more complex way + for (size_t i = 0; i < str.size(); ++i) + hash += (str[i] << (i % 24)) ^ (str[str.size() - 1 - i] >> (i % 8)); - class PropertyId - { - private: - enum Tag : uint8_t - { - kNonCustom, - kCustom, - }; - using IdVariant = std::variant; + return hash; + } - public: - static PropertyId NonCustom(const NonCustomPropertyId &non_custom_property_id) - { - return PropertyId(non_custom_property_id); - }; - static PropertyId Custom(const CustomPropertyId &custom_property_id) - { - return PropertyId(custom_property_id); + private: + uint32_t custom_bit_; + std::string css_; }; - private: - PropertyId(NonCustomPropertyId non_custom_property_id) - : tag_(kNonCustom) - , id_(non_custom_property_id) - { - } - PropertyId(CustomPropertyId custom_property_id) - : tag_(kCustom) - , id_(custom_property_id) + class PropertyId { - } + private: + enum Tag : uint8_t + { + kNonCustom, + kCustom, + }; + using IdVariant = std::variant; + + public: + static PropertyId NonCustom(const NonCustomPropertyId &non_custom_property_id) + { + return PropertyId(non_custom_property_id); + }; + static PropertyId Custom(const CustomPropertyId &custom_property_id) + { + return PropertyId(custom_property_id); + }; - private: - Tag tag_; - IdVariant id_; - }; -} + private: + PropertyId(NonCustomPropertyId non_custom_property_id) + : tag_(kNonCustom) + , id_(non_custom_property_id) + { + } + PropertyId(CustomPropertyId custom_property_id) + : tag_(kCustom) + , id_(custom_property_id) + { + } + + private: + Tag tag_; + IdVariant id_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_condition_rule.hpp b/src/client/cssom/rules/css_condition_rule.hpp index dab7dcb74..e72702acd 100644 --- a/src/client/cssom/rules/css_condition_rule.hpp +++ b/src/client/cssom/rules/css_condition_rule.hpp @@ -6,19 +6,22 @@ #include "../css_rule.hpp" #include "../css_style_declaration.hpp" -namespace client_cssom::rules +namespace endor { - class CSSConditionRule : public CSSGroupingRule + namespace client_cssom::rules { - using CSSGroupingRule::CSSGroupingRule; - - public: - const std::string &conditionText() const + class CSSConditionRule : public CSSGroupingRule { - return conditionText_; - } + using CSSGroupingRule::CSSGroupingRule; + + public: + const std::string &conditionText() const + { + return conditionText_; + } - private: - std::string conditionText_; - }; -} + private: + std::string conditionText_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_font_face_rule.hpp b/src/client/cssom/rules/css_font_face_rule.hpp index a52f439b3..9ddc98a1a 100644 --- a/src/client/cssom/rules/css_font_face_rule.hpp +++ b/src/client/cssom/rules/css_font_face_rule.hpp @@ -5,19 +5,22 @@ #include "../css_rule.hpp" #include "../css_style_declaration.hpp" -namespace client_cssom::rules +namespace endor { - class CSSFontFaceRule : public CSSRule + namespace client_cssom::rules { - using CSSRule::CSSRule; - - public: - const CSSStyleDeclaration &style() const + class CSSFontFaceRule : public CSSRule { - return style_; - } + using CSSRule::CSSRule; + + public: + const CSSStyleDeclaration &style() const + { + return style_; + } - private: - CSSStyleDeclaration style_; - }; -} + private: + CSSStyleDeclaration style_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_grouping_rule.hpp b/src/client/cssom/rules/css_grouping_rule.hpp index c51648155..4c415f5f9 100644 --- a/src/client/cssom/rules/css_grouping_rule.hpp +++ b/src/client/cssom/rules/css_grouping_rule.hpp @@ -4,23 +4,26 @@ #include #include "../css_rule.hpp" -namespace client_cssom::rules +namespace endor { - class CSSGroupingRule : public CSSRule + namespace client_cssom::rules { - using CSSRule::CSSRule; - - public: - inline CSSRuleList &cssRules() const + class CSSGroupingRule : public CSSRule { - return *cssRules_; - } + using CSSRule::CSSRule; + + public: + inline CSSRuleList &cssRules() const + { + return *cssRules_; + } - public: - size_t insertRule(const std::string &rule, size_t index); - void deleteRule(size_t index); + public: + size_t insertRule(const std::string &rule, size_t index); + void deleteRule(size_t index); - private: - std::unique_ptr cssRules_; - }; -} + private: + std::unique_ptr cssRules_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_import_rule.hpp b/src/client/cssom/rules/css_import_rule.hpp index 5e9250049..0baf52a48 100644 --- a/src/client/cssom/rules/css_import_rule.hpp +++ b/src/client/cssom/rules/css_import_rule.hpp @@ -4,29 +4,32 @@ #include #include "../css_rule.hpp" -namespace client_cssom::rules +namespace endor { - class CSSImportRule : public CSSRule + namespace client_cssom::rules { - using CSSRule::CSSRule; - - public: - inline const std::string &href() const - { - return href_; - } - inline const std::string &layerName() const + class CSSImportRule : public CSSRule { - return layerName_; - } - inline const std::optional &supportsText() const - { - return supportsText_; - } + using CSSRule::CSSRule; + + public: + inline const std::string &href() const + { + return href_; + } + inline const std::string &layerName() const + { + return layerName_; + } + inline const std::optional &supportsText() const + { + return supportsText_; + } - private: - std::string href_; - std::string layerName_; - std::optional supportsText_; - }; -} + private: + std::string href_; + std::string layerName_; + std::optional supportsText_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_keyframe_rule.hpp b/src/client/cssom/rules/css_keyframe_rule.hpp index 13e0f460c..8da742256 100644 --- a/src/client/cssom/rules/css_keyframe_rule.hpp +++ b/src/client/cssom/rules/css_keyframe_rule.hpp @@ -4,20 +4,23 @@ #include "../css_rule.hpp" #include "../css_style_declaration.hpp" -namespace client_cssom::rules +namespace endor { - class CSSKeyframeRule : public CSSRule + namespace client_cssom::rules { - using CSSRule::CSSRule; - - public: - std::string keyText; - const CSSStyleDeclaration &style() const + class CSSKeyframeRule : public CSSRule { - return style_; - } + using CSSRule::CSSRule; + + public: + std::string keyText; + const CSSStyleDeclaration &style() const + { + return style_; + } - private: - CSSStyleDeclaration style_; - }; -} + private: + CSSStyleDeclaration style_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_keyframes_rule.hpp b/src/client/cssom/rules/css_keyframes_rule.hpp index 250b77b50..539093676 100644 --- a/src/client/cssom/rules/css_keyframes_rule.hpp +++ b/src/client/cssom/rules/css_keyframes_rule.hpp @@ -5,34 +5,37 @@ #include "../css_rule.hpp" #include "../css_style_declaration.hpp" -namespace client_cssom::rules +namespace endor { - class CSSKeyframesRule : public CSSRule + namespace client_cssom::rules { - using CSSRule::CSSRule; - - public: - std::string name; - const CSSRuleList &cssRules() const - { - return *cssRules_; - } - size_t length() const + class CSSKeyframesRule : public CSSRule { - return cssRules_->length(); - } + using CSSRule::CSSRule; + + public: + std::string name; + const CSSRuleList &cssRules() const + { + return *cssRules_; + } + size_t length() const + { + return cssRules_->length(); + } - public: - void appendRule(std::string rule); - /** + public: + void appendRule(std::string rule); + /** * It deletes the `CSSKeyFrameRule` that matches the specified keyframe selector. * * @param selector The keyframe selector to delete, which must be: "from" or "to" or a percentage. */ - void deleteRule(std::string selector); - const CSSKeyframeRule &findRule(std::string selector) const; + void deleteRule(std::string selector); + const CSSKeyframeRule &findRule(std::string selector) const; - private: - std::unique_ptr cssRules_; - }; -} + private: + std::unique_ptr cssRules_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_media_rule.hpp b/src/client/cssom/rules/css_media_rule.hpp index e2048f683..7c3b5e38b 100644 --- a/src/client/cssom/rules/css_media_rule.hpp +++ b/src/client/cssom/rules/css_media_rule.hpp @@ -9,25 +9,28 @@ #include "../css_rule.hpp" #include "../css_style_declaration.hpp" -namespace client_cssom::rules +namespace endor { - class CSSMediaRule : public CSSConditionRule + namespace client_cssom::rules { - using CSSConditionRule::CSSConditionRule; - - public: - CSSMediaRule(crates::css2::stylesheets::MediaRule &inner) - : CSSConditionRule() + class CSSMediaRule : public CSSConditionRule { - } + using CSSConditionRule::CSSConditionRule; - public: - const MediaList &media() const - { - return *media_; - } + public: + CSSMediaRule(crates::css2::stylesheets::MediaRule &inner) + : CSSConditionRule() + { + } + + public: + const MediaList &media() const + { + return *media_; + } - private: - std::unique_ptr media_; - }; -} + private: + std::unique_ptr media_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_page_rule.hpp b/src/client/cssom/rules/css_page_rule.hpp index 806401233..cf79221ae 100644 --- a/src/client/cssom/rules/css_page_rule.hpp +++ b/src/client/cssom/rules/css_page_rule.hpp @@ -5,20 +5,23 @@ #include "../css_rule.hpp" #include "../css_style_declaration.hpp" -namespace client_cssom::rules +namespace endor { - class CSSPageRule : public CSSRule + namespace client_cssom::rules { - using CSSRule::CSSRule; - - public: - std::string selectorText; - const CSSStyleDeclaration &style() const + class CSSPageRule : public CSSRule { - return style_; - } + using CSSRule::CSSRule; + + public: + std::string selectorText; + const CSSStyleDeclaration &style() const + { + return style_; + } - private: - CSSStyleDeclaration style_; - }; -} + private: + CSSStyleDeclaration style_; + }; + } +} // namespace endor diff --git a/src/client/cssom/rules/css_style_rule.hpp b/src/client/cssom/rules/css_style_rule.hpp index 55b66602e..299fd3e79 100644 --- a/src/client/cssom/rules/css_style_rule.hpp +++ b/src/client/cssom/rules/css_style_rule.hpp @@ -10,56 +10,59 @@ #include "./css_grouping_rule.hpp" -namespace client_cssom::rules +namespace endor { - class CSSStyleRule final : public CSSGroupingRule + namespace client_cssom::rules { - friend class client_cssom::CSSRuleList; - using CSSGroupingRule::CSSGroupingRule; - - public: - CSSStyleRule(crates::css2::stylesheets::StyleRule &inner) - : CSSGroupingRule() - , selectorText_(inner.selectorsText()) - , style_(inner.takeBlock()) + class CSSStyleRule final : public CSSGroupingRule { - auto parsed = selectors::CSSelectorParser::parseSelectors(selectorText_); - if (parsed) + friend class client_cssom::CSSRuleList; + using CSSGroupingRule::CSSGroupingRule; + + public: + CSSStyleRule(crates::css2::stylesheets::StyleRule &inner) + : CSSGroupingRule() + , selectorText_(inner.selectorsText()) + , style_(inner.takeBlock()) { - selectors_ = std::move(*parsed); + auto parsed = selectors::CSSelectorParser::parseSelectors(selectorText_); + if (parsed) + { + selectors_ = std::move(*parsed); + } + // Note: If parsing fails, selectors_ will be empty, which is appropriate } - // Note: If parsing fails, selectors_ will be empty, which is appropriate - } - public: - const selectors::SelectorList &selectors() const - { - return selectors_; - } + public: + const selectors::SelectorList &selectors() const + { + return selectors_; + } - std::string selectorText() const - { - return selectorText_; - } + std::string selectorText() const + { + return selectorText_; + } - const CSSStyleDeclaration &style() const - { - return style_; - } + const CSSStyleDeclaration &style() const + { + return style_; + } - /** + /** * Check if an element matches this rule's selectors * @param element The element to check * @return true if the element matches any selector in this rule */ - bool matches(const std::shared_ptr element) const - { - return selectors::matchesSelectorList(selectors_, element); - } + bool matches(const std::shared_ptr element) const + { + return selectors::matchesSelectorList(selectors_, element); + } - private: - std::string selectorText_; - selectors::SelectorList selectors_; // Native C++ selectors (replaces Rust selectors) - CSSStyleDeclaration style_; - }; -} + private: + std::string selectorText_; + selectors::SelectorList selectors_; // Native C++ selectors (replaces Rust selectors) + CSSStyleDeclaration style_; + }; + } +} // namespace endor diff --git a/src/client/cssom/selectors/css_selector_parser.cpp b/src/client/cssom/selectors/css_selector_parser.cpp index 12eed036c..1d4f9061b 100644 --- a/src/client/cssom/selectors/css_selector_parser.cpp +++ b/src/client/cssom/selectors/css_selector_parser.cpp @@ -3,873 +3,876 @@ #include #include "./css_selector_parser.hpp" -namespace client_cssom::selectors +namespace endor { - using namespace std; - - // Component implementation - Component::Component(ComponentType type, const string &name, Combinator combinator, PseudoClassType pseudoClassType) - : type_(type) - , name_(name) - , combinator_(combinator) - , pseudoClassType_(pseudoClassType) - , argumentSelectorList_(nullptr) - , attributeMatchType_(AttributeMatchType::kUnknown) - , attributeValue_("") - , nthA_(0) - , nthB_(0) + namespace client_cssom::selectors { - } + using namespace std; + + // Component implementation + Component::Component(ComponentType type, const string &name, Combinator combinator, PseudoClassType pseudoClassType) + : type_(type) + , name_(name) + , combinator_(combinator) + , pseudoClassType_(pseudoClassType) + , argumentSelectorList_(nullptr) + , attributeMatchType_(AttributeMatchType::kUnknown) + , attributeValue_("") + , nthA_(0) + , nthB_(0) + { + } - Component::Component(ComponentType type, PseudoClassType pseudoClassType, std::shared_ptr argumentSelectorList) - : type_(type) - , name_("") - , combinator_(Combinator::kUnknown) - , pseudoClassType_(pseudoClassType) - , argumentSelectorList_(argumentSelectorList) - , attributeMatchType_(AttributeMatchType::kUnknown) - , attributeValue_("") - , nthA_(0) - , nthB_(0) - { - } + Component::Component(ComponentType type, PseudoClassType pseudoClassType, std::shared_ptr argumentSelectorList) + : type_(type) + , name_("") + , combinator_(Combinator::kUnknown) + , pseudoClassType_(pseudoClassType) + , argumentSelectorList_(argumentSelectorList) + , attributeMatchType_(AttributeMatchType::kUnknown) + , attributeValue_("") + , nthA_(0) + , nthB_(0) + { + } - Component::Component(ComponentType type, const string &attributeName, AttributeMatchType matchType, const string &attributeValue) - : type_(type) - , name_(attributeName) - , combinator_(Combinator::kUnknown) - , pseudoClassType_(PseudoClassType::kUnknown) - , argumentSelectorList_(nullptr) - , attributeMatchType_(matchType) - , attributeValue_(attributeValue) - , nthA_(0) - , nthB_(0) - { - } + Component::Component(ComponentType type, const string &attributeName, AttributeMatchType matchType, const string &attributeValue) + : type_(type) + , name_(attributeName) + , combinator_(Combinator::kUnknown) + , pseudoClassType_(PseudoClassType::kUnknown) + , argumentSelectorList_(nullptr) + , attributeMatchType_(matchType) + , attributeValue_(attributeValue) + , nthA_(0) + , nthB_(0) + { + } - Component::Component(ComponentType type, PseudoClassType pseudoClassType, int nthA, int nthB) - : type_(type) - , name_("") - , combinator_(Combinator::kUnknown) - , pseudoClassType_(pseudoClassType) - , argumentSelectorList_(nullptr) - , attributeMatchType_(AttributeMatchType::kUnknown) - , attributeValue_("") - , nthA_(nthA) - , nthB_(nthB) - { - } + Component::Component(ComponentType type, PseudoClassType pseudoClassType, int nthA, int nthB) + : type_(type) + , name_("") + , combinator_(Combinator::kUnknown) + , pseudoClassType_(pseudoClassType) + , argumentSelectorList_(nullptr) + , attributeMatchType_(AttributeMatchType::kUnknown) + , attributeValue_("") + , nthA_(nthA) + , nthB_(nthB) + { + } - Component::operator string() const - { - stringstream ss; - switch (type_) + Component::operator string() const { - case ComponentType::kLocalName: - ss << name_; - break; - case ComponentType::kUniversal: - ss << "*"; - break; - case ComponentType::kID: - ss << "#" << name_; - break; - case ComponentType::kClass: - ss << "." << name_; - break; - case ComponentType::kAttribute: - ss << "[" << name_; - switch (attributeMatchType_) - { - case AttributeMatchType::kExists: - break; // Just [attr] - case AttributeMatchType::kExact: - ss << "=\"" << attributeValue_ << "\""; - break; - case AttributeMatchType::kWhitespace: - ss << "~=\"" << attributeValue_ << "\""; - break; - case AttributeMatchType::kPrefix: - ss << "^=\"" << attributeValue_ << "\""; - break; - case AttributeMatchType::kSuffix: - ss << "$=\"" << attributeValue_ << "\""; - break; - case AttributeMatchType::kSubstring: - ss << "*=\"" << attributeValue_ << "\""; - break; - case AttributeMatchType::kDashPrefix: - ss << "|=\"" << attributeValue_ << "\""; - break; - default: - break; - } - ss << "]"; - break; - case ComponentType::kRoot: - ss << ":root"; - break; - case ComponentType::kEmpty: - ss << ":empty"; - break; - case ComponentType::kHost: - ss << ":host"; - break; - case ComponentType::kPseudoElement: - ss << "::" << name_; - break; - case ComponentType::kPseudoClass: - switch (pseudoClassType_) + stringstream ss; + switch (type_) { - case PseudoClassType::kHover: - ss << ":hover"; - break; - case PseudoClassType::kActive: - ss << ":active"; - break; - case PseudoClassType::kFocus: - ss << ":focus"; - break; - case PseudoClassType::kFocusVisible: - ss << ":focus-visible"; + case ComponentType::kLocalName: + ss << name_; break; - case PseudoClassType::kFocusWithin: - ss << ":focus-within"; + case ComponentType::kUniversal: + ss << "*"; break; - case PseudoClassType::kFirstChild: - ss << ":first-child"; + case ComponentType::kID: + ss << "#" << name_; break; - case PseudoClassType::kLastChild: - ss << ":last-child"; + case ComponentType::kClass: + ss << "." << name_; break; - case PseudoClassType::kFirstOfType: - ss << ":first-of-type"; + case ComponentType::kAttribute: + ss << "[" << name_; + switch (attributeMatchType_) + { + case AttributeMatchType::kExists: + break; // Just [attr] + case AttributeMatchType::kExact: + ss << "=\"" << attributeValue_ << "\""; + break; + case AttributeMatchType::kWhitespace: + ss << "~=\"" << attributeValue_ << "\""; + break; + case AttributeMatchType::kPrefix: + ss << "^=\"" << attributeValue_ << "\""; + break; + case AttributeMatchType::kSuffix: + ss << "$=\"" << attributeValue_ << "\""; + break; + case AttributeMatchType::kSubstring: + ss << "*=\"" << attributeValue_ << "\""; + break; + case AttributeMatchType::kDashPrefix: + ss << "|=\"" << attributeValue_ << "\""; + break; + default: + break; + } + ss << "]"; break; - case PseudoClassType::kLastOfType: - ss << ":last-of-type"; + case ComponentType::kRoot: + ss << ":root"; break; - case PseudoClassType::kOnlyChild: - ss << ":only-child"; + case ComponentType::kEmpty: + ss << ":empty"; break; - case PseudoClassType::kOnlyOfType: - ss << ":only-of-type"; + case ComponentType::kHost: + ss << ":host"; break; - case PseudoClassType::kWhere: - ss << ":where("; - if (argumentSelectorList_) - { - ss << static_cast(*argumentSelectorList_); - } - ss << ")"; + case ComponentType::kPseudoElement: + ss << "::" << name_; break; - case PseudoClassType::kNthChild: - ss << ":nth-child("; - if (nthA_ == 0) - { - ss << nthB_; // Simple number like :nth-child(3) - } - else if (nthA_ == 2 && nthB_ == 1) - { - ss << "odd"; // Special case for 2n+1 - } - else if (nthA_ == 2 && nthB_ == 0) - { - ss << "even"; // Special case for 2n - } - else + case ComponentType::kPseudoClass: + switch (pseudoClassType_) { - // General an+b format - if (nthA_ == 1) - ss << "n"; - else if (nthA_ == -1) - ss << "-n"; + case PseudoClassType::kHover: + ss << ":hover"; + break; + case PseudoClassType::kActive: + ss << ":active"; + break; + case PseudoClassType::kFocus: + ss << ":focus"; + break; + case PseudoClassType::kFocusVisible: + ss << ":focus-visible"; + break; + case PseudoClassType::kFocusWithin: + ss << ":focus-within"; + break; + case PseudoClassType::kFirstChild: + ss << ":first-child"; + break; + case PseudoClassType::kLastChild: + ss << ":last-child"; + break; + case PseudoClassType::kFirstOfType: + ss << ":first-of-type"; + break; + case PseudoClassType::kLastOfType: + ss << ":last-of-type"; + break; + case PseudoClassType::kOnlyChild: + ss << ":only-child"; + break; + case PseudoClassType::kOnlyOfType: + ss << ":only-of-type"; + break; + case PseudoClassType::kWhere: + ss << ":where("; + if (argumentSelectorList_) + { + ss << static_cast(*argumentSelectorList_); + } + ss << ")"; + break; + case PseudoClassType::kNthChild: + ss << ":nth-child("; + if (nthA_ == 0) + { + ss << nthB_; // Simple number like :nth-child(3) + } + else if (nthA_ == 2 && nthB_ == 1) + { + ss << "odd"; // Special case for 2n+1 + } + else if (nthA_ == 2 && nthB_ == 0) + { + ss << "even"; // Special case for 2n + } else - ss << nthA_ << "n"; - - if (nthB_ > 0) - ss << "+" << nthB_; - else if (nthB_ < 0) - ss << nthB_; // Negative sign already included + { + // General an+b format + if (nthA_ == 1) + ss << "n"; + else if (nthA_ == -1) + ss << "-n"; + else + ss << nthA_ << "n"; + + if (nthB_ > 0) + ss << "+" << nthB_; + else if (nthB_ < 0) + ss << nthB_; // Negative sign already included + } + ss << ")"; + break; + case PseudoClassType::kNthOfType: + ss << ":nth-of-type("; + if (nthA_ == 0) + { + ss << nthB_; + } + else if (nthA_ == 2 && nthB_ == 1) + { + ss << "odd"; + } + else if (nthA_ == 2 && nthB_ == 0) + { + ss << "even"; + } + else + { + if (nthA_ == 1) + ss << "n"; + else if (nthA_ == -1) + ss << "-n"; + else + ss << nthA_ << "n"; + + if (nthB_ > 0) + ss << "+" << nthB_; + else if (nthB_ < 0) + ss << nthB_; + } + ss << ")"; + break; + default: + ss << ":" << name_; + break; } - ss << ")"; break; - case PseudoClassType::kNthOfType: - ss << ":nth-of-type("; - if (nthA_ == 0) - { - ss << nthB_; - } - else if (nthA_ == 2 && nthB_ == 1) + case ComponentType::kCombinator: + switch (combinator_) { - ss << "odd"; - } - else if (nthA_ == 2 && nthB_ == 0) - { - ss << "even"; - } - else - { - if (nthA_ == 1) - ss << "n"; - else if (nthA_ == -1) - ss << "-n"; - else - ss << nthA_ << "n"; - - if (nthB_ > 0) - ss << "+" << nthB_; - else if (nthB_ < 0) - ss << nthB_; + case Combinator::kDescendant: + ss << " "; + break; + case Combinator::kChild: + ss << " > "; + break; + case Combinator::kNextSibling: + ss << " + "; + break; + case Combinator::kLaterSibling: + ss << " ~ "; + break; + case Combinator::kPseudoElement: + ss << "::"; + break; + default: + ss << " ? "; + break; } - ss << ")"; break; default: - ss << ":" << name_; break; } - break; - case ComponentType::kCombinator: - switch (combinator_) - { - case Combinator::kDescendant: - ss << " "; - break; - case Combinator::kChild: - ss << " > "; - break; - case Combinator::kNextSibling: - ss << " + "; - break; - case Combinator::kLaterSibling: - ss << " ~ "; - break; - case Combinator::kPseudoElement: - ss << "::"; - break; - default: - ss << " ? "; - break; - } - break; - default: - break; + return ss.str(); } - return ss.str(); - } - // Selector implementation - Selector::Selector(vector components) - : components_(move(components)) - { - } + // Selector implementation + Selector::Selector(vector components) + : components_(move(components)) + { + } - Selector::operator string() const - { - stringstream ss; - for (size_t i = 0; i < components_.size(); ++i) + Selector::operator string() const { - if (i > 0 && !components_[i].isCombinator()) + stringstream ss; + for (size_t i = 0; i < components_.size(); ++i) { - // Add implicit space for compound selectors - if (!components_[i - 1].isCombinator()) + if (i > 0 && !components_[i].isCombinator()) { - // This handles cases like "div.class" (no space between tag and class) + // Add implicit space for compound selectors + if (!components_[i - 1].isCombinator()) + { + // This handles cases like "div.class" (no space between tag and class) + } } + ss << static_cast(components_[i]); } - ss << static_cast(components_[i]); + return ss.str(); } - return ss.str(); - } - - // SelectorList implementation - SelectorList::SelectorList(vector selectors) - : selectors_(move(selectors)) - { - } - SelectorList::operator string() const - { - stringstream ss; - for (size_t i = 0; i < selectors_.size(); ++i) + // SelectorList implementation + SelectorList::SelectorList(vector selectors) + : selectors_(move(selectors)) { - if (i > 0) - ss << ", "; - ss << static_cast(selectors_[i]); } - return ss.str(); - } - // CSSelectorParser implementation - optional CSSelectorParser::parseSelectors(const string &selectorText) - { - auto selectors = parseMultipleSelectors(selectorText); - if (!selectors) - return nullopt; + SelectorList::operator string() const + { + stringstream ss; + for (size_t i = 0; i < selectors_.size(); ++i) + { + if (i > 0) + ss << ", "; + ss << static_cast(selectors_[i]); + } + return ss.str(); + } - return SelectorList(move(*selectors)); - } + // CSSelectorParser implementation + optional CSSelectorParser::parseSelectors(const string &selectorText) + { + auto selectors = parseMultipleSelectors(selectorText); + if (!selectors) + return nullopt; - optional> CSSelectorParser::parseMultipleSelectors(const string &text) - { - vector selectors; - stringstream ss(text); - string selectorText; + return SelectorList(move(*selectors)); + } - // Split by comma to get individual selectors - while (getline(ss, selectorText, ',')) + optional> CSSelectorParser::parseMultipleSelectors(const string &text) { - // Trim whitespace - selectorText.erase(0, selectorText.find_first_not_of(" \t\n\r")); - selectorText.erase(selectorText.find_last_not_of(" \t\n\r") + 1); + vector selectors; + stringstream ss(text); + string selectorText; - if (selectorText.empty()) - continue; + // Split by comma to get individual selectors + while (getline(ss, selectorText, ',')) + { + // Trim whitespace + selectorText.erase(0, selectorText.find_first_not_of(" \t\n\r")); + selectorText.erase(selectorText.find_last_not_of(" \t\n\r") + 1); - auto selector = parseSingleSelector(selectorText); - if (!selector) - return nullopt; + if (selectorText.empty()) + continue; - selectors.push_back(move(*selector)); - } + auto selector = parseSingleSelector(selectorText); + if (!selector) + return nullopt; - return selectors; - } + selectors.push_back(move(*selector)); + } - optional CSSelectorParser::parseSingleSelector(const string &text) - { - vector components; - size_t pos = 0; + return selectors; + } - while (pos < text.length()) + optional CSSelectorParser::parseSingleSelector(const string &text) { - skipWhitespace(text, pos); - if (pos >= text.length()) - break; + vector components; + size_t pos = 0; - // Try to parse a component first - auto component = parseComponent(text, pos); - if (component) + while (pos < text.length()) { - components.push_back(move(*component)); + skipWhitespace(text, pos); + if (pos >= text.length()) + break; - // After parsing a component, check for combinators - // Don't skip whitespace here - let parseCombinator handle it - if (pos < text.length()) + // Try to parse a component first + auto component = parseComponent(text, pos); + if (component) { - auto combinator = parseCombinator(text, pos); - if (combinator) + components.push_back(move(*component)); + + // After parsing a component, check for combinators + // Don't skip whitespace here - let parseCombinator handle it + if (pos < text.length()) { - components.emplace_back(ComponentType::kCombinator, "", *combinator); + auto combinator = parseCombinator(text, pos); + if (combinator) + { + components.emplace_back(ComponentType::kCombinator, "", *combinator); + } } + continue; } - continue; - } - // If we can't parse a component, something is wrong - return nullopt; - } - - return Selector(move(components)); - } - - optional CSSelectorParser::parseComponent(const string &text, size_t &pos) - { - if (pos >= text.length()) - return nullopt; - - char c = text[pos]; + // If we can't parse a component, something is wrong + return nullopt; + } - // Universal selector (*) - if (c == '*') - { - ++pos; - return Component(ComponentType::kUniversal); + return Selector(move(components)); } - // ID selector (#id) - if (c == '#') + optional CSSelectorParser::parseComponent(const string &text, size_t &pos) { - ++pos; - auto name = parseIdentifier(text, pos); - if (name.empty()) + if (pos >= text.length()) return nullopt; - return Component(ComponentType::kID, name); - } - // Class selector (.class) - if (c == '.') - { - ++pos; - auto name = parseIdentifier(text, pos); - if (name.empty()) - return nullopt; - return Component(ComponentType::kClass, name); - } - - // Attribute selector ([attr] or [attr=value]) - if (c == '[') - { - return parseAttributeSelector(text, pos); - } + char c = text[pos]; - // Pseudo-class or pseudo-element (:pseudo or ::pseudo) - if (c == ':') - { - ++pos; - bool isPseudoElement = false; + // Universal selector (*) + if (c == '*') + { + ++pos; + return Component(ComponentType::kUniversal); + } - // Check for :: (pseudo-element) - if (pos < text.length() && text[pos] == ':') + // ID selector (#id) + if (c == '#') { ++pos; - isPseudoElement = true; + auto name = parseIdentifier(text, pos); + if (name.empty()) + return nullopt; + return Component(ComponentType::kID, name); } - auto name = parseIdentifier(text, pos); - if (name.empty()) - return nullopt; + // Class selector (.class) + if (c == '.') + { + ++pos; + auto name = parseIdentifier(text, pos); + if (name.empty()) + return nullopt; + return Component(ComponentType::kClass, name); + } - if (isPseudoElement) + // Attribute selector ([attr] or [attr=value]) + if (c == '[') { - return Component(ComponentType::kPseudoElement, name); + return parseAttributeSelector(text, pos); } - else + + // Pseudo-class or pseudo-element (:pseudo or ::pseudo) + if (c == ':') { - // Handle special pseudo-classes - if (name == "root") - return Component(ComponentType::kRoot); - if (name == "empty") - return Component(ComponentType::kEmpty); - if (name == "host") - return Component(ComponentType::kHost); - - // Check for functional pseudo-classes like :where() - if (pos < text.length() && text[pos] == '(') + ++pos; + bool isPseudoElement = false; + + // Check for :: (pseudo-element) + if (pos < text.length() && text[pos] == ':') { - auto functionalComponent = parseFunctionalPseudoClass(name, text, pos); - if (functionalComponent) - return functionalComponent; - // If functional parsing failed, fall through to regular pseudo-class parsing + ++pos; + isPseudoElement = true; } - // Regular pseudo-class - auto pseudoClassType = parsePseudoClass(name); - return Component(ComponentType::kPseudoClass, name, Combinator::kUnknown, pseudoClassType.value_or(PseudoClassType::kUnknown)); + auto name = parseIdentifier(text, pos); + if (name.empty()) + return nullopt; + + if (isPseudoElement) + { + return Component(ComponentType::kPseudoElement, name); + } + else + { + // Handle special pseudo-classes + if (name == "root") + return Component(ComponentType::kRoot); + if (name == "empty") + return Component(ComponentType::kEmpty); + if (name == "host") + return Component(ComponentType::kHost); + + // Check for functional pseudo-classes like :where() + if (pos < text.length() && text[pos] == '(') + { + auto functionalComponent = parseFunctionalPseudoClass(name, text, pos); + if (functionalComponent) + return functionalComponent; + // If functional parsing failed, fall through to regular pseudo-class parsing + } + + // Regular pseudo-class + auto pseudoClassType = parsePseudoClass(name); + return Component(ComponentType::kPseudoClass, name, Combinator::kUnknown, pseudoClassType.value_or(PseudoClassType::kUnknown)); + } } + + // Tag name (element selector) + if (isIdentifierStart(c)) + { + auto name = parseIdentifier(text, pos); + if (name.empty()) + return nullopt; + return Component(ComponentType::kLocalName, name); + } + + return nullopt; } - // Tag name (element selector) - if (isIdentifierStart(c)) + optional CSSelectorParser::parseCombinator(const string &text, size_t &pos) { - auto name = parseIdentifier(text, pos); - if (name.empty()) + if (pos >= text.length()) return nullopt; - return Component(ComponentType::kLocalName, name); - } - return nullopt; - } + // Skip any leading whitespace to handle cases like "div > p" + size_t originalPos = pos; + skipWhitespace(text, pos); - optional CSSelectorParser::parseCombinator(const string &text, size_t &pos) - { - if (pos >= text.length()) - return nullopt; + if (pos >= text.length()) + return nullopt; - // Skip any leading whitespace to handle cases like "div > p" - size_t originalPos = pos; - skipWhitespace(text, pos); + char c = text[pos]; - if (pos >= text.length()) - return nullopt; + switch (c) + { + case '>': + ++pos; + skipWhitespace(text, pos); + return Combinator::kChild; + case '+': + ++pos; + skipWhitespace(text, pos); + return Combinator::kNextSibling; + case '~': + ++pos; + skipWhitespace(text, pos); + return Combinator::kLaterSibling; + } - char c = text[pos]; + // Check for descendant combinator (whitespace that was skipped above) + if (originalPos != pos) + { + // We skipped whitespace but didn't find an explicit combinator + // This means it's a descendant combinator + // Make sure there's content after the whitespace + if (pos < text.length()) + { + return Combinator::kDescendant; + } + } - switch (c) - { - case '>': - ++pos; - skipWhitespace(text, pos); - return Combinator::kChild; - case '+': - ++pos; - skipWhitespace(text, pos); - return Combinator::kNextSibling; - case '~': - ++pos; - skipWhitespace(text, pos); - return Combinator::kLaterSibling; + // Restore position if we didn't find any combinator + pos = originalPos; + return nullopt; } - // Check for descendant combinator (whitespace that was skipped above) - if (originalPos != pos) + optional CSSelectorParser::parsePseudoClass(const string &name) { - // We skipped whitespace but didn't find an explicit combinator - // This means it's a descendant combinator - // Make sure there's content after the whitespace - if (pos < text.length()) - { - return Combinator::kDescendant; - } + if (name == "hover") + return PseudoClassType::kHover; + if (name == "active") + return PseudoClassType::kActive; + if (name == "focus") + return PseudoClassType::kFocus; + if (name == "focus-visible") + return PseudoClassType::kFocusVisible; + if (name == "focus-within") + return PseudoClassType::kFocusWithin; + if (name == "first-child") + return PseudoClassType::kFirstChild; + if (name == "last-child") + return PseudoClassType::kLastChild; + if (name == "first-of-type") + return PseudoClassType::kFirstOfType; + if (name == "last-of-type") + return PseudoClassType::kLastOfType; + if (name == "only-child") + return PseudoClassType::kOnlyChild; + if (name == "only-of-type") + return PseudoClassType::kOnlyOfType; + if (name == "where") + return PseudoClassType::kWhere; + + return nullopt; } - // Restore position if we didn't find any combinator - pos = originalPos; - return nullopt; - } + optional CSSelectorParser::parseFunctionalPseudoClass(const string &name, const string &text, size_t &pos) + { + if (name == "where") + { + // Expect opening parenthesis + if (pos >= text.length() || text[pos] != '(') + return nullopt; - optional CSSelectorParser::parsePseudoClass(const string &name) - { - if (name == "hover") - return PseudoClassType::kHover; - if (name == "active") - return PseudoClassType::kActive; - if (name == "focus") - return PseudoClassType::kFocus; - if (name == "focus-visible") - return PseudoClassType::kFocusVisible; - if (name == "focus-within") - return PseudoClassType::kFocusWithin; - if (name == "first-child") - return PseudoClassType::kFirstChild; - if (name == "last-child") - return PseudoClassType::kLastChild; - if (name == "first-of-type") - return PseudoClassType::kFirstOfType; - if (name == "last-of-type") - return PseudoClassType::kLastOfType; - if (name == "only-child") - return PseudoClassType::kOnlyChild; - if (name == "only-of-type") - return PseudoClassType::kOnlyOfType; - if (name == "where") - return PseudoClassType::kWhere; - - return nullopt; - } + ++pos; // Skip '(' - optional CSSelectorParser::parseFunctionalPseudoClass(const string &name, const string &text, size_t &pos) - { - if (name == "where") - { - // Expect opening parenthesis - if (pos >= text.length() || text[pos] != '(') - return nullopt; + // Find the matching closing parenthesis + size_t startPos = pos; + int parenCount = 1; + size_t endPos = pos; - ++pos; // Skip '(' + while (endPos < text.length() && parenCount > 0) + { + if (text[endPos] == '(') + parenCount++; + else if (text[endPos] == ')') + parenCount--; - // Find the matching closing parenthesis - size_t startPos = pos; - int parenCount = 1; - size_t endPos = pos; + endPos++; // Always move forward + } - while (endPos < text.length() && parenCount > 0) - { - if (text[endPos] == '(') - parenCount++; - else if (text[endPos] == ')') - parenCount--; + if (parenCount != 0) + return nullopt; // Unmatched parentheses - endPos++; // Always move forward - } + // endPos is now one position after the closing parenthesis + // The content is from startPos to endPos-1 (exclusive of closing paren) + string selectorListText = text.substr(startPos, endPos - 1 - startPos); - if (parenCount != 0) - return nullopt; // Unmatched parentheses + // Parse the selector list + auto argumentSelectors = parseMultipleSelectors(selectorListText); + if (!argumentSelectors) + return nullopt; - // endPos is now one position after the closing parenthesis - // The content is from startPos to endPos-1 (exclusive of closing paren) - string selectorListText = text.substr(startPos, endPos - 1 - startPos); + // Create shared_ptr directly + auto selectorListPtr = make_shared(*argumentSelectors); - // Parse the selector list - auto argumentSelectors = parseMultipleSelectors(selectorListText); - if (!argumentSelectors) - return nullopt; + // Update position to after the closing parenthesis + pos = endPos; - // Create shared_ptr directly - auto selectorListPtr = make_shared(*argumentSelectors); + return Component(ComponentType::kPseudoClass, PseudoClassType::kWhere, selectorListPtr); + } + else if (name == "nth-child" || name == "nth-of-type") + { + // Expect opening parenthesis + if (pos >= text.length() || text[pos] != '(') + return nullopt; - // Update position to after the closing parenthesis - pos = endPos; + ++pos; // Skip '(' - return Component(ComponentType::kPseudoClass, PseudoClassType::kWhere, selectorListPtr); - } - else if (name == "nth-child" || name == "nth-of-type") - { - // Expect opening parenthesis - if (pos >= text.length() || text[pos] != '(') - return nullopt; + // Find the closing parenthesis + size_t startPos = pos; + size_t endPos = pos; + while (endPos < text.length() && text[endPos] != ')') + endPos++; - ++pos; // Skip '(' + if (endPos >= text.length()) + return nullopt; // No closing parenthesis - // Find the closing parenthesis - size_t startPos = pos; - size_t endPos = pos; - while (endPos < text.length() && text[endPos] != ')') - endPos++; + // Extract the content inside parentheses + string content = text.substr(startPos, endPos - startPos); - if (endPos >= text.length()) - return nullopt; // No closing parenthesis + // Parse the an+b formula + int a = 0, b = 0; + if (!parseNthFormula(content, a, b)) + return nullopt; - // Extract the content inside parentheses - string content = text.substr(startPos, endPos - startPos); + // Update position to after the closing parenthesis + pos = endPos + 1; - // Parse the an+b formula - int a = 0, b = 0; - if (!parseNthFormula(content, a, b)) - return nullopt; + PseudoClassType pseudoType = (name == "nth-child") ? PseudoClassType::kNthChild : PseudoClassType::kNthOfType; + return Component(ComponentType::kPseudoClass, pseudoType, a, b); + } - // Update position to after the closing parenthesis - pos = endPos + 1; + return nullopt; // Unsupported functional pseudo-class + } - PseudoClassType pseudoType = (name == "nth-child") ? PseudoClassType::kNthChild : PseudoClassType::kNthOfType; - return Component(ComponentType::kPseudoClass, pseudoType, a, b); + void CSSelectorParser::skipWhitespace(const string &text, size_t &pos) + { + while (pos < text.length() && isspace(text[pos])) + ++pos; } - return nullopt; // Unsupported functional pseudo-class - } + string CSSelectorParser::parseIdentifier(const string &text, size_t &pos) + { + string result; - void CSSelectorParser::skipWhitespace(const string &text, size_t &pos) - { - while (pos < text.length() && isspace(text[pos])) - ++pos; - } + if (pos >= text.length() || !isIdentifierStart(text[pos])) + return result; - string CSSelectorParser::parseIdentifier(const string &text, size_t &pos) - { - string result; + while (pos < text.length() && isIdentifierChar(text[pos])) + { + result += text[pos]; + ++pos; + } - if (pos >= text.length() || !isIdentifierStart(text[pos])) return result; + } - while (pos < text.length() && isIdentifierChar(text[pos])) + bool CSSelectorParser::isIdentifierStart(char c) { - result += text[pos]; - ++pos; + return isalpha(c) || c == '_' || c == '-'; } - return result; - } - - bool CSSelectorParser::isIdentifierStart(char c) - { - return isalpha(c) || c == '_' || c == '-'; - } - - bool CSSelectorParser::isIdentifierChar(char c) - { - return isalnum(c) || c == '_' || c == '-'; - } - - optional CSSelectorParser::parseAttributeSelector(const string &text, size_t &pos) - { - if (pos >= text.length() || text[pos] != '[') - return nullopt; + bool CSSelectorParser::isIdentifierChar(char c) + { + return isalnum(c) || c == '_' || c == '-'; + } - ++pos; // Skip '[' + optional CSSelectorParser::parseAttributeSelector(const string &text, size_t &pos) + { + if (pos >= text.length() || text[pos] != '[') + return nullopt; - skipWhitespace(text, pos); + ++pos; // Skip '[' - // Parse attribute name - auto attributeName = parseIdentifier(text, pos); - if (attributeName.empty()) - return nullopt; + skipWhitespace(text, pos); - skipWhitespace(text, pos); + // Parse attribute name + auto attributeName = parseIdentifier(text, pos); + if (attributeName.empty()) + return nullopt; - // Check if it's just [attr] (existence check) - if (pos < text.length() && text[pos] == ']') - { - ++pos; - return Component(ComponentType::kAttribute, attributeName, AttributeMatchType::kExists); - } + skipWhitespace(text, pos); - // Parse attribute match operator - AttributeMatchType matchType = AttributeMatchType::kUnknown; - if (pos < text.length()) - { - char c = text[pos]; - if (c == '=') + // Check if it's just [attr] (existence check) + if (pos < text.length() && text[pos] == ']') { - matchType = AttributeMatchType::kExact; ++pos; + return Component(ComponentType::kAttribute, attributeName, AttributeMatchType::kExists); } - else if (pos + 1 < text.length() && text[pos + 1] == '=') + + // Parse attribute match operator + AttributeMatchType matchType = AttributeMatchType::kUnknown; + if (pos < text.length()) { - switch (c) + char c = text[pos]; + if (c == '=') { - case '~': - matchType = AttributeMatchType::kWhitespace; - break; - case '^': - matchType = AttributeMatchType::kPrefix; - break; - case '$': - matchType = AttributeMatchType::kSuffix; - break; - case '*': - matchType = AttributeMatchType::kSubstring; - break; - case '|': - matchType = AttributeMatchType::kDashPrefix; - break; - default: - return nullopt; + matchType = AttributeMatchType::kExact; + ++pos; + } + else if (pos + 1 < text.length() && text[pos + 1] == '=') + { + switch (c) + { + case '~': + matchType = AttributeMatchType::kWhitespace; + break; + case '^': + matchType = AttributeMatchType::kPrefix; + break; + case '$': + matchType = AttributeMatchType::kSuffix; + break; + case '*': + matchType = AttributeMatchType::kSubstring; + break; + case '|': + matchType = AttributeMatchType::kDashPrefix; + break; + default: + return nullopt; + } + pos += 2; // Skip operator and '=' + } + else + { + return nullopt; // Invalid operator } - pos += 2; // Skip operator and '=' } else { - return nullopt; // Invalid operator + return nullopt; // Unexpected end } - } - else - { - return nullopt; // Unexpected end - } - skipWhitespace(text, pos); + skipWhitespace(text, pos); - // Parse attribute value - string attributeValue; - if (pos < text.length()) - { - char c = text[pos]; - if (c == '"' || c == '\'') + // Parse attribute value + string attributeValue; + if (pos < text.length()) { - // Quoted string - char quote = c; - ++pos; - while (pos < text.length() && text[pos] != quote) + char c = text[pos]; + if (c == '"' || c == '\'') { - if (text[pos] == '\\' && pos + 1 < text.length()) + // Quoted string + char quote = c; + ++pos; + while (pos < text.length() && text[pos] != quote) { - // Handle escape sequences - ++pos; - if (pos < text.length()) + if (text[pos] == '\\' && pos + 1 < text.length()) + { + // Handle escape sequences + ++pos; + if (pos < text.length()) + { + attributeValue += text[pos]; + ++pos; + } + } + else { attributeValue += text[pos]; ++pos; } } + if (pos < text.length() && text[pos] == quote) + ++pos; // Skip closing quote else - { - attributeValue += text[pos]; - ++pos; - } + return nullopt; // Unclosed string } - if (pos < text.length() && text[pos] == quote) - ++pos; // Skip closing quote else - return nullopt; // Unclosed string - } - else - { - // Unquoted identifier - attributeValue = parseIdentifier(text, pos); - if (attributeValue.empty()) - return nullopt; + { + // Unquoted identifier + attributeValue = parseIdentifier(text, pos); + if (attributeValue.empty()) + return nullopt; + } } - } - skipWhitespace(text, pos); - - // Expect closing ']' - if (pos >= text.length() || text[pos] != ']') - return nullopt; - ++pos; + skipWhitespace(text, pos); - return Component(ComponentType::kAttribute, attributeName, matchType, attributeValue); - } + // Expect closing ']' + if (pos >= text.length() || text[pos] != ']') + return nullopt; + ++pos; - bool CSSelectorParser::parseNthFormula(const string &formula, int &a, int &b) - { - // Trim whitespace - string trimmed = formula; - size_t start = trimmed.find_first_not_of(" \t"); - if (start == string::npos) - return false; - trimmed = trimmed.substr(start); - size_t end = trimmed.find_last_not_of(" \t"); - if (end != string::npos) - trimmed = trimmed.substr(0, end + 1); - - // Handle special cases - if (trimmed == "odd") - { - a = 2; - b = 1; - return true; - } - if (trimmed == "even") - { - a = 2; - b = 0; - return true; + return Component(ComponentType::kAttribute, attributeName, matchType, attributeValue); } - // Check for just a number (e.g., "3") - if (trimmed.find('n') == string::npos) + bool CSSelectorParser::parseNthFormula(const string &formula, int &a, int &b) { - try + // Trim whitespace + string trimmed = formula; + size_t start = trimmed.find_first_not_of(" \t"); + if (start == string::npos) + return false; + trimmed = trimmed.substr(start); + size_t end = trimmed.find_last_not_of(" \t"); + if (end != string::npos) + trimmed = trimmed.substr(0, end + 1); + + // Handle special cases + if (trimmed == "odd") { - a = 0; - b = stoi(trimmed); + a = 2; + b = 1; return true; } - catch (...) + if (trimmed == "even") { - return false; + a = 2; + b = 0; + return true; } - } - // Parse an+b format - size_t nPos = trimmed.find('n'); - if (nPos == string::npos) - return false; + // Check for just a number (e.g., "3") + if (trimmed.find('n') == string::npos) + { + try + { + a = 0; + b = stoi(trimmed); + return true; + } + catch (...) + { + return false; + } + } - // Parse 'a' coefficient - string aPart = trimmed.substr(0, nPos); - if (aPart.empty() || aPart == "+") - { - a = 1; - } - else if (aPart == "-") - { - a = -1; - } - else - { - try + // Parse an+b format + size_t nPos = trimmed.find('n'); + if (nPos == string::npos) + return false; + + // Parse 'a' coefficient + string aPart = trimmed.substr(0, nPos); + if (aPart.empty() || aPart == "+") { - a = stoi(aPart); + a = 1; } - catch (...) + else if (aPart == "-") { - return false; + a = -1; } - } - - // Parse 'b' constant (if present) - b = 0; - if (nPos + 1 < trimmed.length()) - { - string bPart = trimmed.substr(nPos + 1); - // Remove leading + if present - if (!bPart.empty() && bPart[0] == '+') - bPart = bPart.substr(1); - - if (!bPart.empty()) + else { try { - b = stoi(bPart); + a = stoi(aPart); } catch (...) { return false; } } - } - return true; + // Parse 'b' constant (if present) + b = 0; + if (nPos + 1 < trimmed.length()) + { + string bPart = trimmed.substr(nPos + 1); + // Remove leading + if present + if (!bPart.empty() && bPart[0] == '+') + bPart = bPart.substr(1); + + if (!bPart.empty()) + { + try + { + b = stoi(bPart); + } + catch (...) + { + return false; + } + } + } + + return true; + } } -} \ No newline at end of file +} // namespace endor \ No newline at end of file diff --git a/src/client/cssom/selectors/css_selector_parser.hpp b/src/client/cssom/selectors/css_selector_parser.hpp index 1a8d8e5b5..ab0d71914 100644 --- a/src/client/cssom/selectors/css_selector_parser.hpp +++ b/src/client/cssom/selectors/css_selector_parser.hpp @@ -5,385 +5,388 @@ #include #include -namespace client_cssom::selectors +namespace endor { - // Forward declarations - class Component; - class Selector; - class SelectorList; + namespace client_cssom::selectors + { + // Forward declarations + class Component; + class Selector; + class SelectorList; - /** + /** * CSS Selector Component Types * These correspond to the Rust enum definitions in css_parser.rs */ - enum class ComponentType - { - kLocalName, // Tag name (e.g., div, p, h1) - kID, // ID selector (e.g., #myid) - kClass, // Class selector (e.g., .myclass) - kUniversal, // Universal selector (*) - kAttribute, // Attribute selector (e.g., [attr], [attr=value]) - kRoot, // :root pseudo-class - kEmpty, // :empty pseudo-class - kScope, // :scope pseudo-class - kHost, // :host pseudo-class - kPseudoElement, // Pseudo-elements (e.g., ::before, ::after) - kPseudoClass, // Other pseudo-classes (e.g., :hover, :focus) - kCombinator, // Combinators (>, +, ~, space) - kUnknown - }; + enum class ComponentType + { + kLocalName, // Tag name (e.g., div, p, h1) + kID, // ID selector (e.g., #myid) + kClass, // Class selector (e.g., .myclass) + kUniversal, // Universal selector (*) + kAttribute, // Attribute selector (e.g., [attr], [attr=value]) + kRoot, // :root pseudo-class + kEmpty, // :empty pseudo-class + kScope, // :scope pseudo-class + kHost, // :host pseudo-class + kPseudoElement, // Pseudo-elements (e.g., ::before, ::after) + kPseudoClass, // Other pseudo-classes (e.g., :hover, :focus) + kCombinator, // Combinators (>, +, ~, space) + kUnknown + }; - /** + /** * CSS Attribute Selector Match Types * These define how attribute values are matched */ - enum class AttributeMatchType - { - kExists, // [attr] - attribute exists - kExact, // [attr=value] - exact match - kWhitespace, // [attr~=value] - whitespace-separated list contains value - kPrefix, // [attr^=value] - starts with value - kSuffix, // [attr$=value] - ends with value - kSubstring, // [attr*=value] - contains value as substring - kDashPrefix, // [attr|=value] - equals value or starts with value- - kUnknown - }; + enum class AttributeMatchType + { + kExists, // [attr] - attribute exists + kExact, // [attr=value] - exact match + kWhitespace, // [attr~=value] - whitespace-separated list contains value + kPrefix, // [attr^=value] - starts with value + kSuffix, // [attr$=value] - ends with value + kSubstring, // [attr*=value] - contains value as substring + kDashPrefix, // [attr|=value] - equals value or starts with value- + kUnknown + }; - /** + /** * CSS Selector Combinators * These define relationships between selectors */ - enum class Combinator - { - kChild, // > (direct child) - kDescendant, // space (descendant) - kNextSibling, // + (adjacent sibling) - kLaterSibling, // ~ (general sibling) - kPseudoElement, // :: (pseudo-element) - kSlotAssignment, // / (slot assignment) - kPart, // part pseudo-element - kUnknown - }; + enum class Combinator + { + kChild, // > (direct child) + kDescendant, // space (descendant) + kNextSibling, // + (adjacent sibling) + kLaterSibling, // ~ (general sibling) + kPseudoElement, // :: (pseudo-element) + kSlotAssignment, // / (slot assignment) + kPart, // part pseudo-element + kUnknown + }; - /** + /** * CSS Pseudo-Class Types * Specific types of pseudo-classes */ - enum class PseudoClassType - { - kHover, - kActive, - kFocus, - kFocusVisible, - kFocusWithin, - kFirstChild, - kLastChild, - kNthChild, - kNthLastChild, - kFirstOfType, - kLastOfType, - kNthOfType, - kNthLastOfType, - kOnlyChild, - kOnlyOfType, - kWhere, - kUnknown - }; + enum class PseudoClassType + { + kHover, + kActive, + kFocus, + kFocusVisible, + kFocusWithin, + kFirstChild, + kLastChild, + kNthChild, + kNthLastChild, + kFirstOfType, + kLastOfType, + kNthOfType, + kNthLastOfType, + kOnlyChild, + kOnlyOfType, + kWhere, + kUnknown + }; - /** + /** * A single component of a CSS selector * Can represent tag names, classes, IDs, pseudo-classes, or combinators */ - class Component - { - public: - Component(ComponentType type, const std::string &name = "", Combinator combinator = Combinator::kUnknown, PseudoClassType pseudoClassType = PseudoClassType::kUnknown); + class Component + { + public: + Component(ComponentType type, const std::string &name = "", Combinator combinator = Combinator::kUnknown, PseudoClassType pseudoClassType = PseudoClassType::kUnknown); - // Constructor for functional pseudo-classes like :where() - Component(ComponentType type, PseudoClassType pseudoClassType, std::shared_ptr argumentSelectorList); + // Constructor for functional pseudo-classes like :where() + Component(ComponentType type, PseudoClassType pseudoClassType, std::shared_ptr argumentSelectorList); - // Constructor for attribute selectors - Component(ComponentType type, const std::string &attributeName, AttributeMatchType matchType, const std::string &attributeValue = ""); + // Constructor for attribute selectors + Component(ComponentType type, const std::string &attributeName, AttributeMatchType matchType, const std::string &attributeValue = ""); - // Constructor for nth-child/nth-of-type pseudo-classes - Component(ComponentType type, PseudoClassType pseudoClassType, int nthA, int nthB); + // Constructor for nth-child/nth-of-type pseudo-classes + Component(ComponentType type, PseudoClassType pseudoClassType, int nthA, int nthB); - // Type checking methods - bool isLocalName() const - { - return type_ == ComponentType::kLocalName; - } - bool isId() const - { - return type_ == ComponentType::kID; - } - bool isClass() const - { - return type_ == ComponentType::kClass; - } - bool isUniversal() const - { - return type_ == ComponentType::kUniversal; - } - bool isAttribute() const - { - return type_ == ComponentType::kAttribute; - } - bool isRoot() const - { - return type_ == ComponentType::kRoot; - } - bool isEmpty() const - { - return type_ == ComponentType::kEmpty; - } - bool isHost() const - { - return type_ == ComponentType::kHost; - } - bool isPseudoElement() const - { - return type_ == ComponentType::kPseudoElement; - } - bool isPseudoClass() const - { - return type_ == ComponentType::kPseudoClass; - } - bool isCombinator() const - { - return type_ == ComponentType::kCombinator; - } + // Type checking methods + bool isLocalName() const + { + return type_ == ComponentType::kLocalName; + } + bool isId() const + { + return type_ == ComponentType::kID; + } + bool isClass() const + { + return type_ == ComponentType::kClass; + } + bool isUniversal() const + { + return type_ == ComponentType::kUniversal; + } + bool isAttribute() const + { + return type_ == ComponentType::kAttribute; + } + bool isRoot() const + { + return type_ == ComponentType::kRoot; + } + bool isEmpty() const + { + return type_ == ComponentType::kEmpty; + } + bool isHost() const + { + return type_ == ComponentType::kHost; + } + bool isPseudoElement() const + { + return type_ == ComponentType::kPseudoElement; + } + bool isPseudoClass() const + { + return type_ == ComponentType::kPseudoClass; + } + bool isCombinator() const + { + return type_ == ComponentType::kCombinator; + } - // Pseudo-class subtype checking - bool isHover() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kHover; - } - bool isActive() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kActive; - } - bool isFocus() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kFocus; - } - bool isFirstChild() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kFirstChild; - } - bool isLastChild() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kLastChild; - } - bool isFirstOfType() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kFirstOfType; - } - bool isLastOfType() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kLastOfType; - } - bool isWhere() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kWhere; - } - bool isNthChild() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kNthChild; - } - bool isNthOfType() const - { - return isPseudoClass() && pseudoClassType_ == PseudoClassType::kNthOfType; - } + // Pseudo-class subtype checking + bool isHover() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kHover; + } + bool isActive() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kActive; + } + bool isFocus() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kFocus; + } + bool isFirstChild() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kFirstChild; + } + bool isLastChild() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kLastChild; + } + bool isFirstOfType() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kFirstOfType; + } + bool isLastOfType() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kLastOfType; + } + bool isWhere() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kWhere; + } + bool isNthChild() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kNthChild; + } + bool isNthOfType() const + { + return isPseudoClass() && pseudoClassType_ == PseudoClassType::kNthOfType; + } - // Accessors - ComponentType type() const - { - return type_; - } - const std::string &name() const - { - return name_; - } - const std::string &id() const - { - return name_; - } // For ID components - const std::string &className() const - { - return name_; - } // For class components - Combinator combinator() const - { - return combinator_; - } - PseudoClassType pseudoClassType() const - { - return pseudoClassType_; - } - const std::shared_ptr &argumentSelectorList() const - { - return argumentSelectorList_; - } + // Accessors + ComponentType type() const + { + return type_; + } + const std::string &name() const + { + return name_; + } + const std::string &id() const + { + return name_; + } // For ID components + const std::string &className() const + { + return name_; + } // For class components + Combinator combinator() const + { + return combinator_; + } + PseudoClassType pseudoClassType() const + { + return pseudoClassType_; + } + const std::shared_ptr &argumentSelectorList() const + { + return argumentSelectorList_; + } - // Attribute selector accessors - const std::string &attributeName() const - { - return name_; // For attribute components, name_ holds the attribute name - } - const std::string &attributeValue() const - { - return attributeValue_; - } - AttributeMatchType attributeMatchType() const - { - return attributeMatchType_; - } + // Attribute selector accessors + const std::string &attributeName() const + { + return name_; // For attribute components, name_ holds the attribute name + } + const std::string &attributeValue() const + { + return attributeValue_; + } + AttributeMatchType attributeMatchType() const + { + return attributeMatchType_; + } - // nth-child/nth-of-type accessors (for an+b formula) - int nthA() const - { - return nthA_; - } - int nthB() const - { - return nthB_; - } + // nth-child/nth-of-type accessors (for an+b formula) + int nthA() const + { + return nthA_; + } + int nthB() const + { + return nthB_; + } - // String representation - operator std::string() const; + // String representation + operator std::string() const; - private: - ComponentType type_; - std::string name_; - Combinator combinator_; - PseudoClassType pseudoClassType_; - std::shared_ptr argumentSelectorList_; // For functional pseudo-classes like :where() + private: + ComponentType type_; + std::string name_; + Combinator combinator_; + PseudoClassType pseudoClassType_; + std::shared_ptr argumentSelectorList_; // For functional pseudo-classes like :where() - // Attribute selector specific fields - AttributeMatchType attributeMatchType_; - std::string attributeValue_; + // Attribute selector specific fields + AttributeMatchType attributeMatchType_; + std::string attributeValue_; - // nth-child/nth-of-type specific fields (for an+b formula) - int nthA_; // 'a' coefficient in an+b - int nthB_; // 'b' constant in an+b - }; + // nth-child/nth-of-type specific fields (for an+b formula) + int nthA_; // 'a' coefficient in an+b + int nthB_; // 'b' constant in an+b + }; - /** + /** * A CSS selector consists of one or more components * e.g., "div.class > p:hover" has multiple components */ - class Selector - { - public: - Selector() = default; - explicit Selector(std::vector components); - - const std::vector &components() const - { - return components_; - } - void addComponent(const Component &component) - { - components_.push_back(component); - } - bool empty() const - { - return components_.empty(); - } - size_t size() const + class Selector { - return components_.size(); - } + public: + Selector() = default; + explicit Selector(std::vector components); - // String representation - operator std::string() const; + const std::vector &components() const + { + return components_; + } + void addComponent(const Component &component) + { + components_.push_back(component); + } + bool empty() const + { + return components_.empty(); + } + size_t size() const + { + return components_.size(); + } - private: - std::vector components_; - }; + // String representation + operator std::string() const; - /** + private: + std::vector components_; + }; + + /** * A list of CSS selectors separated by commas * e.g., "div, .class, #id" is a selector list with 3 selectors */ - class SelectorList - { - public: - SelectorList() = default; - explicit SelectorList(std::vector selectors); - - const std::vector &selectors() const + class SelectorList { - return selectors_; - } - void addSelector(const Selector &selector) - { - selectors_.push_back(selector); - } - bool empty() const - { - return selectors_.empty(); - } - size_t size() const - { - return selectors_.size(); - } + public: + SelectorList() = default; + explicit SelectorList(std::vector selectors); - // Iterator support for range-based loops - auto begin() const - { - return selectors_.begin(); - } - auto end() const - { - return selectors_.end(); - } - auto begin() - { - return selectors_.begin(); - } - auto end() - { - return selectors_.end(); - } + const std::vector &selectors() const + { + return selectors_; + } + void addSelector(const Selector &selector) + { + selectors_.push_back(selector); + } + bool empty() const + { + return selectors_.empty(); + } + size_t size() const + { + return selectors_.size(); + } + + // Iterator support for range-based loops + auto begin() const + { + return selectors_.begin(); + } + auto end() const + { + return selectors_.end(); + } + auto begin() + { + return selectors_.begin(); + } + auto end() + { + return selectors_.end(); + } - // String representation - operator std::string() const; + // String representation + operator std::string() const; - private: - std::vector selectors_; - }; + private: + std::vector selectors_; + }; - /** + /** * CSS Selector Parser * Parses CSS selector strings into structured selector lists */ - class CSSelectorParser - { - public: - /** + class CSSelectorParser + { + public: + /** * Parse a CSS selector string into a SelectorList * @param selectorText The CSS selector string to parse * @return Optional SelectorList if parsing succeeds, nullopt otherwise */ - static std::optional parseSelectors(const std::string &selectorText); + static std::optional parseSelectors(const std::string &selectorText); - private: - // Internal parsing methods - static std::optional> parseMultipleSelectors(const std::string &text); - static std::optional parseSingleSelector(const std::string &text); - static std::optional parseComponent(const std::string &text, size_t &pos); - static std::optional parseFunctionalPseudoClass(const std::string &name, const std::string &text, size_t &pos); - static std::optional parseAttributeSelector(const std::string &text, size_t &pos); - static std::optional parseCombinator(const std::string &text, size_t &pos); - static std::optional parsePseudoClass(const std::string &name); + private: + // Internal parsing methods + static std::optional> parseMultipleSelectors(const std::string &text); + static std::optional parseSingleSelector(const std::string &text); + static std::optional parseComponent(const std::string &text, size_t &pos); + static std::optional parseFunctionalPseudoClass(const std::string &name, const std::string &text, size_t &pos); + static std::optional parseAttributeSelector(const std::string &text, size_t &pos); + static std::optional parseCombinator(const std::string &text, size_t &pos); + static std::optional parsePseudoClass(const std::string &name); - // Utility methods - static void skipWhitespace(const std::string &text, size_t &pos); - static std::string parseIdentifier(const std::string &text, size_t &pos); - static bool isIdentifierStart(char c); - static bool isIdentifierChar(char c); - static bool parseNthFormula(const std::string &formula, int &a, int &b); - }; -} \ No newline at end of file + // Utility methods + static void skipWhitespace(const std::string &text, size_t &pos); + static std::string parseIdentifier(const std::string &text, size_t &pos); + static bool isIdentifierStart(char c); + static bool isIdentifierChar(char c); + static bool parseNthFormula(const std::string &formula, int &a, int &b); + }; + } +} // namespace endor \ No newline at end of file diff --git a/src/client/cssom/selectors/matching.cpp b/src/client/cssom/selectors/matching.cpp index f8b298e31..6ebb2fe59 100644 --- a/src/client/cssom/selectors/matching.cpp +++ b/src/client/cssom/selectors/matching.cpp @@ -3,342 +3,411 @@ #include #include "./matching.hpp" -namespace client_cssom::selectors +namespace endor { - using namespace std; - using namespace dom; - - // Helper functions for pseudo-class matching - bool isRootElement(const shared_ptr element) + namespace client_cssom::selectors { - try - { - const auto &document = element->getOwnerDocumentChecked(); - auto documentElement = document.documentElement(); - return documentElement && documentElement == element; - } - catch (...) - { - return false; - } - } + using namespace std; + using namespace dom; - bool isFirstChild(const shared_ptr element) - { - auto parent = element->getParentNode(); - if (!parent) - return false; - - auto firstChild = parent->firstChild(); - return firstChild && firstChild == element; - } - - bool isLastChild(const shared_ptr element) - { - auto parent = element->getParentNode(); - if (!parent) - return false; - - auto lastChild = parent->lastChild(); - return lastChild && lastChild == element; - } - - bool isFirstOfType(const shared_ptr element) - { - auto parent = element->getParentNode(); - if (!parent) - return false; - - // Check all previous siblings to see if any have the same tag name - auto currentSibling = element->previousSibling(); - while (currentSibling) + // Helper functions for pseudo-class matching + bool isRootElement(const shared_ptr element) { - auto siblingElement = dynamic_pointer_cast(currentSibling); - if (siblingElement && strcasecmp(siblingElement->tagName.c_str(), element->tagName.c_str()) == 0) - return false; // Found a sibling of the same type before this element - currentSibling = currentSibling->previousSibling(); + try + { + const auto &document = element->getOwnerDocumentChecked(); + auto documentElement = document.documentElement(); + return documentElement && documentElement == element; + } + catch (...) + { + return false; + } } - return true; - } - - bool isLastOfType(const shared_ptr element) - { - auto parent = element->getParentNode(); - if (!parent) - return false; - // Check all next siblings to see if any have the same tag name - auto currentSibling = element->nextSibling(); - while (currentSibling) + bool isFirstChild(const shared_ptr element) { - auto siblingElement = dynamic_pointer_cast(currentSibling); - if (siblingElement && strcasecmp(siblingElement->tagName.c_str(), element->tagName.c_str()) == 0) - return false; // Found a sibling of the same type after this element - currentSibling = currentSibling->nextSibling(); - } - return true; - } - - bool isNthChild(const shared_ptr element, int a, int b) - { - auto parent = element->getParentNode(); - if (!parent) - return false; + auto parent = element->getParentNode(); + if (!parent) + return false; - // Count the element's position among all element siblings (1-indexed) - int position = 1; - auto currentSibling = element->previousSibling(); - while (currentSibling) - { - auto siblingElement = dynamic_pointer_cast(currentSibling); - if (siblingElement) - position++; - currentSibling = currentSibling->previousSibling(); + auto firstChild = parent->firstChild(); + return firstChild && firstChild == element; } - // Check if position matches the an+b formula - if (a == 0) - { - // Simple position match (e.g., nth-child(3)) - return position == b; - } - else if (a > 0) + bool isLastChild(const shared_ptr element) { - // Forward sequence (e.g., 2n+1) - if (position < b) + auto parent = element->getParentNode(); + if (!parent) return false; - return (position - b) % a == 0; + + auto lastChild = parent->lastChild(); + return lastChild && lastChild == element; } - else + + bool isFirstOfType(const shared_ptr element) { - // Backward sequence (e.g., -n+3) - if (position > b) + auto parent = element->getParentNode(); + if (!parent) return false; - return (b - position) % (-a) == 0; - } - } - - bool isNthOfType(const shared_ptr element, int a, int b) - { - auto parent = element->getParentNode(); - if (!parent) - return false; - // Count the element's position among siblings of the same type (1-indexed) - int position = 1; - auto currentSibling = element->previousSibling(); - while (currentSibling) - { - auto siblingElement = dynamic_pointer_cast(currentSibling); - if (siblingElement && strcasecmp(siblingElement->tagName.c_str(), element->tagName.c_str()) == 0) - position++; - currentSibling = currentSibling->previousSibling(); + // Check all previous siblings to see if any have the same tag name + auto currentSibling = element->previousSibling(); + while (currentSibling) + { + auto siblingElement = dynamic_pointer_cast(currentSibling); + if (siblingElement && strcasecmp(siblingElement->tagName.c_str(), element->tagName.c_str()) == 0) + return false; // Found a sibling of the same type before this element + currentSibling = currentSibling->previousSibling(); + } + return true; } - // Check if position matches the an+b formula - if (a == 0) + bool isLastOfType(const shared_ptr element) { - return position == b; + auto parent = element->getParentNode(); + if (!parent) + return false; + + // Check all next siblings to see if any have the same tag name + auto currentSibling = element->nextSibling(); + while (currentSibling) + { + auto siblingElement = dynamic_pointer_cast(currentSibling); + if (siblingElement && strcasecmp(siblingElement->tagName.c_str(), element->tagName.c_str()) == 0) + return false; // Found a sibling of the same type after this element + currentSibling = currentSibling->nextSibling(); + } + return true; } - else if (a > 0) + + bool isNthChild(const shared_ptr element, int a, int b) { - if (position < b) + auto parent = element->getParentNode(); + if (!parent) return false; - return (position - b) % a == 0; + + // Count the element's position among all element siblings (1-indexed) + int position = 1; + auto currentSibling = element->previousSibling(); + while (currentSibling) + { + auto siblingElement = dynamic_pointer_cast(currentSibling); + if (siblingElement) + position++; + currentSibling = currentSibling->previousSibling(); + } + + // Check if position matches the an+b formula + if (a == 0) + { + // Simple position match (e.g., nth-child(3)) + return position == b; + } + else if (a > 0) + { + // Forward sequence (e.g., 2n+1) + if (position < b) + return false; + return (position - b) % a == 0; + } + else + { + // Backward sequence (e.g., -n+3) + if (position > b) + return false; + return (b - position) % (-a) == 0; + } } - else + + bool isNthOfType(const shared_ptr element, int a, int b) { - if (position > b) + auto parent = element->getParentNode(); + if (!parent) return false; - return (b - position) % (-a) == 0; + + // Count the element's position among siblings of the same type (1-indexed) + int position = 1; + auto currentSibling = element->previousSibling(); + while (currentSibling) + { + auto siblingElement = dynamic_pointer_cast(currentSibling); + if (siblingElement && strcasecmp(siblingElement->tagName.c_str(), element->tagName.c_str()) == 0) + position++; + currentSibling = currentSibling->previousSibling(); + } + + // Check if position matches the an+b formula + if (a == 0) + { + return position == b; + } + else if (a > 0) + { + if (position < b) + return false; + return (position - b) % a == 0; + } + else + { + if (position > b) + return false; + return (b - position) % (-a) == 0; + } } - } - bool matchesSelectorList(const SelectorList &selectors, const shared_ptr element) - { - MatchingContext context; - for (const auto &selector : selectors) + bool matchesSelectorList(const SelectorList &selectors, const shared_ptr element) { - if (matchesSelector(selector, element, context)) - return true; + MatchingContext context; + for (const auto &selector : selectors) + { + if (matchesSelector(selector, element, context)) + return true; + } + return false; } - return false; - } - bool matchesSelector(const Selector &selector, const shared_ptr element, MatchingContext &context) - { - assert(!selector.empty()); + bool matchesSelector(const Selector &selector, const shared_ptr element, MatchingContext &context) + { + assert(!selector.empty()); - // CSS selectors are matched right-to-left, so we need to start from the end - // and work backwards through the components - return matchesSelectorFromEnd(selector, element, context); - } + // CSS selectors are matched right-to-left, so we need to start from the end + // and work backwards through the components + return matchesSelectorFromEnd(selector, element, context); + } - bool matchesSelectorComponentNonCombinator(const Component &component, - const shared_ptr element, - MatchingContext &context) - { - assert(!component.isCombinator()); - - if (component.isUniversal()) - return true; // Universal selector matches any element - if (component.isLocalName()) - return strcasecmp(element->tagName.c_str(), component.name().c_str()) == 0; - if (component.isId()) - return element->id == component.id(); - if (component.isClass()) - return element->classList().contains(component.name()); - if (component.isAttribute()) + bool matchesSelectorComponentNonCombinator(const Component &component, + const shared_ptr element, + MatchingContext &context) { - const std::string &attrName = component.attributeName(); - const std::string &attrValue = component.attributeValue(); + assert(!component.isCombinator()); + + if (component.isUniversal()) + return true; // Universal selector matches any element + if (component.isLocalName()) + return strcasecmp(element->tagName.c_str(), component.name().c_str()) == 0; + if (component.isId()) + return element->id == component.id(); + if (component.isClass()) + return element->classList().contains(component.name()); + if (component.isAttribute()) + { + const std::string &attrName = component.attributeName(); + const std::string &attrValue = component.attributeValue(); - // Check if element has the attribute - if (!element->hasAttribute(attrName)) - return false; + // Check if element has the attribute + if (!element->hasAttribute(attrName)) + return false; - // For existence check, just having the attribute is enough - if (component.attributeMatchType() == AttributeMatchType::kExists) - return true; + // For existence check, just having the attribute is enough + if (component.attributeMatchType() == AttributeMatchType::kExists) + return true; - // Get the actual attribute value - std::string elementAttrValue = element->getAttribute(attrName); + // Get the actual attribute value + std::string elementAttrValue = element->getAttribute(attrName); - switch (component.attributeMatchType()) - { - case AttributeMatchType::kExact: - return elementAttrValue == attrValue; - case AttributeMatchType::kWhitespace: - { - // Check if attrValue appears as a whole word in a whitespace-separated list - std::istringstream iss(elementAttrValue); - std::string word; - while (iss >> word) + switch (component.attributeMatchType()) { - if (word == attrValue) - return true; + case AttributeMatchType::kExact: + return elementAttrValue == attrValue; + case AttributeMatchType::kWhitespace: + { + // Check if attrValue appears as a whole word in a whitespace-separated list + std::istringstream iss(elementAttrValue); + std::string word; + while (iss >> word) + { + if (word == attrValue) + return true; + } + return false; + } + case AttributeMatchType::kPrefix: + return elementAttrValue.length() >= attrValue.length() && + elementAttrValue.substr(0, attrValue.length()) == attrValue; + case AttributeMatchType::kSuffix: + return elementAttrValue.length() >= attrValue.length() && + elementAttrValue.substr(elementAttrValue.length() - attrValue.length()) == attrValue; + case AttributeMatchType::kSubstring: + return elementAttrValue.find(attrValue) != std::string::npos; + case AttributeMatchType::kDashPrefix: + return elementAttrValue == attrValue || + (elementAttrValue.length() > attrValue.length() && + elementAttrValue.substr(0, attrValue.length()) == attrValue && + elementAttrValue[attrValue.length()] == '-'); + default: + return false; } - return false; - } - case AttributeMatchType::kPrefix: - return elementAttrValue.length() >= attrValue.length() && - elementAttrValue.substr(0, attrValue.length()) == attrValue; - case AttributeMatchType::kSuffix: - return elementAttrValue.length() >= attrValue.length() && - elementAttrValue.substr(elementAttrValue.length() - attrValue.length()) == attrValue; - case AttributeMatchType::kSubstring: - return elementAttrValue.find(attrValue) != std::string::npos; - case AttributeMatchType::kDashPrefix: - return elementAttrValue == attrValue || - (elementAttrValue.length() > attrValue.length() && - elementAttrValue.substr(0, attrValue.length()) == attrValue && - elementAttrValue[attrValue.length()] == '-'); - default: - return false; } - } - if (component.isPseudoClass()) - { - if (component.isHover()) - return element->isHovered(); - if (component.isFocus()) - return element->isFocused(); - if (component.isFirstChild()) - return isFirstChild(element); - if (component.isLastChild()) - return isLastChild(element); - if (component.isFirstOfType()) - return isFirstOfType(element); - if (component.isLastOfType()) - return isLastOfType(element); - if (component.isWhere()) + if (component.isPseudoClass()) { - // :where() matches if any selector in its argument list matches the element - if (component.argumentSelectorList()) + if (component.isHover()) + return element->isHovered(); + if (component.isFocus()) + return element->isFocused(); + if (component.isFirstChild()) + return isFirstChild(element); + if (component.isLastChild()) + return isLastChild(element); + if (component.isFirstOfType()) + return isFirstOfType(element); + if (component.isLastOfType()) + return isLastOfType(element); + if (component.isWhere()) { - return matchesSelectorList(*component.argumentSelectorList(), element); + // :where() matches if any selector in its argument list matches the element + if (component.argumentSelectorList()) + { + return matchesSelectorList(*component.argumentSelectorList(), element); + } + return false; // Empty :where() matches nothing } - return false; // Empty :where() matches nothing - } - if (component.isNthChild()) - { - return isNthChild(element, component.nthA(), component.nthB()); - } - if (component.isNthOfType()) - { - return isNthOfType(element, component.nthA(), component.nthB()); + if (component.isNthChild()) + { + return isNthChild(element, component.nthA(), component.nthB()); + } + if (component.isNthOfType()) + { + return isNthOfType(element, component.nthA(), component.nthB()); + } + // TODO: Implement support for :active pseudo-class when element->isActive() is available. } - // TODO: Implement support for :active pseudo-class when element->isActive() is available. + + if (component.isRoot()) + return isRootElement(element); + // if (component.isEmpty()) + // return element->isEmpty(); + + // Returns false if the above checks did not match. + return false; } - if (component.isRoot()) - return isRootElement(element); - // if (component.isEmpty()) - // return element->isEmpty(); + bool matchesSelectorFromEnd(const Selector &selector, + const shared_ptr element, + MatchingContext &context) + { + const auto &components = selector.components(); + if (components.empty()) + return false; - // Returns false if the above checks did not match. - return false; - } + // Start from the end (rightmost component) and work backwards + int currentPos = components.size() - 1; + shared_ptr currentElement = element; - bool matchesSelectorFromEnd(const Selector &selector, - const shared_ptr element, - MatchingContext &context) - { - const auto &components = selector.components(); - if (components.empty()) - return false; + while (currentPos >= 0) + { + const auto &component = components[currentPos]; + + if (component.isCombinator()) + { + // Move to the next element based on combinator type + switch (component.combinator()) + { + case Combinator::kChild: + // Child combinator: element must be direct child + if (!currentElement->hasTypedParentNode()) + return false; + currentElement = currentElement->getParentNodeAs(); + break; + + case Combinator::kDescendant: + // Descendant combinator: find an ancestor that matches next component + if (!currentElement->hasTypedParentNode()) + return false; + + // Get the next component (to the left) that we need to match + if (currentPos == 0) + return false; // No component to match + + { + const auto &ancestorComponent = components[currentPos - 1]; + shared_ptr ancestor = currentElement->getParentNodeAs(); - // Start from the end (rightmost component) and work backwards - int currentPos = components.size() - 1; - shared_ptr currentElement = element; + // Search up the ancestor chain + while (ancestor != nullptr) + { + if (matchesSelectorComponentNonCombinator(ancestorComponent, ancestor, context)) + { + currentElement = ancestor; + currentPos--; // Skip the ancestor component since we matched it + break; + } + ancestor = ancestor->getParentNodeAs(); + } - while (currentPos >= 0) + if (ancestor == nullptr) + return false; // No matching ancestor found + } + break; + + case Combinator::kNextSibling: + case Combinator::kLaterSibling: + case Combinator::kPseudoElement: + case Combinator::kSlotAssignment: + case Combinator::kPart: + case Combinator::kUnknown: + // TODO: Implement these combinators + return false; + } + } + else + { + // Non-combinator component - check if current element matches + if (!matchesSelectorComponentNonCombinator(component, currentElement, context)) + return false; + } + + currentPos--; + } + + return true; // All components matched + } + + bool matchesSelectorComponent(const Selector &selector, + vector::const_iterator &it, + const shared_ptr element, + MatchingContext &context) { - const auto &component = components[currentPos]; + // If we reached the end of the selector, it means that the element matches all the components. + if (it == selector.components().end()) + return true; + + shared_ptr nextElement = element; // The next element to check + const auto &component = *it; if (component.isCombinator()) { - // Move to the next element based on combinator type switch (component.combinator()) { case Combinator::kChild: - // Child combinator: element must be direct child - if (!currentElement->hasTypedParentNode()) + if (!element->hasTypedParentNode()) return false; - currentElement = currentElement->getParentNodeAs(); + nextElement = element->getParentNodeAs(); break; - case Combinator::kDescendant: - // Descendant combinator: find an ancestor that matches next component - if (!currentElement->hasTypedParentNode()) + if (!element->hasTypedParentNode()) return false; - - // Get the next component (to the left) that we need to match - if (currentPos == 0) - return false; // No component to match - + else { - const auto &ancestorComponent = components[currentPos - 1]; - shared_ptr ancestor = currentElement->getParentNodeAs(); - - // Search up the ancestor chain - while (ancestor != nullptr) + const Component &ancestorComponent = *(++it); + shared_ptr maybeAncestorElement = element->getParentNodeAs(); + while (true) { - if (matchesSelectorComponentNonCombinator(ancestorComponent, ancestor, context)) + // If we reached the root element, we can stop. + if (maybeAncestorElement == nullptr) + return false; + + // If the ancestor element matches the ancestor component, we can go to the next component. + if (matchesSelectorComponentNonCombinator(ancestorComponent, maybeAncestorElement, context)) { - currentElement = ancestor; - currentPos--; // Skip the ancestor component since we matched it + nextElement = maybeAncestorElement; break; } - ancestor = ancestor->getParentNodeAs(); + maybeAncestorElement = maybeAncestorElement->getParentNodeAs(); } - - if (ancestor == nullptr) - return false; // No matching ancestor found } break; - case Combinator::kNextSibling: case Combinator::kLaterSibling: case Combinator::kPseudoElement: @@ -346,89 +415,23 @@ namespace client_cssom::selectors case Combinator::kPart: case Combinator::kUnknown: // TODO: Implement these combinators - return false; + break; } } else { - // Non-combinator component - check if current element matches - if (!matchesSelectorComponentNonCombinator(component, currentElement, context)) + // Non-combinator component, we need to check if the element matches the component. + // - If the element matches the component, we can go to the next component to check until the end of the selector. + // - If the element does not match the component, we can stop and return false. + if (!matchesSelectorComponentNonCombinator(component, element, context)) return false; } - currentPos--; - } - - return true; // All components matched - } - - bool matchesSelectorComponent(const Selector &selector, - vector::const_iterator &it, - const shared_ptr element, - MatchingContext &context) - { - // If we reached the end of the selector, it means that the element matches all the components. - if (it == selector.components().end()) - return true; - - shared_ptr nextElement = element; // The next element to check - const auto &component = *it; - - if (component.isCombinator()) - { - switch (component.combinator()) - { - case Combinator::kChild: - if (!element->hasTypedParentNode()) - return false; - nextElement = element->getParentNodeAs(); - break; - case Combinator::kDescendant: - if (!element->hasTypedParentNode()) - return false; - else - { - const Component &ancestorComponent = *(++it); - shared_ptr maybeAncestorElement = element->getParentNodeAs(); - while (true) - { - // If we reached the root element, we can stop. - if (maybeAncestorElement == nullptr) - return false; - - // If the ancestor element matches the ancestor component, we can go to the next component. - if (matchesSelectorComponentNonCombinator(ancestorComponent, maybeAncestorElement, context)) - { - nextElement = maybeAncestorElement; - break; - } - maybeAncestorElement = maybeAncestorElement->getParentNodeAs(); - } - } - break; - case Combinator::kNextSibling: - case Combinator::kLaterSibling: - case Combinator::kPseudoElement: - case Combinator::kSlotAssignment: - case Combinator::kPart: - case Combinator::kUnknown: - // TODO: Implement these combinators - break; - } - } - else - { - // Non-combinator component, we need to check if the element matches the component. - // - If the element matches the component, we can go to the next component to check until the end of the selector. - // - If the element does not match the component, we can stop and return false. - if (!matchesSelectorComponentNonCombinator(component, element, context)) - return false; + // Go to the next component + return matchesSelectorComponent(selector, + ++it, + nextElement, + context); } - - // Go to the next component - return matchesSelectorComponent(selector, - ++it, - nextElement, - context); } -} +} // namespace endor diff --git a/src/client/cssom/selectors/matching.hpp b/src/client/cssom/selectors/matching.hpp index b74fec480..290809c6f 100644 --- a/src/client/cssom/selectors/matching.hpp +++ b/src/client/cssom/selectors/matching.hpp @@ -5,36 +5,38 @@ #include #include "./css_selector_parser.hpp" -namespace client_cssom::selectors +namespace endor { - class MatchingContext + namespace client_cssom::selectors { - public: - MatchingContext() = default; - }; + class MatchingContext + { + public: + MatchingContext() = default; + }; - /** + /** * Check if the element matches the specified selectors. * * @param selectors The CSS selector list. * @param element The element to check. * @returns Whether the element matches the selectors. */ - bool matchesSelectorList(const SelectorList &selectors, - const std::shared_ptr element); + bool matchesSelectorList(const SelectorList &selectors, + const std::shared_ptr element); - /** + /** * Check if the element matches the specified selector. * * @param selector The CSS selector. * @param element The element to check. * @returns Whether the element matches the selector. */ - bool matchesSelector(const Selector &selector, - const std::shared_ptr element, - MatchingContext &context); + bool matchesSelector(const Selector &selector, + const std::shared_ptr element, + MatchingContext &context); - /** + /** * Check if the element matches the specified selector component. * * @param selector The CSS selector. @@ -42,12 +44,12 @@ namespace client_cssom::selectors * @param element The element to check. * @returns Whether the element matches the selector component. */ - bool matchesSelectorComponent(const Selector &selector, - std::vector::const_iterator &it, - const std::shared_ptr element, - MatchingContext &context); + bool matchesSelectorComponent(const Selector &selector, + std::vector::const_iterator &it, + const std::shared_ptr element, + MatchingContext &context); - /** + /** * Check if the element matches the specified selector component. * NOTE: The component should not be a combinator. * @@ -55,11 +57,11 @@ namespace client_cssom::selectors * @param element The element to check. * @returns Whether the element matches the component. */ - bool matchesSelectorComponentNonCombinator(const Component &component, - const std::shared_ptr element, - MatchingContext &context); + bool matchesSelectorComponentNonCombinator(const Component &component, + const std::shared_ptr element, + MatchingContext &context); - /** + /** * Match selector starting from the end (right-to-left matching). * * @param selector The CSS selector. @@ -67,7 +69,8 @@ namespace client_cssom::selectors * @param context The matching context. * @returns Whether the element matches the selector. */ - bool matchesSelectorFromEnd(const Selector &selector, - const std::shared_ptr element, - MatchingContext &context); -} + bool matchesSelectorFromEnd(const Selector &selector, + const std::shared_ptr element, + MatchingContext &context); + } +} // namespace endor diff --git a/src/client/cssom/style_cache.cpp b/src/client/cssom/style_cache.cpp index 9e89f097a..6ae701d9c 100644 --- a/src/client/cssom/style_cache.cpp +++ b/src/client/cssom/style_cache.cpp @@ -3,66 +3,69 @@ #include "./style_cache.hpp" -namespace client_cssom +namespace endor { - using namespace std; - - std::shared_ptr StyleCache::findStyle(shared_ptr elementOrTextNode) const - { - if (TR_UNLIKELY(elementOrTextNode == nullptr)) - return nullptr; - - auto it = find(elementOrTextNode->uid); - if (it != end()) - return it->second; - return nullptr; - } - - shared_ptr StyleCache::createStyle(shared_ptr elementOrTextNode, - bool useElementStyle, - bool writeCache) + namespace client_cssom { - assert(elementOrTextNode != nullptr); + using namespace std; - shared_ptr newStyle = nullptr; - if (elementOrTextNode->isHTMLElement()) - { - auto element = dynamic_pointer_cast(elementOrTextNode); - assert(element != nullptr && "The element must be an HTMLElement"); - newStyle = make_shared(useElementStyle ? element->style() : element->defaultStyle(), - values::computed::Context::From(element)); - } - else if (elementOrTextNode->isText()) - { - auto textNode = dynamic_pointer_cast(elementOrTextNode); - newStyle = make_shared(textNode->defaultStyle(), - values::computed::Context::From(textNode)); - } - else + std::shared_ptr StyleCache::findStyle(shared_ptr elementOrTextNode) const { - assert(false && "Only HTMLElement or Text node can be used to create a style."); + if (TR_UNLIKELY(elementOrTextNode == nullptr)) + return nullptr; + + auto it = find(elementOrTextNode->uid); + if (it != end()) + return it->second; return nullptr; } - assert(newStyle != nullptr); - if (writeCache) + shared_ptr StyleCache::createStyle(shared_ptr elementOrTextNode, + bool useElementStyle, + bool writeCache) { - insert({elementOrTextNode->uid, newStyle}); - } - return newStyle; - } + assert(elementOrTextNode != nullptr); - bool StyleCache::resetStyle(shared_ptr elementOrTextNode) - { - if (TR_UNLIKELY(elementOrTextNode == nullptr)) - return false; + shared_ptr newStyle = nullptr; + if (elementOrTextNode->isHTMLElement()) + { + auto element = dynamic_pointer_cast(elementOrTextNode); + assert(element != nullptr && "The element must be an HTMLElement"); + newStyle = make_shared(useElementStyle ? element->style() : element->defaultStyle(), + values::computed::Context::From(element)); + } + else if (elementOrTextNode->isText()) + { + auto textNode = dynamic_pointer_cast(elementOrTextNode); + newStyle = make_shared(textNode->defaultStyle(), + values::computed::Context::From(textNode)); + } + else + { + assert(false && "Only HTMLElement or Text node can be used to create a style."); + return nullptr; + } - auto it = find(elementOrTextNode->uid); - if (it != end()) + assert(newStyle != nullptr); + if (writeCache) + { + insert({elementOrTextNode->uid, newStyle}); + } + return newStyle; + } + + bool StyleCache::resetStyle(shared_ptr elementOrTextNode) { - erase(it); - return true; + if (TR_UNLIKELY(elementOrTextNode == nullptr)) + return false; + + auto it = find(elementOrTextNode->uid); + if (it != end()) + { + erase(it); + return true; + } + return false; } - return false; } -} +} // namespace endor diff --git a/src/client/cssom/style_cache.hpp b/src/client/cssom/style_cache.hpp index db57bc12d..c7854bb15 100644 --- a/src/client/cssom/style_cache.hpp +++ b/src/client/cssom/style_cache.hpp @@ -5,23 +5,26 @@ #include #include "./computed_style.hpp" -namespace client_cssom +namespace endor { - class StyleCache : std::unordered_map> + namespace client_cssom { - public: - StyleCache() = default; + class StyleCache : std::unordered_map> + { + public: + StyleCache() = default; - public: - std::shared_ptr findStyle(std::shared_ptr elementOrTextNode) const; - std::shared_ptr createStyle(std::shared_ptr elementOrTextNode, - bool useElementStyle = true, - bool writeCache = true); - bool resetStyle(std::shared_ptr elementOrTextNode); + public: + std::shared_ptr findStyle(std::shared_ptr elementOrTextNode) const; + std::shared_ptr createStyle(std::shared_ptr elementOrTextNode, + bool useElementStyle = true, + bool writeCache = true); + bool resetStyle(std::shared_ptr elementOrTextNode); - inline void invalidateCache() - { - clear(); - } - }; -} + inline void invalidateCache() + { + clear(); + } + }; + } +} // namespace endor diff --git a/src/client/cssom/style_traits.hpp b/src/client/cssom/style_traits.hpp index ebf243277..a40dfe52b 100644 --- a/src/client/cssom/style_traits.hpp +++ b/src/client/cssom/style_traits.hpp @@ -6,105 +6,108 @@ #include #include -namespace client_cssom +namespace endor { - // Forward declaration of the `values::computed::Context` class. - namespace values + namespace client_cssom { - namespace computed + // Forward declaration of the `values::computed::Context` class. + namespace values { - class Context; + namespace computed + { + class Context; + } } - } - - class ToCss - { - public: - virtual ~ToCss() = default; - virtual std::string toCss() const = 0; - }; - - template - class ToComputedValue - { - public: - virtual ~ToComputedValue() = default; - virtual ComputedValue toComputedValue(values::computed::Context &) const = 0; - }; - template - class ToLayoutValue - { - public: - virtual ~ToLayoutValue() = default; - virtual LayoutValue toLayoutValue() const = 0; - }; - - template - class SpecifiedValuesArray : public std::vector - { - using std::vector::vector; + class ToCss + { + public: + virtual ~ToCss() = default; + virtual std::string toCss() const = 0; + }; - public: - // Convert the array to a vector of computed values. - template - std::vector toComputedValues(values::computed::Context &context) const + template + class ToComputedValue { - std::vector computed_result; - for (const auto &value : *this) - computed_result.push_back(value.toComputedValue(context)); - return computed_result; - } - }; + public: + virtual ~ToComputedValue() = default; + virtual ComputedValue toComputedValue(values::computed::Context &) const = 0; + }; - class Parse - { - public: - // Parse a string to a single value of type T which is derived from Parse. - template - requires transmute::common::derived_from - static T ParseSingleValue(const std::string &input) + template + class ToLayoutValue { - T value; - if (!input.empty() && value.parse(input)) - return value; - return T(); - } + public: + virtual ~ToLayoutValue() = default; + virtual LayoutValue toLayoutValue() const = 0; + }; - // Parse a string(such as "1s,2s") to an array of type T which is derived from Parse. template - requires transmute::common::derived_from - static SpecifiedValuesArray ParseValuesArray(const std::string &input) + class SpecifiedValuesArray : public std::vector { - SpecifiedValuesArray values; - size_t start = 0; - size_t end = 0; + using std::vector::vector; - while ((end = input.find(',', start)) != std::string::npos) + public: + // Convert the array to a vector of computed values. + template + std::vector toComputedValues(values::computed::Context &context) const { - std::string token = input.substr(start, end - start); - // Trim whitespace from the token - token.erase(token.begin(), std::find_if(token.begin(), token.end(), [](int ch) - { return !std::isspace(ch); })); - token.erase(std::find_if(token.rbegin(), token.rend(), [](int ch) - { return !std::isspace(ch); }) - .base(), - token.end()); + std::vector computed_result; + for (const auto &value : *this) + computed_result.push_back(value.toComputedValue(context)); + return computed_result; + } + }; - values.push_back(ParseSingleValue(token)); - start = end + 1; + class Parse + { + public: + // Parse a string to a single value of type T which is derived from Parse. + template + requires transmute::common::derived_from + static T ParseSingleValue(const std::string &input) + { + T value; + if (!input.empty() && value.parse(input)) + return value; + return T(); } - // Handle the last token - if (start < input.size()) - values.push_back(ParseSingleValue(input.substr(start))); - return values; - } + // Parse a string(such as "1s,2s") to an array of type T which is derived from Parse. + template + requires transmute::common::derived_from + static SpecifiedValuesArray ParseValuesArray(const std::string &input) + { + SpecifiedValuesArray values; + size_t start = 0; + size_t end = 0; + + while ((end = input.find(',', start)) != std::string::npos) + { + std::string token = input.substr(start, end - start); + // Trim whitespace from the token + token.erase(token.begin(), std::find_if(token.begin(), token.end(), [](int ch) + { return !std::isspace(ch); })); + token.erase(std::find_if(token.rbegin(), token.rend(), [](int ch) + { return !std::isspace(ch); }) + .base(), + token.end()); + + values.push_back(ParseSingleValue(token)); + start = end + 1; + } - virtual ~Parse() = default; + // Handle the last token + if (start < input.size()) + values.push_back(ParseSingleValue(input.substr(start))); + return values; + } + + virtual ~Parse() = default; - protected: - // Implement this method to parse the given input string into the specific value type. - virtual bool parse(const std::string &input) = 0; - }; -} + protected: + // Implement this method to parse the given input string into the specific value type. + virtual bool parse(const std::string &input) = 0; + }; + } +} // namespace endor diff --git a/src/client/cssom/stylesheet.hpp b/src/client/cssom/stylesheet.hpp index b2da8b71a..9e87d11f1 100644 --- a/src/client/cssom/stylesheet.hpp +++ b/src/client/cssom/stylesheet.hpp @@ -3,46 +3,49 @@ #include #include -namespace client_cssom +namespace endor { - class StyleSheet + namespace client_cssom { - public: - StyleSheet() = default; - virtual ~StyleSheet() = default; - - public: - bool disabled; - inline const std::string &href() const - { - return href_; - } - inline std::shared_ptr ownerNode() const - { - return ownerNode_.lock(); - } - inline const dom::Node &ownerNodeChecked() const - { - return *ownerNode(); - } - inline std::shared_ptr parentStyleSheet() const + class StyleSheet { - return parentStyleSheet_.lock(); - } - inline const std::string &title() const - { - return title_; - } - inline const std::string &type() const - { - return type_; - } + public: + StyleSheet() = default; + virtual ~StyleSheet() = default; + + public: + bool disabled; + inline const std::string &href() const + { + return href_; + } + inline std::shared_ptr ownerNode() const + { + return ownerNode_.lock(); + } + inline const dom::Node &ownerNodeChecked() const + { + return *ownerNode(); + } + inline std::shared_ptr parentStyleSheet() const + { + return parentStyleSheet_.lock(); + } + inline const std::string &title() const + { + return title_; + } + inline const std::string &type() const + { + return type_; + } - private: - std::string href_; - std::weak_ptr ownerNode_; - std::weak_ptr parentStyleSheet_; - std::string title_; - std::string type_; - }; -} + private: + std::string href_; + std::weak_ptr ownerNode_; + std::weak_ptr parentStyleSheet_; + std::string title_; + std::string type_; + }; + } +} // namespace endor diff --git a/src/client/cssom/units.hpp b/src/client/cssom/units.hpp index 9ec1c1748..2df5c75f7 100644 --- a/src/client/cssom/units.hpp +++ b/src/client/cssom/units.hpp @@ -4,31 +4,33 @@ #include #include -namespace client_cssom +namespace endor { - // The Pixel Per Inch (PPI) constant. - constexpr const int PPI = 92; + namespace client_cssom + { + // The Pixel Per Inch (PPI) constant. + constexpr const int PPI = 92; - // The device pixel ratio. - constexpr const float DevicePixelRatio = 1.0f; + // The device pixel ratio. + constexpr const float DevicePixelRatio = 1.0f; - // The screen width and height. - constexpr const int ScreenWidth = 1920; - constexpr const int ScreenHeight = 1080; - constexpr const int VolumeDepth = 400; + // The screen width and height. + constexpr const int ScreenWidth = 1920; + constexpr const int ScreenHeight = 1080; + constexpr const int VolumeDepth = 400; - /** + /** * Convert the pixel value to the centimeter value. * * @param pixel The pixel value to convert. * @returns The centimeter value. */ - inline float pixelToCm(float pixel) - { - return pixel / PPI * 2.54f; - } + inline float pixelToCm(float pixel) + { + return pixel / PPI * 2.54f; + } - /** + /** * Convert the pixel value to the meter value, the formula is: `pixelToMeter(pixel) = * (pixel / PPI * 2.54f) / 100.0f`. * @@ -37,26 +39,27 @@ namespace client_cssom * @param pixel The pixel value to convert. * @returns The meter value. */ - inline float pixelToMeter(float pixel) - { - return pixelToCm(pixel) / 100.0f; - } + inline float pixelToMeter(float pixel) + { + return pixelToCm(pixel) / 100.0f; + } - /** + /** * Convert a `Size3` value from pixel to a new `Size3` value in meter. * * @param sizeInPx The size in pixel to convert. * @returns The new size in meter. */ - inline math::Size3 pixelToMeter(math::Size3 sizeInPx) - { - return math::Size3(pixelToMeter(sizeInPx.width()), - pixelToMeter(sizeInPx.height()), - pixelToMeter(sizeInPx.depth())); - } + inline math::Size3 pixelToMeter(math::Size3 sizeInPx) + { + return math::Size3(pixelToMeter(sizeInPx.width()), + pixelToMeter(sizeInPx.height()), + pixelToMeter(sizeInPx.depth())); + } - inline float meterToPixel(float meter) - { - return meter * 100.0f * PPI / 2.54f; + inline float meterToPixel(float meter) + { + return meter * 100.0f * PPI / 2.54f; + } } -} +} // namespace endor diff --git a/src/client/cssom/values/common.hpp b/src/client/cssom/values/common.hpp index 20186ad98..38aa38b2a 100644 --- a/src/client/cssom/values/common.hpp +++ b/src/client/cssom/values/common.hpp @@ -2,72 +2,75 @@ #include -namespace client_cssom::values +namespace endor { - class CSSFloat : public ToCss, - public Parse + namespace client_cssom::values { - friend class Parse; - - public: - CSSFloat() - : value(0.0f) - { - } - CSSFloat(float v) - : value(v) + class CSSFloat : public ToCss, + public Parse { - } + friend class Parse; - public: - CSSFloat operator+(const float other) const - { - return CSSFloat(value + other); - } - CSSFloat operator-(const float other) const - { - return CSSFloat(value - other); - } - CSSFloat operator*(const float other) const - { - return CSSFloat(value * other); - } - CSSFloat operator/(const float other) const - { - if (other == 0.0f) - throw std::runtime_error("Division by zero in CSSFloat."); - return CSSFloat(value / other); - } + public: + CSSFloat() + : value(0.0f) + { + } + CSSFloat(float v) + : value(v) + { + } - public: - inline bool isZero() const - { - return value == 0.0f; - } - std::string toCss() const override - { - return std::to_string(value); - } + public: + CSSFloat operator+(const float other) const + { + return CSSFloat(value + other); + } + CSSFloat operator-(const float other) const + { + return CSSFloat(value - other); + } + CSSFloat operator*(const float other) const + { + return CSSFloat(value * other); + } + CSSFloat operator/(const float other) const + { + if (other == 0.0f) + throw std::runtime_error("Division by zero in CSSFloat."); + return CSSFloat(value / other); + } - private: - bool parse(const std::string &input) override - { - try + public: + inline bool isZero() const { - value = std::stof(input); - return true; + return value == 0.0f; } - catch (const std::invalid_argument &) + std::string toCss() const override { - return false; // Invalid float format. + return std::to_string(value); } - catch (const std::out_of_range &) + + private: + bool parse(const std::string &input) override { - return false; // Float value out of range. + try + { + value = std::stof(input); + return true; + } + catch (const std::invalid_argument &) + { + return false; // Invalid float format. + } + catch (const std::out_of_range &) + { + return false; // Float value out of range. + } } - } - public: - float value; - }; -} + public: + float value; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/align.hpp b/src/client/cssom/values/computed/align.hpp index a32775e4a..4d0a554e5 100644 --- a/src/client/cssom/values/computed/align.hpp +++ b/src/client/cssom/values/computed/align.hpp @@ -4,225 +4,228 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class AlignContent : public specified::AlignContent, - public ToLayoutValue + namespace client_cssom::values::computed { - using specified::AlignContent::AlignContent; - - public: - AlignContent(specified::AlignContent other) - : specified::AlignContent(other) + class AlignContent : public specified::AlignContent, + public ToLayoutValue { - } + using specified::AlignContent::AlignContent; - crates::layout2::styles::AlignContent toLayoutValue() const override - { - switch (tag()) + public: + AlignContent(specified::AlignContent other) + : specified::AlignContent(other) { - case specified::AlignContent::Tag::kStart: - return crates::layout2::styles::AlignContent::Start(); - case specified::AlignContent::Tag::kEnd: - return crates::layout2::styles::AlignContent::End(); - case specified::AlignContent::Tag::kCenter: - return crates::layout2::styles::AlignContent::Center(); - case specified::AlignContent::Tag::kFlexStart: - return crates::layout2::styles::AlignContent::FlexStart(); - case specified::AlignContent::Tag::kFlexEnd: - return crates::layout2::styles::AlignContent::FlexEnd(); - case specified::AlignContent::Tag::kSpaceBetween: - return crates::layout2::styles::AlignContent::SpaceBetween(); - case specified::AlignContent::Tag::kSpaceAround: - return crates::layout2::styles::AlignContent::SpaceAround(); - case specified::AlignContent::Tag::kSpaceEvenly: - return crates::layout2::styles::AlignContent::SpaceEvenly(); - case specified::AlignContent::Tag::kStretch: - return crates::layout2::styles::AlignContent::Stretch(); - case specified::AlignContent::Tag::kNormal: - default: - return crates::layout2::styles::AlignContent::Normal(); } - } - }; - class JustifyContent : public specified::JustifyContent, - public ToLayoutValue - { - using specified::JustifyContent::JustifyContent; + crates::layout2::styles::AlignContent toLayoutValue() const override + { + switch (tag()) + { + case specified::AlignContent::Tag::kStart: + return crates::layout2::styles::AlignContent::Start(); + case specified::AlignContent::Tag::kEnd: + return crates::layout2::styles::AlignContent::End(); + case specified::AlignContent::Tag::kCenter: + return crates::layout2::styles::AlignContent::Center(); + case specified::AlignContent::Tag::kFlexStart: + return crates::layout2::styles::AlignContent::FlexStart(); + case specified::AlignContent::Tag::kFlexEnd: + return crates::layout2::styles::AlignContent::FlexEnd(); + case specified::AlignContent::Tag::kSpaceBetween: + return crates::layout2::styles::AlignContent::SpaceBetween(); + case specified::AlignContent::Tag::kSpaceAround: + return crates::layout2::styles::AlignContent::SpaceAround(); + case specified::AlignContent::Tag::kSpaceEvenly: + return crates::layout2::styles::AlignContent::SpaceEvenly(); + case specified::AlignContent::Tag::kStretch: + return crates::layout2::styles::AlignContent::Stretch(); + case specified::AlignContent::Tag::kNormal: + default: + return crates::layout2::styles::AlignContent::Normal(); + } + } + }; - public: - JustifyContent(specified::JustifyContent other) - : specified::JustifyContent(other) + class JustifyContent : public specified::JustifyContent, + public ToLayoutValue { - } + using specified::JustifyContent::JustifyContent; - crates::layout2::styles::JustifyContent toLayoutValue() const override - { - switch (tag()) + public: + JustifyContent(specified::JustifyContent other) + : specified::JustifyContent(other) { - case specified::JustifyContent::Tag::kStart: - return crates::layout2::styles::JustifyContent::Start(); - case specified::JustifyContent::Tag::kEnd: - return crates::layout2::styles::JustifyContent::End(); - case specified::JustifyContent::Tag::kCenter: - return crates::layout2::styles::JustifyContent::Center(); - case specified::JustifyContent::Tag::kFlexStart: - return crates::layout2::styles::JustifyContent::FlexStart(); - case specified::JustifyContent::Tag::kFlexEnd: - return crates::layout2::styles::JustifyContent::FlexEnd(); - // TODO(yorkie): support left and right in layout2. - // case specified::JustifyContent::Tag::kLeft: - // return crates::layout2::styles::JustifyContent::Left(); - // case specified::JustifyContent::Tag::kRight: - // return crates::layout2::styles::JustifyContent::Right(); - case specified::JustifyContent::kSpaceBetween: - return crates::layout2::styles::JustifyContent::SpaceBetween(); - case specified::JustifyContent::kSpaceAround: - return crates::layout2::styles::JustifyContent::SpaceAround(); - case specified::JustifyContent::kSpaceEvenly: - return crates::layout2::styles::JustifyContent::SpaceEvenly(); - case specified::JustifyContent::Tag::kStretch: - return crates::layout2::styles::JustifyContent::Stretch(); - case specified::JustifyContent::Tag::kNormal: - default: - return crates::layout2::styles::JustifyContent::Normal(); } - } - }; - class AlignItems : public specified::AlignItems, - public ToLayoutValue - { - using specified::AlignItems::AlignItems; + crates::layout2::styles::JustifyContent toLayoutValue() const override + { + switch (tag()) + { + case specified::JustifyContent::Tag::kStart: + return crates::layout2::styles::JustifyContent::Start(); + case specified::JustifyContent::Tag::kEnd: + return crates::layout2::styles::JustifyContent::End(); + case specified::JustifyContent::Tag::kCenter: + return crates::layout2::styles::JustifyContent::Center(); + case specified::JustifyContent::Tag::kFlexStart: + return crates::layout2::styles::JustifyContent::FlexStart(); + case specified::JustifyContent::Tag::kFlexEnd: + return crates::layout2::styles::JustifyContent::FlexEnd(); + // TODO(yorkie): support left and right in layout2. + // case specified::JustifyContent::Tag::kLeft: + // return crates::layout2::styles::JustifyContent::Left(); + // case specified::JustifyContent::Tag::kRight: + // return crates::layout2::styles::JustifyContent::Right(); + case specified::JustifyContent::kSpaceBetween: + return crates::layout2::styles::JustifyContent::SpaceBetween(); + case specified::JustifyContent::kSpaceAround: + return crates::layout2::styles::JustifyContent::SpaceAround(); + case specified::JustifyContent::kSpaceEvenly: + return crates::layout2::styles::JustifyContent::SpaceEvenly(); + case specified::JustifyContent::Tag::kStretch: + return crates::layout2::styles::JustifyContent::Stretch(); + case specified::JustifyContent::Tag::kNormal: + default: + return crates::layout2::styles::JustifyContent::Normal(); + } + } + }; - public: - AlignItems(specified::AlignItems other) - : specified::AlignItems(other) + class AlignItems : public specified::AlignItems, + public ToLayoutValue { - } + using specified::AlignItems::AlignItems; - crates::layout2::styles::AlignItems toLayoutValue() const override - { - switch (tag()) + public: + AlignItems(specified::AlignItems other) + : specified::AlignItems(other) { - case specified::AlignItems::Tag::kStart: - return crates::layout2::styles::AlignItems::Start(); - case specified::AlignItems::Tag::kEnd: - return crates::layout2::styles::AlignItems::End(); - case specified::AlignItems::Tag::kCenter: - return crates::layout2::styles::AlignItems::Center(); - case specified::AlignItems::Tag::kFlexStart: - return crates::layout2::styles::AlignItems::FlexStart(); - case specified::AlignItems::Tag::kFlexEnd: - return crates::layout2::styles::AlignItems::FlexEnd(); - case specified::AlignItems::Tag::kStretch: - return crates::layout2::styles::AlignItems::Stretch(); - case specified::AlignItems::Tag::kNormal: - default: - return crates::layout2::styles::AlignItems::Normal(); } - } - }; - class AlignSelf : public specified::AlignSelf, - public ToLayoutValue - { - using specified::AlignSelf::AlignSelf; + crates::layout2::styles::AlignItems toLayoutValue() const override + { + switch (tag()) + { + case specified::AlignItems::Tag::kStart: + return crates::layout2::styles::AlignItems::Start(); + case specified::AlignItems::Tag::kEnd: + return crates::layout2::styles::AlignItems::End(); + case specified::AlignItems::Tag::kCenter: + return crates::layout2::styles::AlignItems::Center(); + case specified::AlignItems::Tag::kFlexStart: + return crates::layout2::styles::AlignItems::FlexStart(); + case specified::AlignItems::Tag::kFlexEnd: + return crates::layout2::styles::AlignItems::FlexEnd(); + case specified::AlignItems::Tag::kStretch: + return crates::layout2::styles::AlignItems::Stretch(); + case specified::AlignItems::Tag::kNormal: + default: + return crates::layout2::styles::AlignItems::Normal(); + } + } + }; - public: - AlignSelf(specified::AlignSelf other) - : specified::AlignSelf(other) + class AlignSelf : public specified::AlignSelf, + public ToLayoutValue { - } + using specified::AlignSelf::AlignSelf; - crates::layout2::styles::AlignSelf toLayoutValue() const override - { - switch (tag()) + public: + AlignSelf(specified::AlignSelf other) + : specified::AlignSelf(other) { - case specified::AlignSelf::Tag::kStart: - return crates::layout2::styles::AlignSelf::Start(); - case specified::AlignSelf::Tag::kEnd: - return crates::layout2::styles::AlignSelf::End(); - case specified::AlignSelf::Tag::kCenter: - return crates::layout2::styles::AlignSelf::Center(); - case specified::AlignSelf::Tag::kFlexStart: - return crates::layout2::styles::AlignSelf::FlexStart(); - case specified::AlignSelf::Tag::kFlexEnd: - return crates::layout2::styles::AlignSelf::FlexEnd(); - case specified::AlignSelf::Tag::kStretch: - return crates::layout2::styles::AlignSelf::Stretch(); - case specified::AlignSelf::Tag::kAuto: - default: - return crates::layout2::styles::AlignSelf::Auto(); } - } - }; - class JustifySelf : public specified::JustifySelf, - public ToLayoutValue - { - using specified::JustifySelf::JustifySelf; + crates::layout2::styles::AlignSelf toLayoutValue() const override + { + switch (tag()) + { + case specified::AlignSelf::Tag::kStart: + return crates::layout2::styles::AlignSelf::Start(); + case specified::AlignSelf::Tag::kEnd: + return crates::layout2::styles::AlignSelf::End(); + case specified::AlignSelf::Tag::kCenter: + return crates::layout2::styles::AlignSelf::Center(); + case specified::AlignSelf::Tag::kFlexStart: + return crates::layout2::styles::AlignSelf::FlexStart(); + case specified::AlignSelf::Tag::kFlexEnd: + return crates::layout2::styles::AlignSelf::FlexEnd(); + case specified::AlignSelf::Tag::kStretch: + return crates::layout2::styles::AlignSelf::Stretch(); + case specified::AlignSelf::Tag::kAuto: + default: + return crates::layout2::styles::AlignSelf::Auto(); + } + } + }; - public: - JustifySelf(specified::JustifySelf other) - : specified::JustifySelf(other) + class JustifySelf : public specified::JustifySelf, + public ToLayoutValue { - } + using specified::JustifySelf::JustifySelf; - crates::layout2::styles::JustifySelf toLayoutValue() const override - { - switch (tag()) + public: + JustifySelf(specified::JustifySelf other) + : specified::JustifySelf(other) { - case specified::JustifySelf::Tag::kStart: - return crates::layout2::styles::JustifySelf::Start(); - case specified::JustifySelf::Tag::kEnd: - return crates::layout2::styles::JustifySelf::End(); - case specified::JustifySelf::Tag::kCenter: - return crates::layout2::styles::JustifySelf::Center(); - case specified::JustifySelf::Tag::kFlexStart: - return crates::layout2::styles::JustifySelf::FlexStart(); - case specified::JustifySelf::Tag::kFlexEnd: - return crates::layout2::styles::JustifySelf::FlexEnd(); - case specified::JustifySelf::Tag::kStretch: - return crates::layout2::styles::JustifySelf::Stretch(); - case specified::JustifySelf::Tag::kAuto: - default: - return crates::layout2::styles::JustifySelf::Auto(); } - } - }; - class JustifyItems : public specified::JustifyItems, - public ToLayoutValue - { - using specified::JustifyItems::JustifyItems; + crates::layout2::styles::JustifySelf toLayoutValue() const override + { + switch (tag()) + { + case specified::JustifySelf::Tag::kStart: + return crates::layout2::styles::JustifySelf::Start(); + case specified::JustifySelf::Tag::kEnd: + return crates::layout2::styles::JustifySelf::End(); + case specified::JustifySelf::Tag::kCenter: + return crates::layout2::styles::JustifySelf::Center(); + case specified::JustifySelf::Tag::kFlexStart: + return crates::layout2::styles::JustifySelf::FlexStart(); + case specified::JustifySelf::Tag::kFlexEnd: + return crates::layout2::styles::JustifySelf::FlexEnd(); + case specified::JustifySelf::Tag::kStretch: + return crates::layout2::styles::JustifySelf::Stretch(); + case specified::JustifySelf::Tag::kAuto: + default: + return crates::layout2::styles::JustifySelf::Auto(); + } + } + }; - public: - JustifyItems(specified::JustifyItems other) - : specified::JustifyItems(other) + class JustifyItems : public specified::JustifyItems, + public ToLayoutValue { - } + using specified::JustifyItems::JustifyItems; - crates::layout2::styles::JustifyItems toLayoutValue() const override - { - switch (tag()) + public: + JustifyItems(specified::JustifyItems other) + : specified::JustifyItems(other) + { + } + + crates::layout2::styles::JustifyItems toLayoutValue() const override { - case specified::JustifyItems::Tag::kStart: - return crates::layout2::styles::JustifyItems::Start(); - case specified::JustifyItems::Tag::kEnd: - return crates::layout2::styles::JustifyItems::End(); - case specified::JustifyItems::Tag::kCenter: - return crates::layout2::styles::JustifyItems::Center(); - case specified::JustifyItems::Tag::kFlexStart: - return crates::layout2::styles::JustifyItems::FlexStart(); - case specified::JustifyItems::Tag::kFlexEnd: - return crates::layout2::styles::JustifyItems::FlexEnd(); - case specified::JustifyItems::Tag::kStretch: - return crates::layout2::styles::JustifyItems::Stretch(); - default: - return crates::layout2::styles::JustifyItems::Normal(); + switch (tag()) + { + case specified::JustifyItems::Tag::kStart: + return crates::layout2::styles::JustifyItems::Start(); + case specified::JustifyItems::Tag::kEnd: + return crates::layout2::styles::JustifyItems::End(); + case specified::JustifyItems::Tag::kCenter: + return crates::layout2::styles::JustifyItems::Center(); + case specified::JustifyItems::Tag::kFlexStart: + return crates::layout2::styles::JustifyItems::FlexStart(); + case specified::JustifyItems::Tag::kFlexEnd: + return crates::layout2::styles::JustifyItems::FlexEnd(); + case specified::JustifyItems::Tag::kStretch: + return crates::layout2::styles::JustifyItems::Stretch(); + default: + return crates::layout2::styles::JustifyItems::Normal(); + } } - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/angle.hpp b/src/client/cssom/values/computed/angle.hpp index 32e4f13d7..7ffe96251 100644 --- a/src/client/cssom/values/computed/angle.hpp +++ b/src/client/cssom/values/computed/angle.hpp @@ -3,44 +3,47 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - // Angle class for computed values. - // The value is stored in degrees. - class Angle : public ToCss + namespace client_cssom::values::computed { - static constexpr float RAD_PER_DEG = M_PI / 180.0f; - - public: - static Angle FromRadians(float radians) - { - return Angle(radians / RAD_PER_DEG); - } - static Angle FromDegrees(float degrees) + // Angle class for computed values. + // The value is stored in degrees. + class Angle : public ToCss { - return Angle(degrees); - } + static constexpr float RAD_PER_DEG = M_PI / 180.0f; - public: - Angle(float degrees = 0.0f) - : value_(degrees) - { - } + public: + static Angle FromRadians(float radians) + { + return Angle(radians / RAD_PER_DEG); + } + static Angle FromDegrees(float degrees) + { + return Angle(degrees); + } - std::string toCss() const override - { - return std::to_string(value_) + "deg"; - } - float degrees() const - { - return value_; - } - float radians() const - { - return value_ * RAD_PER_DEG; - } + public: + Angle(float degrees = 0.0f) + : value_(degrees) + { + } + + std::string toCss() const override + { + return std::to_string(value_) + "deg"; + } + float degrees() const + { + return value_; + } + float radians() const + { + return value_ * RAD_PER_DEG; + } - private: - float value_; - }; -} + private: + float value_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/animation.hpp b/src/client/cssom/values/computed/animation.hpp index 772d4da66..93d257918 100644 --- a/src/client/cssom/values/computed/animation.hpp +++ b/src/client/cssom/values/computed/animation.hpp @@ -2,7 +2,10 @@ #include -namespace client_cssom::values::computed +namespace endor { - using TransitionProperty = generics::GenericTransitionProperty; -} + namespace client_cssom::values::computed + { + using TransitionProperty = generics::GenericTransitionProperty; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/background.hpp b/src/client/cssom/values/computed/background.hpp index 146bc9fde..651e22c56 100644 --- a/src/client/cssom/values/computed/background.hpp +++ b/src/client/cssom/values/computed/background.hpp @@ -3,87 +3,90 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class BackgroundBlendMode; - class BackgroundClip; - class BackgroundOrigin; - class BackgroundRepeat; - class BackgroundSize; - class BackgroundPosition; -} - -namespace client_cssom::values::computed -{ - class BackgroundBlendMode : public generics::GenericBackgroundBlendMode + namespace client_cssom::values::specified { - using generics::GenericBackgroundBlendMode::GenericBackgroundBlendMode; + class BackgroundBlendMode; + class BackgroundClip; + class BackgroundOrigin; + class BackgroundRepeat; + class BackgroundSize; + class BackgroundPosition; + } - public: - operator SkBlendMode() const + namespace client_cssom::values::computed + { + class BackgroundBlendMode : public generics::GenericBackgroundBlendMode { - switch (tag_) + using generics::GenericBackgroundBlendMode::GenericBackgroundBlendMode; + + public: + operator SkBlendMode() const { - case BackgroundBlendMode::kNormal: - return SkBlendMode::kSrcOver; - case BackgroundBlendMode::kMultiply: - return SkBlendMode::kMultiply; - case BackgroundBlendMode::kScreen: - return SkBlendMode::kScreen; - case BackgroundBlendMode::kOverlay: - return SkBlendMode::kOverlay; - case BackgroundBlendMode::kDarken: - return SkBlendMode::kDarken; - case BackgroundBlendMode::kLighten: - return SkBlendMode::kLighten; - case BackgroundBlendMode::kColorDodge: - return SkBlendMode::kColorDodge; - case BackgroundBlendMode::kColorBurn: - return SkBlendMode::kColorBurn; - case BackgroundBlendMode::kHardLight: - return SkBlendMode::kHardLight; - case BackgroundBlendMode::kSoftLight: - return SkBlendMode::kSoftLight; - case BackgroundBlendMode::kDifference: - return SkBlendMode::kDifference; - case BackgroundBlendMode::kExclusion: - return SkBlendMode::kExclusion; - case BackgroundBlendMode::kHue: - return SkBlendMode::kHue; - case BackgroundBlendMode::kSaturation: - return SkBlendMode::kSaturation; - case BackgroundBlendMode::kColor: - return SkBlendMode::kColor; - case BackgroundBlendMode::kLuminosity: - return SkBlendMode::kLuminosity; - default: - return SkBlendMode::kSrcOver; // Default to SrcOver if none match + switch (tag_) + { + case BackgroundBlendMode::kNormal: + return SkBlendMode::kSrcOver; + case BackgroundBlendMode::kMultiply: + return SkBlendMode::kMultiply; + case BackgroundBlendMode::kScreen: + return SkBlendMode::kScreen; + case BackgroundBlendMode::kOverlay: + return SkBlendMode::kOverlay; + case BackgroundBlendMode::kDarken: + return SkBlendMode::kDarken; + case BackgroundBlendMode::kLighten: + return SkBlendMode::kLighten; + case BackgroundBlendMode::kColorDodge: + return SkBlendMode::kColorDodge; + case BackgroundBlendMode::kColorBurn: + return SkBlendMode::kColorBurn; + case BackgroundBlendMode::kHardLight: + return SkBlendMode::kHardLight; + case BackgroundBlendMode::kSoftLight: + return SkBlendMode::kSoftLight; + case BackgroundBlendMode::kDifference: + return SkBlendMode::kDifference; + case BackgroundBlendMode::kExclusion: + return SkBlendMode::kExclusion; + case BackgroundBlendMode::kHue: + return SkBlendMode::kHue; + case BackgroundBlendMode::kSaturation: + return SkBlendMode::kSaturation; + case BackgroundBlendMode::kColor: + return SkBlendMode::kColor; + case BackgroundBlendMode::kLuminosity: + return SkBlendMode::kLuminosity; + default: + return SkBlendMode::kSrcOver; // Default to SrcOver if none match + } } - } - }; + }; - class BackgroundClip : public generics::GenericBackgroundClip - { - using generics::GenericBackgroundClip::GenericBackgroundClip; - }; + class BackgroundClip : public generics::GenericBackgroundClip + { + using generics::GenericBackgroundClip::GenericBackgroundClip; + }; - class BackgroundOrigin : public generics::GenericBackgroundOrigin - { - using generics::GenericBackgroundOrigin::GenericBackgroundOrigin; - }; + class BackgroundOrigin : public generics::GenericBackgroundOrigin + { + using generics::GenericBackgroundOrigin::GenericBackgroundOrigin; + }; - class BackgroundRepeat : public generics::GenericBackgroundRepeat - { - using generics::GenericBackgroundRepeat::GenericBackgroundRepeat; - }; + class BackgroundRepeat : public generics::GenericBackgroundRepeat + { + using generics::GenericBackgroundRepeat::GenericBackgroundRepeat; + }; - class BackgroundSize : public generics::GenericBackgroundSize - { - using generics::GenericBackgroundSize::GenericBackgroundSize; - }; + class BackgroundSize : public generics::GenericBackgroundSize + { + using generics::GenericBackgroundSize::GenericBackgroundSize; + }; - class BackgroundPosition : public generics::GenericBackgroundPosition - { - using generics::GenericBackgroundPosition::GenericBackgroundPosition; - }; -} + class BackgroundPosition : public generics::GenericBackgroundPosition + { + using generics::GenericBackgroundPosition::GenericBackgroundPosition; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/border.hpp b/src/client/cssom/values/computed/border.hpp index d39d23742..1ac82c5e5 100644 --- a/src/client/cssom/values/computed/border.hpp +++ b/src/client/cssom/values/computed/border.hpp @@ -8,104 +8,107 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - // A computed value for -webkit-text-stroke-width. - using LineWidth = CSSFloat; - - // A computed value for border-width (and the like). - class BorderSideWidth : public CSSFloat, - public ToLayoutValue + namespace client_cssom::values::computed { - using CSSFloat::CSSFloat; + // A computed value for -webkit-text-stroke-width. + using LineWidth = CSSFloat; - public: - crates::layout2::styles::LengthPercentage toLayoutValue() const + // A computed value for border-width (and the like). + class BorderSideWidth : public CSSFloat, + public ToLayoutValue { - return crates::layout2::styles::LengthPercentage::Length(value); - } - }; - - // A computed value for the `border-style` property. - class BorderSideStyle : public generics::GenericBorderStyle - { - using generics::GenericBorderStyle::GenericBorderStyle; - }; + using CSSFloat::CSSFloat; - // A computed value for the `border-width` property. - class BorderWidth : public generics::Rect - { - using generics::Rect::Rect; + public: + crates::layout2::styles::LengthPercentage toLayoutValue() const + { + return crates::layout2::styles::LengthPercentage::Length(value); + } + }; - public: - BorderWidth(generics::Rect rect) - : generics::Rect(rect) + // A computed value for the `border-style` property. + class BorderSideStyle : public generics::GenericBorderStyle { - } - }; + using generics::GenericBorderStyle::GenericBorderStyle; + }; - // A computed value for the `border-color` property. - class BorderColor : public generics::Rect - { - using generics::Rect::Rect; - - public: - BorderColor(generics::Rect rect) - : generics::Rect(rect) + // A computed value for the `border-width` property. + class BorderWidth : public generics::Rect { - } - }; + using generics::Rect::Rect; - // A computed value for the `border-style` property. - class BorderStyle : public generics::Rect - { - using generics::Rect::Rect; + public: + BorderWidth(generics::Rect rect) + : generics::Rect(rect) + { + } + }; - public: - BorderStyle(generics::Rect rect) - : generics::Rect(rect) + // A computed value for the `border-color` property. + class BorderColor : public generics::Rect { - } - }; + using generics::Rect::Rect; - // A computed value for the `border-image-width` property. - using BorderImageWidth = BorderWidth; + public: + BorderColor(generics::Rect rect) + : generics::Rect(rect) + { + } + }; - // A computed value for the `border-*-radius` longhand properties. - class BorderCornerRadius : public generics::GenericBorderCornerRadius - { - using generics::GenericBorderCornerRadius::GenericBorderCornerRadius; - - public: - static BorderCornerRadius Zero() + // A computed value for the `border-style` property. + class BorderStyle : public generics::Rect { - return BorderCornerRadius(0.0f); - } + using generics::Rect::Rect; - public: - BorderCornerRadius(float px) - : GenericBorderCornerRadius(NonNegativeLengthPercentage::Length(px)) - { - } - bool isZero() const - { - return value_ == 0.0f; - } - }; + public: + BorderStyle(generics::Rect rect) + : generics::Rect(rect) + { + } + }; - // A computed value for the `border-radius` property. - class BorderRadius : public generics::GenericBorderRadius - { - using generics::GenericBorderRadius::GenericBorderRadius; + // A computed value for the `border-image-width` property. + using BorderImageWidth = BorderWidth; - public: - static BorderRadius Zero() + // A computed value for the `border-*-radius` longhand properties. + class BorderCornerRadius : public generics::GenericBorderCornerRadius + { + using generics::GenericBorderCornerRadius::GenericBorderCornerRadius; + + public: + static BorderCornerRadius Zero() + { + return BorderCornerRadius(0.0f); + } + + public: + BorderCornerRadius(float px) + : GenericBorderCornerRadius(NonNegativeLengthPercentage::Length(px)) + { + } + bool isZero() const + { + return value_ == 0.0f; + } + }; + + // A computed value for the `border-radius` property. + class BorderRadius : public generics::GenericBorderRadius { - return BorderRadius(BorderCornerRadius::Zero(), - BorderCornerRadius::Zero(), - BorderCornerRadius::Zero(), - BorderCornerRadius::Zero()); - } - }; - -} + using generics::GenericBorderRadius::GenericBorderRadius; + + public: + static BorderRadius Zero() + { + return BorderRadius(BorderCornerRadius::Zero(), + BorderCornerRadius::Zero(), + BorderCornerRadius::Zero(), + BorderCornerRadius::Zero()); + } + }; + + } +} // namespace endor diff --git a/src/client/cssom/values/computed/box.hpp b/src/client/cssom/values/computed/box.hpp index 1c49fdd49..01476d1e4 100644 --- a/src/client/cssom/values/computed/box.hpp +++ b/src/client/cssom/values/computed/box.hpp @@ -5,27 +5,30 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - using Display = specified::Display; - using BoxSizing = specified::BoxSizing; - using Overflow = specified::Overflow; - - class Padding : public generics::Rect + namespace client_cssom::values::computed { - public: - Padding(generics::Rect rect) - : generics::Rect(rect) + using Display = specified::Display; + using BoxSizing = specified::BoxSizing; + using Overflow = specified::Overflow; + + class Padding : public generics::Rect { - } - }; + public: + Padding(generics::Rect rect) + : generics::Rect(rect) + { + } + }; - class Margin : public generics::Rect - { - public: - Margin(generics::Rect rect) - : generics::Rect(rect) + class Margin : public generics::Rect { - } - }; -} + public: + Margin(generics::Rect rect) + : generics::Rect(rect) + { + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/classes.hpp b/src/client/cssom/values/computed/classes.hpp index 5964ad6c6..3f0f0fb12 100644 --- a/src/client/cssom/values/computed/classes.hpp +++ b/src/client/cssom/values/computed/classes.hpp @@ -21,7 +21,11 @@ #include "./transform.hpp" // Forward declarations that not included to avoid circular dependencies -namespace client_cssom::values::computed + +namespace endor { - class Context; -} + namespace client_cssom::values::computed + { + class Context; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/color.hpp b/src/client/cssom/values/computed/color.hpp index 0b93085c4..bee8be5e9 100644 --- a/src/client/cssom/values/computed/color.hpp +++ b/src/client/cssom/values/computed/color.hpp @@ -4,37 +4,40 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - // A computed value for ``. - class Color : public generics::GenericColor + namespace client_cssom::values::computed { - using generics::GenericColor::GenericColor; - - public: - static Color Transparent() - { - return Absolute(glm::vec4(0, 0, 0, 0)); - } - static Color Black() - { - return Absolute(glm::vec4(0, 0, 0, 255)); - } - static Color White() + // A computed value for ``. + class Color : public generics::GenericColor { - return Absolute(glm::vec4(255, 255, 255, 255)); - } + using generics::GenericColor::GenericColor; - public: - // Resolve the color to an absolute color. - SkColor resolveToAbsoluteColor(SkColor current_color = SK_ColorTRANSPARENT) const - { - if (isAbsolute()) - return getAbsoluteColor(); - else if (isCurrentColor()) - return current_color; - else - return SK_ColorTRANSPARENT; // Default to transparent for unknown - } - }; -} + public: + static Color Transparent() + { + return Absolute(glm::vec4(0, 0, 0, 0)); + } + static Color Black() + { + return Absolute(glm::vec4(0, 0, 0, 255)); + } + static Color White() + { + return Absolute(glm::vec4(255, 255, 255, 255)); + } + + public: + // Resolve the color to an absolute color. + SkColor resolveToAbsoluteColor(SkColor current_color = SK_ColorTRANSPARENT) const + { + if (isAbsolute()) + return getAbsoluteColor(); + else if (isCurrentColor()) + return current_color; + else + return SK_ColorTRANSPARENT; // Default to transparent for unknown + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/common.hpp b/src/client/cssom/values/computed/common.hpp index 7cdc1bc8c..3711132cd 100644 --- a/src/client/cssom/values/computed/common.hpp +++ b/src/client/cssom/values/computed/common.hpp @@ -3,66 +3,69 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - using NonNegativeNumber = generics::NonNegative; - - // A computed value for `min-width`, `min-height`, `width` or `height` property. - class CSSPixelLength + namespace client_cssom::values::computed { - public: - CSSPixelLength(float px) - : value(px) - { - } + using NonNegativeNumber = generics::NonNegative; - bool operator==(const CSSPixelLength &other) const - { - return value == other.value; - } - bool operator==(float other) const - { - return value == other; - } - bool operator<(const CSSPixelLength &other) const - { - return value < other.value; - } - bool operator<(float other) const - { - return value < other; - } - bool operator>(const CSSPixelLength &other) const + // A computed value for `min-width`, `min-height`, `width` or `height` property. + class CSSPixelLength { - return value > other.value; - } - bool operator>(float other) const - { - return value > other; - } + public: + CSSPixelLength(float px) + : value(px) + { + } - public: - inline float px() const - { - return value; - } + bool operator==(const CSSPixelLength &other) const + { + return value == other.value; + } + bool operator==(float other) const + { + return value == other; + } + bool operator<(const CSSPixelLength &other) const + { + return value < other.value; + } + bool operator<(float other) const + { + return value < other; + } + bool operator>(const CSSPixelLength &other) const + { + return value > other.value; + } + bool operator>(float other) const + { + return value > other; + } - // Returns the absolute value of the CSSPixelLength. - inline CSSPixelLength abs() const - { - return CSSPixelLength(std::abs(value)); - } + public: + inline float px() const + { + return value; + } - inline CSSPixelLength min(const CSSPixelLength &other) const - { - return CSSPixelLength(std::min(value, other.value)); - } - inline CSSPixelLength max(const CSSPixelLength &other) const - { - return CSSPixelLength(std::max(value, other.value)); - } + // Returns the absolute value of the CSSPixelLength. + inline CSSPixelLength abs() const + { + return CSSPixelLength(std::abs(value)); + } + + inline CSSPixelLength min(const CSSPixelLength &other) const + { + return CSSPixelLength(std::min(value, other.value)); + } + inline CSSPixelLength max(const CSSPixelLength &other) const + { + return CSSPixelLength(std::max(value, other.value)); + } - public: - float value; - }; -} + public: + float value; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/context.hpp b/src/client/cssom/values/computed/context.hpp index 1607493ba..2b70f6fd2 100644 --- a/src/client/cssom/values/computed/context.hpp +++ b/src/client/cssom/values/computed/context.hpp @@ -10,204 +10,207 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - // Context for computed value conversion. - class Context + namespace client_cssom::values::computed { - public: - static Context From(std::shared_ptr element_or_text_node) + // Context for computed value conversion. + class Context { - return Context(element_or_text_node); - } - - public: - Context(std::shared_ptr element_or_text_node) - : element_or_text_node_(element_or_text_node) - , device_(GetDevice(element_or_text_node)) - , reset_style_(GetDefaultStyleRef(element_or_text_node)) - , in_media_query_(false) - , in_container_query_(false) - { - } + public: + static Context From(std::shared_ptr element_or_text_node) + { + return Context(element_or_text_node); + } - public: - inline float rootFontSize() const - { - return device_.rootFontSize(); - } - float baseFontSize() const - { - std::shared_ptr node = element_or_text_node_.lock(); - if (TR_UNLIKELY(node == nullptr) || !node->isElementOrText()) - return rootFontSize(); + public: + Context(std::shared_ptr element_or_text_node) + : element_or_text_node_(element_or_text_node) + , device_(GetDevice(element_or_text_node)) + , reset_style_(GetDefaultStyleRef(element_or_text_node)) + , in_media_query_(false) + , in_container_query_(false) + { + } - if (node->isElement()) + public: + inline float rootFontSize() const { - auto element = dynamic_pointer_cast(node); - assert(element != nullptr && "The node should be an Element."); - if (element->hasAdoptedStyle()) - return element->adoptedStyleRef().fontSize().computedSize().px(); - else - return rootFontSize(); + return device_.rootFontSize(); } - else if (node->isText()) + float baseFontSize() const { - // For text nodes, we use the font size of the parent element. - auto text = dynamic_pointer_cast(node); - assert(text != nullptr && "The parent element should not be null."); - if (text->hasAdoptedStyle()) - return text->adoptedStyleRef().fontSize().computedSize().px(); - else + std::shared_ptr node = element_or_text_node_.lock(); + if (TR_UNLIKELY(node == nullptr) || !node->isElementOrText()) return rootFontSize(); - } - // Unreachable case - assert(false && "Unreachable"); - } - inline int baseFontWeight() const - { - return 400; - } - inline float baseLineHeight() const - { - std::shared_ptr node = element_or_text_node_.lock(); - if (TR_UNLIKELY(node == nullptr) || !node->isElementOrText()) - return rootLineHeight(); - - if (node->isElement()) + if (node->isElement()) + { + auto element = dynamic_pointer_cast(node); + assert(element != nullptr && "The node should be an Element."); + if (element->hasAdoptedStyle()) + return element->adoptedStyleRef().fontSize().computedSize().px(); + else + return rootFontSize(); + } + else if (node->isText()) + { + // For text nodes, we use the font size of the parent element. + auto text = dynamic_pointer_cast(node); + assert(text != nullptr && "The parent element should not be null."); + if (text->hasAdoptedStyle()) + return text->adoptedStyleRef().fontSize().computedSize().px(); + else + return rootFontSize(); + } + + // Unreachable case + assert(false && "Unreachable"); + } + inline int baseFontWeight() const { - auto element = dynamic_pointer_cast(node); - assert(element != nullptr && "The node should be an Element."); - if (element->hasAdoptedStyle()) - return element->adoptedStyleRef().lineHeight().computedSize(baseFontSize()); - else - return rootLineHeight(); + return 400; } - else if (node->isText()) + inline float baseLineHeight() const { - // For text nodes, we use the font size of the parent element. - auto text = dynamic_pointer_cast(node); - assert(text != nullptr && "The parent element should not be null."); - if (text->hasAdoptedStyle()) - return text->adoptedStyleRef().lineHeight().computedSize(baseFontSize()); - else + std::shared_ptr node = element_or_text_node_.lock(); + if (TR_UNLIKELY(node == nullptr) || !node->isElementOrText()) return rootLineHeight(); - } - // Unreachable case - assert(false && "Unreachable"); - } - inline float rootLineHeight() const - { - return device_.rootLineHeight(); - } - inline glm::uvec4 baseViewport() const - { - auto device_viewport = device_.viewportSize(); - return glm::uvec4(device_viewport.x, device_viewport.y, 0, 0); - } - - // Returns this reference as `HTMLElement` object, otherwise throws an exception. - const dom::HTMLElement &elementRef() const - { - std::shared_ptr node = element_or_text_node_.lock(); - if (TR_UNLIKELY(node == nullptr)) - throw std::runtime_error("The internal `element_or_text_node` is null."); - - if (TR_LIKELY(node->isHTMLElement())) + if (node->isElement()) + { + auto element = dynamic_pointer_cast(node); + assert(element != nullptr && "The node should be an Element."); + if (element->hasAdoptedStyle()) + return element->adoptedStyleRef().lineHeight().computedSize(baseFontSize()); + else + return rootLineHeight(); + } + else if (node->isText()) + { + // For text nodes, we use the font size of the parent element. + auto text = dynamic_pointer_cast(node); + assert(text != nullptr && "The parent element should not be null."); + if (text->hasAdoptedStyle()) + return text->adoptedStyleRef().lineHeight().computedSize(baseFontSize()); + else + return rootLineHeight(); + } + + // Unreachable case + assert(false && "Unreachable"); + } + inline float rootLineHeight() const { - auto html_element = dynamic_pointer_cast(node); - return *html_element; + return device_.rootLineHeight(); } - else + inline glm::uvec4 baseViewport() const { - throw std::runtime_error("The node is not an HTMLElement."); + auto device_viewport = device_.viewportSize(); + return glm::uvec4(device_viewport.x, device_viewport.y, 0, 0); } - } - // Returns this reference as `Text` object, otherwise throws an exception. - const dom::Text &textRef() const - { - std::shared_ptr node = element_or_text_node_.lock(); - if (TR_UNLIKELY(node == nullptr)) - throw std::runtime_error("The internal `element_or_text_node` is null."); - if (TR_LIKELY(node->isText())) + // Returns this reference as `HTMLElement` object, otherwise throws an exception. + const dom::HTMLElement &elementRef() const { - auto text_node = dynamic_pointer_cast(node); - return *text_node; + std::shared_ptr node = element_or_text_node_.lock(); + if (TR_UNLIKELY(node == nullptr)) + throw std::runtime_error("The internal `element_or_text_node` is null."); + + if (TR_LIKELY(node->isHTMLElement())) + { + auto html_element = dynamic_pointer_cast(node); + return *html_element; + } + else + { + throw std::runtime_error("The node is not an HTMLElement."); + } } - else + // Returns this reference as `Text` object, otherwise throws an exception. + const dom::Text &textRef() const { - throw std::runtime_error("The node is not a Text node."); + std::shared_ptr node = element_or_text_node_.lock(); + if (TR_UNLIKELY(node == nullptr)) + throw std::runtime_error("The internal `element_or_text_node` is null."); + + if (TR_LIKELY(node->isText())) + { + auto text_node = dynamic_pointer_cast(node); + return *text_node; + } + else + { + throw std::runtime_error("The node is not a Text node."); + } } - } - // Returns the parent element of the current element or text node. - inline std::shared_ptr parentElement() const - { - if (auto node = element_or_text_node_.lock()) - return node->getParentElement(); - return nullptr; - } - - inline const std::optional resetStyle() const - { - return reset_style_; - } - // The parent element's adopted style should be inherited by the child element. - // This method returns the inherited style from the parent element. - inline const std::optional inheritedStyle() const - { - auto parent_element = parentElement(); - if (parent_element == nullptr || !parent_element->hasAdoptedStyle()) - return ComputedStyle(); - else - return parent_element->adoptedStyleRef(); - } - - // Returns the base URI of the element or document for URL resolution - inline std::string getBaseURI() const - { - if (auto node = element_or_text_node_.lock()) - return node->baseURI; - return ""; - } + // Returns the parent element of the current element or text node. + inline std::shared_ptr parentElement() const + { + if (auto node = element_or_text_node_.lock()) + return node->getParentElement(); + return nullptr; + } - private: - static const Device &GetDevice(std::shared_ptr element_or_text_node) - { - assert(element_or_text_node != nullptr && "The element or text node must not be null."); + inline const std::optional resetStyle() const + { + return reset_style_; + } + // The parent element's adopted style should be inherited by the child element. + // This method returns the inherited style from the parent element. + inline const std::optional inheritedStyle() const + { + auto parent_element = parentElement(); + if (parent_element == nullptr || !parent_element->hasAdoptedStyle()) + return ComputedStyle(); + else + return parent_element->adoptedStyleRef(); + } - std::shared_ptr window; - if (element_or_text_node->isDocument()) + // Returns the base URI of the element or document for URL resolution + inline std::string getBaseURI() const { - window = dynamic_pointer_cast(element_or_text_node)->defaultView(); + if (auto node = element_or_text_node_.lock()) + return node->baseURI; + return ""; } - else + + private: + static const Device &GetDevice(std::shared_ptr element_or_text_node) { - const dom::Document &owner_document = element_or_text_node->getOwnerDocumentChecked(); - window = owner_document.defaultView(); + assert(element_or_text_node != nullptr && "The element or text node must not be null."); + + std::shared_ptr window; + if (element_or_text_node->isDocument()) + { + window = dynamic_pointer_cast(element_or_text_node)->defaultView(); + } + else + { + const dom::Document &owner_document = element_or_text_node->getOwnerDocumentChecked(); + window = owner_document.defaultView(); + } + return window->device(); } - return window->device(); - } - static const std::optional GetDefaultStyleRef(std::shared_ptr element_or_text_node) - { - if (element_or_text_node->isHTMLElement()) + static const std::optional GetDefaultStyleRef(std::shared_ptr element_or_text_node) { - auto element = dynamic_pointer_cast(element_or_text_node); - if (element != nullptr) - return ComputedStyle(element->defaultStyle(), std::nullopt); + if (element_or_text_node->isHTMLElement()) + { + auto element = dynamic_pointer_cast(element_or_text_node); + if (element != nullptr) + return ComputedStyle(element->defaultStyle(), std::nullopt); + } + return std::nullopt; // No default style for text nodes. } - return std::nullopt; // No default style for text nodes. - } - private: - Device device_; - std::weak_ptr element_or_text_node_; - std::optional reset_style_; + private: + Device device_; + std::weak_ptr element_or_text_node_; + std::optional reset_style_; - bool in_media_query_; - bool in_container_query_; - }; -} + bool in_media_query_; + bool in_container_query_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/easing.hpp b/src/client/cssom/values/computed/easing.hpp index 63cad1a29..f3acaeabc 100644 --- a/src/client/cssom/values/computed/easing.hpp +++ b/src/client/cssom/values/computed/easing.hpp @@ -2,10 +2,13 @@ #include -namespace client_cssom::values::computed +namespace endor { - class TimingFunction : public generics::GenericTimingFunction + namespace client_cssom::values::computed { - using generics::GenericTimingFunction::GenericTimingFunction; - }; -} + class TimingFunction : public generics::GenericTimingFunction + { + using generics::GenericTimingFunction::GenericTimingFunction; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/filter.hpp b/src/client/cssom/values/computed/filter.hpp index a958c5ec4..d36fd7f89 100644 --- a/src/client/cssom/values/computed/filter.hpp +++ b/src/client/cssom/values/computed/filter.hpp @@ -2,32 +2,35 @@ #include -namespace client_cssom::values +namespace endor { - namespace specified + namespace client_cssom::values { - class FilterFunction; - class Filter; - } - - namespace computed - { - class FilterFunction : public generics::GenericFilterFunction + namespace specified { - friend class specified::FilterFunction; - friend class specified::Filter; - using generics::GenericFilterFunction::GenericFilterFunction; + class FilterFunction; + class Filter; + } - public: - FilterFunction() - : generics::GenericFilterFunction(kNone) + namespace computed + { + class FilterFunction : public generics::GenericFilterFunction { - } - }; + friend class specified::FilterFunction; + friend class specified::Filter; + using generics::GenericFilterFunction::GenericFilterFunction; - class Filter : public generics::GenericFilter - { - using generics::GenericFilter::GenericFilter; - }; + public: + FilterFunction() + : generics::GenericFilterFunction(kNone) + { + } + }; + + class Filter : public generics::GenericFilter + { + using generics::GenericFilter::GenericFilter; + }; + } } -} +} // namespace endor diff --git a/src/client/cssom/values/computed/flex.hpp b/src/client/cssom/values/computed/flex.hpp index 1b651ce47..4625e03c0 100644 --- a/src/client/cssom/values/computed/flex.hpp +++ b/src/client/cssom/values/computed/flex.hpp @@ -4,51 +4,54 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class FlexDirection : public generics::GenericFlexDirection, - public ToLayoutValue + namespace client_cssom::values::computed { - using generics::GenericFlexDirection::GenericFlexDirection; - - public: - crates::layout2::styles::FlexDirection toLayoutValue() const override + class FlexDirection : public generics::GenericFlexDirection, + public ToLayoutValue { - switch (tag_) + using generics::GenericFlexDirection::GenericFlexDirection; + + public: + crates::layout2::styles::FlexDirection toLayoutValue() const override { - case kRow: - return crates::layout2::styles::FlexDirection::Row(); - case kRowReverse: - return crates::layout2::styles::FlexDirection::RowReverse(); - case kColumn: - return crates::layout2::styles::FlexDirection::Column(); - case kColumnReverse: - return crates::layout2::styles::FlexDirection::ColumnReverse(); - default: - return crates::layout2::styles::FlexDirection::Row(); + switch (tag_) + { + case kRow: + return crates::layout2::styles::FlexDirection::Row(); + case kRowReverse: + return crates::layout2::styles::FlexDirection::RowReverse(); + case kColumn: + return crates::layout2::styles::FlexDirection::Column(); + case kColumnReverse: + return crates::layout2::styles::FlexDirection::ColumnReverse(); + default: + return crates::layout2::styles::FlexDirection::Row(); + } } - } - }; + }; - class FlexWrap : public generics::GenericFlexWrap, - public ToLayoutValue - { - using generics::GenericFlexWrap::GenericFlexWrap; - - public: - crates::layout2::styles::FlexWrap toLayoutValue() const override + class FlexWrap : public generics::GenericFlexWrap, + public ToLayoutValue { - switch (tag_) + using generics::GenericFlexWrap::GenericFlexWrap; + + public: + crates::layout2::styles::FlexWrap toLayoutValue() const override { - case kNoWrap: - return crates::layout2::styles::FlexWrap::NoWrap(); - case kWrap: - return crates::layout2::styles::FlexWrap::Wrap(); - case kWrapReverse: - return crates::layout2::styles::FlexWrap::WrapReverse(); - default: - return crates::layout2::styles::FlexWrap::NoWrap(); + switch (tag_) + { + case kNoWrap: + return crates::layout2::styles::FlexWrap::NoWrap(); + case kWrap: + return crates::layout2::styles::FlexWrap::Wrap(); + case kWrapReverse: + return crates::layout2::styles::FlexWrap::WrapReverse(); + default: + return crates::layout2::styles::FlexWrap::NoWrap(); + } } - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/font.hpp b/src/client/cssom/values/computed/font.hpp index 20d218a14..2c3cf2c08 100644 --- a/src/client/cssom/values/computed/font.hpp +++ b/src/client/cssom/values/computed/font.hpp @@ -7,158 +7,161 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class FontSize + namespace client_cssom::values::computed { - public: - static constexpr float FONT_SMALL_PX = 12.0f; - static constexpr float FONT_MEDIUM_PX = 16.0f; - static constexpr float FONT_LARGE_PX = 20.0f; - - static FontSize Large() - { - return FontSize(FONT_SMALL_PX); - } - static FontSize Medium() + class FontSize { - return FontSize(FONT_MEDIUM_PX); - } - static FontSize Small() - { - return FontSize(FONT_LARGE_PX); - } + public: + static constexpr float FONT_SMALL_PX = 12.0f; + static constexpr float FONT_MEDIUM_PX = 16.0f; + static constexpr float FONT_LARGE_PX = 20.0f; - public: - FontSize(float sizeInPx = FONT_MEDIUM_PX) - : computed_size_(sizeInPx) - , used_size_(sizeInPx) - { - } + static FontSize Large() + { + return FontSize(FONT_SMALL_PX); + } + static FontSize Medium() + { + return FontSize(FONT_MEDIUM_PX); + } + static FontSize Small() + { + return FontSize(FONT_LARGE_PX); + } - public: - Length computedSize() const - { - return computed_size_; - } - Length usedSize() const - { - return used_size_; - } + public: + FontSize(float sizeInPx = FONT_MEDIUM_PX) + : computed_size_(sizeInPx) + , used_size_(sizeInPx) + { + } - private: - NonNegativeLength computed_size_; - NonNegativeLength used_size_; - }; + public: + Length computedSize() const + { + return computed_size_; + } + Length usedSize() const + { + return used_size_; + } - class FontWeight - { - private: - static constexpr int NORMAL = 400; - static constexpr int BOLD = 700; - static constexpr int BOLDER = 900; - static constexpr int LIGHTER = 100; - - public: - static FontWeight Normal() - { - return FontWeight(NORMAL); - } - static FontWeight Bold() - { - return FontWeight(BOLD); - } - static FontWeight Bolder() - { - return FontWeight(BOLDER); - } - static FontWeight Lighter() - { - return FontWeight(LIGHTER); - } + private: + NonNegativeLength computed_size_; + NonNegativeLength used_size_; + }; - public: - FontWeight(int value = NORMAL) - : value_(value) + class FontWeight { - } + private: + static constexpr int NORMAL = 400; + static constexpr int BOLD = 700; + static constexpr int BOLDER = 900; + static constexpr int LIGHTER = 100; - int value() const - { - return value_; - } + public: + static FontWeight Normal() + { + return FontWeight(NORMAL); + } + static FontWeight Bold() + { + return FontWeight(BOLD); + } + static FontWeight Bolder() + { + return FontWeight(BOLDER); + } + static FontWeight Lighter() + { + return FontWeight(LIGHTER); + } - private: - int value_; - }; + public: + FontWeight(int value = NORMAL) + : value_(value) + { + } - class FontStyle - { - public: - enum class Slant - { - kNormal, - kItalic, - kOblique, + int value() const + { + return value_; + } + + private: + int value_; }; - static FontStyle Normal() + class FontStyle { - return FontStyle(Slant::kNormal); - } - static FontStyle Italic() - { - return FontStyle(Slant::kItalic); - } - static FontStyle Oblique(std::optional angle = std::nullopt) - { - FontStyle oblique_style(Slant::kOblique); - oblique_style.oblique_angle_ = angle; - return oblique_style; - } - - public: - FontStyle(Slant slant = Slant::kNormal) - : slant_(slant) - { - } + public: + enum class Slant + { + kNormal, + kItalic, + kOblique, + }; - Slant slant() const - { - return slant_; - } - operator SkFontStyle::Slant() const - { - switch (slant_) + static FontStyle Normal() + { + return FontStyle(Slant::kNormal); + } + static FontStyle Italic() { - case Slant::kNormal: - return SkFontStyle::Slant::kUpright_Slant; - case Slant::kItalic: - return SkFontStyle::Slant::kItalic_Slant; - case Slant::kOblique: - return SkFontStyle::Slant::kOblique_Slant; + return FontStyle(Slant::kItalic); + } + static FontStyle Oblique(std::optional angle = std::nullopt) + { + FontStyle oblique_style(Slant::kOblique); + oblique_style.oblique_angle_ = angle; + return oblique_style; } - } - private: - Slant slant_; - std::optional oblique_angle_; - }; + public: + FontStyle(Slant slant = Slant::kNormal) + : slant_(slant) + { + } - class LineHeight : public generics::GenericLineHeight - { - using generics::GenericLineHeight::GenericLineHeight; + Slant slant() const + { + return slant_; + } + operator SkFontStyle::Slant() const + { + switch (slant_) + { + case Slant::kNormal: + return SkFontStyle::Slant::kUpright_Slant; + case Slant::kItalic: + return SkFontStyle::Slant::kItalic_Slant; + case Slant::kOblique: + return SkFontStyle::Slant::kOblique_Slant; + } + } + + private: + Slant slant_; + std::optional oblique_angle_; + }; - public: - // Returns the computed size in pixels based on the base font size. - float computedSize(float base_font_size) const + class LineHeight : public generics::GenericLineHeight { - if (isLength()) - return getLength().px(); - else if (isNumber()) - return getNumber().value * base_font_size; - else - return 1.2f * base_font_size; // Default line height - } - }; -} + using generics::GenericLineHeight::GenericLineHeight; + + public: + // Returns the computed size in pixels based on the base font size. + float computedSize(float base_font_size) const + { + if (isLength()) + return getLength().px(); + else if (isNumber()) + return getNumber().value * base_font_size; + else + return 1.2f * base_font_size; // Default line height + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/image.cpp b/src/client/cssom/values/computed/image.cpp index c9b36a8b6..2158e98e2 100644 --- a/src/client/cssom/values/computed/image.cpp +++ b/src/client/cssom/values/computed/image.cpp @@ -4,76 +4,79 @@ #include "./image.hpp" #include "./url.hpp" -namespace client_cssom::values::computed +namespace endor { - using namespace std; - - string Image::getUrl() const + namespace client_cssom::values::computed { - if (isUrl()) + using namespace std; + + string Image::getUrl() const { - const auto &url_or_none = get(*this); - if (url_or_none.url.has_value()) - return url_or_none.url.value(); + if (isUrl()) + { + const auto &url_or_none = get(*this); + if (url_or_none.url.has_value()) + return url_or_none.url.value(); + } + return ""; } - return ""; - } - const Gradient *Image::getGradient() const - { - if (isGradient()) + const Gradient *Image::getGradient() const { - return &get(*this); + if (isGradient()) + { + return &get(*this); + } + return nullptr; } - return nullptr; - } - const typename Gradient::LinearGradient *Image::getLinearGradient() const - { - const auto *gradient = getGradient(); - if (gradient && holds_alternative(gradient->gradient_type)) + const typename Gradient::LinearGradient *Image::getLinearGradient() const { - return &get(gradient->gradient_type); + const auto *gradient = getGradient(); + if (gradient && holds_alternative(gradient->gradient_type)) + { + return &get(gradient->gradient_type); + } + return nullptr; } - return nullptr; - } - const typename Gradient::RadialGradient *Image::getRadialGradient() const - { - const auto *gradient = getGradient(); - if (gradient && holds_alternative(gradient->gradient_type)) + const typename Gradient::RadialGradient *Image::getRadialGradient() const { - return &get(gradient->gradient_type); + const auto *gradient = getGradient(); + if (gradient && holds_alternative(gradient->gradient_type)) + { + return &get(gradient->gradient_type); + } + return nullptr; } - return nullptr; - } - bool Image::isGradientRepeating() const - { - const auto *gradient = getGradient(); - return gradient ? gradient->repeating : false; - } + bool Image::isGradientRepeating() const + { + const auto *gradient = getGradient(); + return gradient ? gradient->repeating : false; + } - bool Image::isUrlImageLoaded() const - { - return url_image_data_.size() > 0; - } + bool Image::isUrlImageLoaded() const + { + return url_image_data_.size() > 0; + } - bool Image::isUrlImageLoadingOrLoaded() const - { - return is_url_image_loading_ || isUrlImageLoaded(); - } + bool Image::isUrlImageLoadingOrLoaded() const + { + return is_url_image_loading_ || isUrlImageLoaded(); + } - void Image::startLoadingUrlImage() - { - is_url_image_loading_ = true; - url_image_data_.clear(); - } + void Image::startLoadingUrlImage() + { + is_url_image_loading_ = true; + url_image_data_.clear(); + } - void Image::setUrlImageData(const void *data, size_t length) - { - url_image_data_.assign(static_cast(data), - static_cast(data) + length); - is_url_image_loading_ = false; + void Image::setUrlImageData(const void *data, size_t length) + { + url_image_data_.assign(static_cast(data), + static_cast(data) + length); + is_url_image_loading_ = false; + } } -} +} // namespace endor diff --git a/src/client/cssom/values/computed/image.hpp b/src/client/cssom/values/computed/image.hpp index a596182c8..395764121 100644 --- a/src/client/cssom/values/computed/image.hpp +++ b/src/client/cssom/values/computed/image.hpp @@ -6,87 +6,90 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - using GradientItem = generics::GenericGradientItem; - using GradientBase = generics::GenericGradient; - - class Gradient : public GradientBase + namespace client_cssom::values::computed { - using GradientBase::GenericGradient; - }; + using GradientItem = generics::GenericGradientItem; + using GradientBase = generics::GenericGradient; - class Image : public generics::GenericImage - { - public: - static Image None() + class Gradient : public GradientBase { - return Image(); - } - - public: - // Default constructor creates a 'none' image - Image() = default; - Image(const Gradient &gradient) - { - emplace(std::move(gradient)); - } - - // Copy constructor - Image(const Image &) = default; - Image &operator=(const Image &) = default; - - // Move constructor - Image(Image &&) = default; - Image &operator=(Image &&) = default; - - // Check if image is none/empty - bool isNone() const - { - return std::holds_alternative(*this); - } - - // Check if image is a URL - bool isUrl() const - { - return std::holds_alternative(*this); - } - - // Check if image is a gradient - bool isGradient() const - { - return std::holds_alternative(*this); - } - - // Get URL if it's a URL, otherwise return empty string - std::string getUrl() const; - - // Get gradient data if it's a gradient, otherwise return nullptr - const Gradient *getGradient() const; - - // Get linear gradient data if it's a linear gradient, otherwise return nullptr - const typename Gradient::LinearGradient *getLinearGradient() const; - - // Get radial gradient data if it's a radial gradient, otherwise return nullptr - const typename Gradient::RadialGradient *getRadialGradient() const; - - // Check if gradient is repeating (only valid if isGradient() is true) - bool isGradientRepeating() const; + using GradientBase::GenericGradient; + }; - // Returns the url() `Image` starts loading image data or is already loaded. - bool isUrlImageLoaded() const; - bool isUrlImageLoadingOrLoaded() const; - void startLoadingUrlImage(); - void setUrlImageData(const void *data, size_t length); - const std::vector &getUrlImageData() const + class Image : public generics::GenericImage { - return url_image_data_; - } - - private: - std::vector url_image_data_; - bool is_url_image_loading_ = false; - }; -} + public: + static Image None() + { + return Image(); + } + + public: + // Default constructor creates a 'none' image + Image() = default; + Image(const Gradient &gradient) + { + emplace(std::move(gradient)); + } + + // Copy constructor + Image(const Image &) = default; + Image &operator=(const Image &) = default; + + // Move constructor + Image(Image &&) = default; + Image &operator=(Image &&) = default; + + // Check if image is none/empty + bool isNone() const + { + return std::holds_alternative(*this); + } + + // Check if image is a URL + bool isUrl() const + { + return std::holds_alternative(*this); + } + + // Check if image is a gradient + bool isGradient() const + { + return std::holds_alternative(*this); + } + + // Get URL if it's a URL, otherwise return empty string + std::string getUrl() const; + + // Get gradient data if it's a gradient, otherwise return nullptr + const Gradient *getGradient() const; + + // Get linear gradient data if it's a linear gradient, otherwise return nullptr + const typename Gradient::LinearGradient *getLinearGradient() const; + + // Get radial gradient data if it's a radial gradient, otherwise return nullptr + const typename Gradient::RadialGradient *getRadialGradient() const; + + // Check if gradient is repeating (only valid if isGradient() is true) + bool isGradientRepeating() const; + + // Returns the url() `Image` starts loading image data or is already loaded. + bool isUrlImageLoaded() const; + bool isUrlImageLoadingOrLoaded() const; + void startLoadingUrlImage(); + void setUrlImageData(const void *data, size_t length); + const std::vector &getUrlImageData() const + { + return url_image_data_; + } + + private: + std::vector url_image_data_; + bool is_url_image_loading_ = false; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/length.hpp b/src/client/cssom/values/computed/length.hpp index f0d8f1ca8..c958725ba 100644 --- a/src/client/cssom/values/computed/length.hpp +++ b/src/client/cssom/values/computed/length.hpp @@ -9,105 +9,108 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - // An alias of computed `` value. - using Length = CSSPixelLength; - - // Either a computed `` or the `auto` keyword. - using LengthOrAuto = generics::GenericLengthPercentageOrAuto; - - // The non-negative length percentage value. - class NonNegativeLength : public generics::NonNegative + namespace client_cssom::values::computed { - public: - NonNegativeLength(float px) - : generics::NonNegative(std::max(0.0f, px)) - { - } + // An alias of computed `` value. + using Length = CSSPixelLength; - public: - inline NonNegativeLength clamp() const + // Either a computed `` or the `auto` keyword. + using LengthOrAuto = generics::GenericLengthPercentageOrAuto; + + // The non-negative length percentage value. + class NonNegativeLength : public generics::NonNegative { - return px() < 0.0f ? NonNegativeLength(0.0f) : *this; - } - }; + public: + NonNegativeLength(float px) + : generics::NonNegative(std::max(0.0f, px)) + { + } - // A computed value for `min-width`, `min-height`, `width` or `height` property. - class Size : public generics::GenericSize, - public ToLayoutValue - { - using generics::GenericSize::GenericSize; + public: + inline NonNegativeLength clamp() const + { + return px() < 0.0f ? NonNegativeLength(0.0f) : *this; + } + }; - public: - // Returns the computed value as a `Dimension` object. - crates::layout2::styles::Dimension toLayoutValue() const override + // A computed value for `min-width`, `min-height`, `width` or `height` property. + class Size : public generics::GenericSize, + public ToLayoutValue { - if (isAuto()) - return crates::layout2::styles::Dimension::Auto(); + using generics::GenericSize::GenericSize; - if (isLengthPercentage()) + public: + // Returns the computed value as a `Dimension` object. + crates::layout2::styles::Dimension toLayoutValue() const override { - const auto &lp = lengthPercent(); - if (lp.isLength()) - return crates::layout2::styles::Dimension::Length(lp.getLength().px()); - else if (lp.isPercentage()) - return crates::layout2::styles::Dimension::Percentage(lp.getPercentage().value()); - // TODO(yorkie): support calc() value? - } - return crates::layout2::styles::Dimension::Auto(); - } - }; + if (isAuto()) + return crates::layout2::styles::Dimension::Auto(); - // A computed value for `max-width` or `max-height` property. - class MaxSize : public generics::GenericMaxSize, - public ToLayoutValue - { - using generics::GenericMaxSize::GenericMaxSize; + if (isLengthPercentage()) + { + const auto &lp = lengthPercent(); + if (lp.isLength()) + return crates::layout2::styles::Dimension::Length(lp.getLength().px()); + else if (lp.isPercentage()) + return crates::layout2::styles::Dimension::Percentage(lp.getPercentage().value()); + // TODO(yorkie): support calc() value? + } + return crates::layout2::styles::Dimension::Auto(); + } + }; - public: - // Returns the computed value as a `Dimension` object. - crates::layout2::styles::Dimension toLayoutValue() const override + // A computed value for `max-width` or `max-height` property. + class MaxSize : public generics::GenericMaxSize, + public ToLayoutValue { - if (isNone()) - return crates::layout2::styles::Dimension::Auto(); + using generics::GenericMaxSize::GenericMaxSize; - if (isLengthPercentage()) + public: + // Returns the computed value as a `Dimension` object. + crates::layout2::styles::Dimension toLayoutValue() const override { - const auto &lp = lengthPercent(); - if (lp.isLength()) - return crates::layout2::styles::Dimension::Length(lp.getLength().px()); - else if (lp.isPercentage()) - return crates::layout2::styles::Dimension::Percentage(lp.getPercentage().value()); - // TODO(yorkie): support calc() value? - } - return crates::layout2::styles::Dimension::Auto(); - } - }; + if (isNone()) + return crates::layout2::styles::Dimension::Auto(); - // A computed type for `margin` properties. - class MarginSize : public generics::GenericMargin, - public ToLayoutValue - { - using generics::GenericMargin::GenericMargin; + if (isLengthPercentage()) + { + const auto &lp = lengthPercent(); + if (lp.isLength()) + return crates::layout2::styles::Dimension::Length(lp.getLength().px()); + else if (lp.isPercentage()) + return crates::layout2::styles::Dimension::Percentage(lp.getPercentage().value()); + // TODO(yorkie): support calc() value? + } + return crates::layout2::styles::Dimension::Auto(); + } + }; - public: - // Returns the computed value as a `LengthPercentageAuto` object. - crates::layout2::styles::LengthPercentageAuto toLayoutValue() const override + // A computed type for `margin` properties. + class MarginSize : public generics::GenericMargin, + public ToLayoutValue { - if (isAuto()) - return crates::layout2::styles::LengthPercentageAuto::Auto(); + using generics::GenericMargin::GenericMargin; - if (isLengthPercentage()) + public: + // Returns the computed value as a `LengthPercentageAuto` object. + crates::layout2::styles::LengthPercentageAuto toLayoutValue() const override { - const auto &lp = lengthPercent(); - if (lp.isLength()) - return crates::layout2::styles::LengthPercentageAuto::Length(lp.getLength().px()); - else if (lp.isPercentage()) - return crates::layout2::styles::LengthPercentageAuto::Percentage(lp.getPercentage().value()); - // TODO(yorkie): support calc() value? + if (isAuto()) + return crates::layout2::styles::LengthPercentageAuto::Auto(); + + if (isLengthPercentage()) + { + const auto &lp = lengthPercent(); + if (lp.isLength()) + return crates::layout2::styles::LengthPercentageAuto::Length(lp.getLength().px()); + else if (lp.isPercentage()) + return crates::layout2::styles::LengthPercentageAuto::Percentage(lp.getPercentage().value()); + // TODO(yorkie): support calc() value? + } + return crates::layout2::styles::LengthPercentageAuto::Auto(); } - return crates::layout2::styles::LengthPercentageAuto::Auto(); - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/length_percentage.hpp b/src/client/cssom/values/computed/length_percentage.hpp index 7dcbe6842..b3d0e3c2b 100644 --- a/src/client/cssom/values/computed/length_percentage.hpp +++ b/src/client/cssom/values/computed/length_percentage.hpp @@ -8,189 +8,192 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class LengthPercentage : public ToLayoutValue, - public ToCss + namespace client_cssom::values::computed { - private: - enum Tag : uint8_t + class LengthPercentage : public ToLayoutValue, + public ToCss { - kLength, - kPercentage, - kCalc, - }; + private: + enum Tag : uint8_t + { + kLength, + kPercentage, + kCalc, + }; - struct LengthVariant - { - Tag tag; - CSSPixelLength length; + struct LengthVariant + { + Tag tag; + CSSPixelLength length; + + static LengthVariant From(const CSSPixelLength &length) + { + return LengthVariant{Tag::kLength, length}; + } + }; - static LengthVariant From(const CSSPixelLength &length) + struct PercentageVariant { - return LengthVariant{Tag::kLength, length}; - } - }; + Tag tag; + Percentage percentage; - struct PercentageVariant - { - Tag tag; - Percentage percentage; + static PercentageVariant From(const Percentage &percentage) + { + return PercentageVariant{Tag::kPercentage, percentage}; + } + }; + + struct CalcVariant + { + Tag tag; + void *calc_ptr; + }; + + using Variant = std::variant; - static PercentageVariant From(const Percentage &percentage) + public: + static LengthPercentage Length(const CSSPixelLength &length) { - return PercentageVariant{Tag::kPercentage, percentage}; + return LengthPercentage(LengthVariant::From(length)); + } + static LengthPercentage Percentage(const Percentage &percentage) + { + return LengthPercentage(PercentageVariant::From(percentage)); } - }; - struct CalcVariant - { - Tag tag; - void *calc_ptr; - }; + public: + LengthPercentage(float value = 0.0f) + : variant_(LengthVariant::From(CSSPixelLength(value))) + { + } + LengthPercentage(const LengthVariant length) + : variant_(length) + { + } + LengthPercentage(const PercentageVariant percentage) + : variant_(percentage) + { + } - using Variant = std::variant; + public: + bool operator==(const LengthPercentage &other) const + { + if (isLength() && other.isLength()) + return getLength() == other.getLength(); + else if (isPercentage() && other.isPercentage()) + return getPercentage() == other.getPercentage(); + else + return false; + } + bool operator==(float other) const + { + if (isLength()) + return getLength() == other; + else if (isPercentage()) + return getPercentage() == other; + else + return false; + } + bool operator<(const LengthPercentage &other) const + { + if (isLength() && other.isLength()) + return getLength() < other.getLength(); + else if (isPercentage() && other.isPercentage()) + return getPercentage() < other.getPercentage(); + else + return false; + } + bool operator<(float other) const + { + if (isLength()) + return getLength() < other; + else if (isPercentage()) + return getPercentage() < other; + else + return false; + } + bool operator>(const LengthPercentage &other) const + { + if (isLength() && other.isLength()) + return getLength() > other.getLength(); + else if (isPercentage() && other.isPercentage()) + return getPercentage() > other.getPercentage(); + else + return false; + } + bool operator>(float other) const + { + if (isLength()) + return getLength() > other; + else if (isPercentage()) + return getPercentage() > other; + else + return false; + } - public: - static LengthPercentage Length(const CSSPixelLength &length) - { - return LengthPercentage(LengthVariant::From(length)); - } - static LengthPercentage Percentage(const Percentage &percentage) - { - return LengthPercentage(PercentageVariant::From(percentage)); - } + public: + inline bool isLength() const + { + return std::holds_alternative(variant_); + } + inline bool isPercentage() const + { + return std::holds_alternative(variant_); + } + inline bool isCalc() const + { + return std::holds_alternative(variant_); + } - public: - LengthPercentage(float value = 0.0f) - : variant_(LengthVariant::From(CSSPixelLength(value))) - { - } - LengthPercentage(const LengthVariant length) - : variant_(length) - { - } - LengthPercentage(const PercentageVariant percentage) - : variant_(percentage) - { - } + inline CSSPixelLength getLength() const + { + return isLength() + ? std::get(variant_).length + : CSSPixelLength(0.0f); + } + inline computed::Percentage getPercentage() const + { + return isPercentage() + ? std::get(variant_).percentage + : computed::Percentage(0.0f); + } - public: - bool operator==(const LengthPercentage &other) const - { - if (isLength() && other.isLength()) - return getLength() == other.getLength(); - else if (isPercentage() && other.isPercentage()) - return getPercentage() == other.getPercentage(); - else - return false; - } - bool operator==(float other) const - { - if (isLength()) - return getLength() == other; - else if (isPercentage()) - return getPercentage() == other; - else - return false; - } - bool operator<(const LengthPercentage &other) const - { - if (isLength() && other.isLength()) - return getLength() < other.getLength(); - else if (isPercentage() && other.isPercentage()) - return getPercentage() < other.getPercentage(); - else - return false; - } - bool operator<(float other) const - { - if (isLength()) - return getLength() < other; - else if (isPercentage()) - return getPercentage() < other; - else - return false; - } - bool operator>(const LengthPercentage &other) const - { - if (isLength() && other.isLength()) - return getLength() > other.getLength(); - else if (isPercentage() && other.isPercentage()) - return getPercentage() > other.getPercentage(); - else - return false; - } - bool operator>(float other) const - { - if (isLength()) - return getLength() > other; - else if (isPercentage()) - return getPercentage() > other; - else - return false; - } - - public: - inline bool isLength() const - { - return std::holds_alternative(variant_); - } - inline bool isPercentage() const - { - return std::holds_alternative(variant_); - } - inline bool isCalc() const - { - return std::holds_alternative(variant_); - } + // Converts this LengthPercentage to a Percentage if it is a percentage. + inline std::optional toPercentage() const + { + if (isPercentage()) + return std::get(variant_).percentage; + return std::nullopt; + } - inline CSSPixelLength getLength() const - { - return isLength() - ? std::get(variant_).length - : CSSPixelLength(0.0f); - } - inline computed::Percentage getPercentage() const - { - return isPercentage() - ? std::get(variant_).percentage - : computed::Percentage(0.0f); - } + crates::layout2::styles::LengthPercentage toLayoutValue() const override + { + if (isLength()) + return crates::layout2::styles::LengthPercentage::Length(getLength().px()); + else if (isPercentage()) + return crates::layout2::styles::LengthPercentage::Percentage(getPercentage().value()); + else + return crates::layout2::styles::LengthPercentage::Length(0.0f); + } - // Converts this LengthPercentage to a Percentage if it is a percentage. - inline std::optional toPercentage() const - { - if (isPercentage()) - return std::get(variant_).percentage; - return std::nullopt; - } + std::string toCss() const override + { + if (isLength()) + return std::to_string(getLength().px()) + "px"; + else if (isPercentage()) + return getPercentage().toCss(); + else if (isCalc()) + return "calc(0px)"; // Basic fallback for calc values + else + return "0px"; + } - crates::layout2::styles::LengthPercentage toLayoutValue() const override - { - if (isLength()) - return crates::layout2::styles::LengthPercentage::Length(getLength().px()); - else if (isPercentage()) - return crates::layout2::styles::LengthPercentage::Percentage(getPercentage().value()); - else - return crates::layout2::styles::LengthPercentage::Length(0.0f); - } - - std::string toCss() const override - { - if (isLength()) - return std::to_string(getLength().px()) + "px"; - else if (isPercentage()) - return getPercentage().toCss(); - else if (isCalc()) - return "calc(0px)"; // Basic fallback for calc values - else - return "0px"; - } - - private: - Variant variant_; - }; - - using NonNegativeLengthPercentage = generics::NonNegative; -} + private: + Variant variant_; + }; + + using NonNegativeLengthPercentage = generics::NonNegative; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/percentage.hpp b/src/client/cssom/values/computed/percentage.hpp index f08cf6ad6..45328e4b4 100644 --- a/src/client/cssom/values/computed/percentage.hpp +++ b/src/client/cssom/values/computed/percentage.hpp @@ -4,82 +4,85 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class Percentage : public ToCss + namespace client_cssom::values::computed { - public: - static Percentage Handred() + class Percentage : public ToCss { - return Percentage(1.0f); - } + public: + static Percentage Handred() + { + return Percentage(1.0f); + } - public: - // Construct a percentage with the value in the range of [0.0f, 1.0f]. - Percentage(float value) - : value_(value) - { - if (isnan(value_) || isinf(value_)) - value_ = 0.0f; // Normalize NaN or Inf to 0.0f - } - Percentage(const Percentage &) = default; - Percentage &operator=(const Percentage &) = default; - Percentage(Percentage &&) = default; - Percentage &operator=(Percentage &&) = default; - ~Percentage() = default; + public: + // Construct a percentage with the value in the range of [0.0f, 1.0f]. + Percentage(float value) + : value_(value) + { + if (isnan(value_) || isinf(value_)) + value_ = 0.0f; // Normalize NaN or Inf to 0.0f + } + Percentage(const Percentage &) = default; + Percentage &operator=(const Percentage &) = default; + Percentage(Percentage &&) = default; + Percentage &operator=(Percentage &&) = default; + ~Percentage() = default; - bool operator==(const Percentage &other) const - { - return value_ == other.value_; - } - bool operator==(float other) const - { - return value_ == other; - } - bool operator<(const Percentage &other) const - { - return value_ < other.value_; - } - bool operator<(float other) const - { - return value_ < other; - } - bool operator>(const Percentage &other) const - { - return value_ > other.value_; - } - bool operator>(float other) const - { - return value_ > other; - } + bool operator==(const Percentage &other) const + { + return value_ == other.value_; + } + bool operator==(float other) const + { + return value_ == other; + } + bool operator<(const Percentage &other) const + { + return value_ < other.value_; + } + bool operator<(float other) const + { + return value_ < other; + } + bool operator>(const Percentage &other) const + { + return value_ > other.value_; + } + bool operator>(float other) const + { + return value_ > other; + } - public: - std::string toCss() const override - { - return std::to_string(value_ * 100.0f) + "%"; - } + public: + std::string toCss() const override + { + return std::to_string(value_ * 100.0f) + "%"; + } - // Returns the value of the percentage in the range of [0.0f, 1.0f]. - inline float value() const - { - return value_; - } - inline Percentage abs() const - { - return Percentage(std::abs(value_)); - } - inline Percentage clampToNonNegative() const - { - return Percentage(std::max(0.0f, value_)); - } + // Returns the value of the percentage in the range of [0.0f, 1.0f]. + inline float value() const + { + return value_; + } + inline Percentage abs() const + { + return Percentage(std::abs(value_)); + } + inline Percentage clampToNonNegative() const + { + return Percentage(std::max(0.0f, value_)); + } - // Compute the percentage value with the base. - inline float computeWithBase(float base) const - { - return std::round(value_ * base); - } + // Compute the percentage value with the base. + inline float computeWithBase(float base) const + { + return std::round(value_ * base); + } - private: - float value_; // 0.0f ~ 1.0f - }; -} + private: + float value_; // 0.0f ~ 1.0f + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/position.hpp b/src/client/cssom/values/computed/position.hpp index a7d4ce833..9889f40e7 100644 --- a/src/client/cssom/values/computed/position.hpp +++ b/src/client/cssom/values/computed/position.hpp @@ -4,64 +4,67 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - using PositionType = generics::PositionType; - - class PositionComponent : public LengthPercentage, - public generics::PositionComponent + namespace client_cssom::values::computed { - using LengthPercentage::LengthPercentage; + using PositionType = generics::PositionType; - public: - inline bool isCenter() const + class PositionComponent : public LengthPercentage, + public generics::PositionComponent { - const auto &percentage = toPercentage(); - return percentage.has_value() && percentage->value() == 0.5f; - } - }; + using LengthPercentage::LengthPercentage; - using HorizontalPosition = PositionComponent; - using VerticalPosition = PositionComponent; + public: + inline bool isCenter() const + { + const auto &percentage = toPercentage(); + return percentage.has_value() && percentage->value() == 0.5f; + } + }; - class Position : public generics::GenericPosition - { - // TODO(yorkie): add methods? - }; + using HorizontalPosition = PositionComponent; + using VerticalPosition = PositionComponent; - using PositionOrAuto = generics::GenericPositionOrAuto; + class Position : public generics::GenericPosition + { + // TODO(yorkie): add methods? + }; - class InsetSize : public generics::GenericInset, - public ToLayoutValue - { - using generics::GenericInset::GenericInset; + using PositionOrAuto = generics::GenericPositionOrAuto; - public: - // Returns the computed value as a `LengthPercentageAuto` object. - crates::layout2::styles::LengthPercentageAuto toLayoutValue() const override + class InsetSize : public generics::GenericInset, + public ToLayoutValue { - if (isAuto()) - return crates::layout2::styles::LengthPercentageAuto::Auto(); + using generics::GenericInset::GenericInset; - if (isLengthPercentage()) + public: + // Returns the computed value as a `LengthPercentageAuto` object. + crates::layout2::styles::LengthPercentageAuto toLayoutValue() const override { - const auto &lp = lengthPercent(); - if (lp.isLength()) - return crates::layout2::styles::LengthPercentageAuto::Length(lp.getLength().px()); - else if (lp.isPercentage()) - return crates::layout2::styles::LengthPercentageAuto::Percentage(lp.getPercentage().value()); - // TODO(yorkie): support calc() value? + if (isAuto()) + return crates::layout2::styles::LengthPercentageAuto::Auto(); + + if (isLengthPercentage()) + { + const auto &lp = lengthPercent(); + if (lp.isLength()) + return crates::layout2::styles::LengthPercentageAuto::Length(lp.getLength().px()); + else if (lp.isPercentage()) + return crates::layout2::styles::LengthPercentageAuto::Percentage(lp.getPercentage().value()); + // TODO(yorkie): support calc() value? + } + return crates::layout2::styles::LengthPercentageAuto::Auto(); } - return crates::layout2::styles::LengthPercentageAuto::Auto(); - } - }; + }; - class Inset : public generics::Rect - { - public: - Inset(generics::Rect rect) - : generics::Rect(rect) + class Inset : public generics::Rect { - } - }; -} + public: + Inset(generics::Rect rect) + : generics::Rect(rect) + { + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/text.hpp b/src/client/cssom/values/computed/text.hpp index 03dc42463..7259d3c9d 100644 --- a/src/client/cssom/values/computed/text.hpp +++ b/src/client/cssom/values/computed/text.hpp @@ -4,113 +4,116 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class TextAlign : public specified::TextAlign + namespace client_cssom::values::computed { - using specified::TextAlign::TextAlign; - - public: - TextAlign(const specified::TextAlign &other) - : specified::TextAlign(other) + class TextAlign : public specified::TextAlign { - } + using specified::TextAlign::TextAlign; - operator skia::textlayout::TextAlign() const - { - switch (tag_) + public: + TextAlign(const specified::TextAlign &other) + : specified::TextAlign(other) { - case Tag::kStart: - return skia::textlayout::TextAlign::kStart; - case Tag::kEnd: - return skia::textlayout::TextAlign::kEnd; - case Tag::kLeft: - return skia::textlayout::TextAlign::kLeft; - case Tag::kRight: - return skia::textlayout::TextAlign::kRight; - case Tag::kCenter: - return skia::textlayout::TextAlign::kCenter; - case Tag::kJustify: - return skia::textlayout::TextAlign::kJustify; - default: - // TODO(yorkie): support match-parent. - assert(false && "Invalid tag."); - return skia::textlayout::TextAlign::kStart; } - } - // Convert to layout value for layout system - crates::layout2::styles::TextAlign toLayoutValue() const - { - switch (tag_) + operator skia::textlayout::TextAlign() const { - case Tag::kStart: - return crates::layout2::styles::TextAlign::Start(); - case Tag::kEnd: - return crates::layout2::styles::TextAlign::End(); - case Tag::kLeft: - return crates::layout2::styles::TextAlign::Left(); - case Tag::kRight: - return crates::layout2::styles::TextAlign::Right(); - case Tag::kCenter: - return crates::layout2::styles::TextAlign::Center(); - case Tag::kJustify: - return crates::layout2::styles::TextAlign::Justify(); - default: - // TODO(yorkie): support match-parent. - return crates::layout2::styles::TextAlign::Start(); + switch (tag_) + { + case Tag::kStart: + return skia::textlayout::TextAlign::kStart; + case Tag::kEnd: + return skia::textlayout::TextAlign::kEnd; + case Tag::kLeft: + return skia::textlayout::TextAlign::kLeft; + case Tag::kRight: + return skia::textlayout::TextAlign::kRight; + case Tag::kCenter: + return skia::textlayout::TextAlign::kCenter; + case Tag::kJustify: + return skia::textlayout::TextAlign::kJustify; + default: + // TODO(yorkie): support match-parent. + assert(false && "Invalid tag."); + return skia::textlayout::TextAlign::kStart; + } } - } - }; - class Direction : public client_cssom::values::specified::Direction - { - using client_cssom::values::specified::Direction::Direction; + // Convert to layout value for layout system + crates::layout2::styles::TextAlign toLayoutValue() const + { + switch (tag_) + { + case Tag::kStart: + return crates::layout2::styles::TextAlign::Start(); + case Tag::kEnd: + return crates::layout2::styles::TextAlign::End(); + case Tag::kLeft: + return crates::layout2::styles::TextAlign::Left(); + case Tag::kRight: + return crates::layout2::styles::TextAlign::Right(); + case Tag::kCenter: + return crates::layout2::styles::TextAlign::Center(); + case Tag::kJustify: + return crates::layout2::styles::TextAlign::Justify(); + default: + // TODO(yorkie): support match-parent. + return crates::layout2::styles::TextAlign::Start(); + } + } + }; - public: - operator skia::textlayout::TextDirection() const + class Direction : public client_cssom::values::specified::Direction { - return tag_ == Tag::kLTR - ? skia::textlayout::TextDirection::kLtr - : skia::textlayout::TextDirection::kRtl; - } - }; + using client_cssom::values::specified::Direction::Direction; - class VerticalAlign : public specified::VerticalAlign - { - using specified::VerticalAlign::VerticalAlign; + public: + operator skia::textlayout::TextDirection() const + { + return tag_ == Tag::kLTR + ? skia::textlayout::TextDirection::kLtr + : skia::textlayout::TextDirection::kRtl; + } + }; - public: - VerticalAlign(const specified::VerticalAlign &other) - : specified::VerticalAlign(other) + class VerticalAlign : public specified::VerticalAlign { - } + using specified::VerticalAlign::VerticalAlign; - // Check if this is a baseline alignment - bool isBaseline() const - { - return tag() == Tag::kBaseline; - } + public: + VerticalAlign(const specified::VerticalAlign &other) + : specified::VerticalAlign(other) + { + } - // Check if this is a keyword alignment (not length/percentage) - bool isKeyword() const - { - return tag() != Tag::kLength && tag() != Tag::kPercentage; - } + // Check if this is a baseline alignment + bool isBaseline() const + { + return tag() == Tag::kBaseline; + } - // Get the offset in pixels for length/percentage values - // For percentage, lineHeight should be provided - float getOffset(float lineHeight = 0.0f) const - { - switch (tag()) + // Check if this is a keyword alignment (not length/percentage) + bool isKeyword() const + { + return tag() != Tag::kLength && tag() != Tag::kPercentage; + } + + // Get the offset in pixels for length/percentage values + // For percentage, lineHeight should be provided + float getOffset(float lineHeight = 0.0f) const { - case Tag::kLength: - return value(); - case Tag::kPercentage: - return (value() / 100.0f) * lineHeight; - default: - return 0.0f; // Keywords don't have numeric offsets + switch (tag()) + { + case Tag::kLength: + return value(); + case Tag::kPercentage: + return (value() / 100.0f) * lineHeight; + default: + return 0.0f; // Keywords don't have numeric offsets + } } - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/time.hpp b/src/client/cssom/values/computed/time.hpp index 57a093d7a..03f90548b 100644 --- a/src/client/cssom/values/computed/time.hpp +++ b/src/client/cssom/values/computed/time.hpp @@ -2,7 +2,10 @@ #include -namespace client_cssom::values::computed +namespace endor { - using Time = generics::GenericTime; -} + namespace client_cssom::values::computed + { + using Time = generics::GenericTime; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/transform.hpp b/src/client/cssom/values/computed/transform.hpp index 1b808b282..8ddd3fa12 100644 --- a/src/client/cssom/values/computed/transform.hpp +++ b/src/client/cssom/values/computed/transform.hpp @@ -8,247 +8,250 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - class TransformOperation; - using TransformOperationBase = generics::GenericTransformOperation; - - class TransformOperation : public TransformOperationBase - { - using TransformOperationBase::GenericTransformOperation; - }; - - class Transform : public generics::GenericTransform + namespace client_cssom::values::computed { - using generics::GenericTransform::GenericTransform; + class TransformOperation; + using TransformOperationBase = generics::GenericTransformOperation; - public: - // Apply transforms without element size context (percentages will be treated as 0) - size_t applyTo(glm::mat4 &mat) const + class TransformOperation : public TransformOperationBase { - return applyTo(mat, glm::vec2(0.0f, 0.0f)); - } + using TransformOperationBase::GenericTransformOperation; + }; - // Apply transforms with element size context for percentage resolution - size_t applyTo(glm::mat4 &mat, const glm::vec2 &elementSize) const + class Transform : public generics::GenericTransform { - size_t count = 0; - for (const auto &op : operations()) - { - if (op.isEmpty()) - continue; - - if (op.isMatrix()) - applyMatrix(op.getMatrix(), mat); - else if (op.isMatrix3D()) - applyMatrix3D(op.getMatrix3D(), mat); - else if (op.isSkew()) - applySkew(op.getSkew(), mat); - else if (op.isSkewX()) - applySkewX(op.getSkewX(), mat); - else if (op.isSkewY()) - applySkewY(op.getSkewY(), mat); - else if (op.isTranslate()) - applyTranslate(op.getTranslate(), mat, elementSize); - else if (op.isTranslateX()) - applyTranslateX(op.getTranslateX(), mat, elementSize); - else if (op.isTranslateY()) - applyTranslateY(op.getTranslateY(), mat, elementSize); - else if (op.isTranslateZ()) - applyTranslateZ(op.getTranslateZ(), mat); - else if (op.isTranslate3D()) - applyTranslate3D(op.getTranslate3D(), mat, elementSize); - else if (op.isScale()) - applyScale(op.getScale(), mat); - else if (op.isScaleX()) - applyScaleX(op.getScaleX(), mat); - else if (op.isScaleY()) - applyScaleY(op.getScaleY(), mat); - else if (op.isScaleZ()) - applyScaleZ(op.getScaleZ(), mat); - else if (op.isScale3D()) - applyScale3D(op.getScale3D(), mat); - else if (op.isRotate()) - applyRotate(op.getRotate(), mat); - else if (op.isRotateX()) - applyRotateX(op.getRotateX(), mat); - else if (op.isRotateY()) - applyRotateY(op.getRotateY(), mat); - else if (op.isRotateZ()) - applyRotateZ(op.getRotateZ(), mat); - else if (op.isRotate3D()) - applyRotate3D(op.getRotate3D(), mat); - else - continue; // Unsupported operation, skip it. - - count++; - } - return count; - } + using generics::GenericTransform::GenericTransform; - private: - // Helper method to resolve LengthPercentage to pixels with element size context - float resolveLengthPercentage(const LengthPercentage &value, float elementDimension) const - { - if (value.isLength()) + public: + // Apply transforms without element size context (percentages will be treated as 0) + size_t applyTo(glm::mat4 &mat) const { - return value.getLength().px(); + return applyTo(mat, glm::vec2(0.0f, 0.0f)); } - else if (value.isPercentage()) + + // Apply transforms with element size context for percentage resolution + size_t applyTo(glm::mat4 &mat, const glm::vec2 &elementSize) const { - // For transforms, percentage is relative to the element's own size - return value.getPercentage().computeWithBase(elementDimension); + size_t count = 0; + for (const auto &op : operations()) + { + if (op.isEmpty()) + continue; + + if (op.isMatrix()) + applyMatrix(op.getMatrix(), mat); + else if (op.isMatrix3D()) + applyMatrix3D(op.getMatrix3D(), mat); + else if (op.isSkew()) + applySkew(op.getSkew(), mat); + else if (op.isSkewX()) + applySkewX(op.getSkewX(), mat); + else if (op.isSkewY()) + applySkewY(op.getSkewY(), mat); + else if (op.isTranslate()) + applyTranslate(op.getTranslate(), mat, elementSize); + else if (op.isTranslateX()) + applyTranslateX(op.getTranslateX(), mat, elementSize); + else if (op.isTranslateY()) + applyTranslateY(op.getTranslateY(), mat, elementSize); + else if (op.isTranslateZ()) + applyTranslateZ(op.getTranslateZ(), mat); + else if (op.isTranslate3D()) + applyTranslate3D(op.getTranslate3D(), mat, elementSize); + else if (op.isScale()) + applyScale(op.getScale(), mat); + else if (op.isScaleX()) + applyScaleX(op.getScaleX(), mat); + else if (op.isScaleY()) + applyScaleY(op.getScaleY(), mat); + else if (op.isScaleZ()) + applyScaleZ(op.getScaleZ(), mat); + else if (op.isScale3D()) + applyScale3D(op.getScale3D(), mat); + else if (op.isRotate()) + applyRotate(op.getRotate(), mat); + else if (op.isRotateX()) + applyRotateX(op.getRotateX(), mat); + else if (op.isRotateY()) + applyRotateY(op.getRotateY(), mat); + else if (op.isRotateZ()) + applyRotateZ(op.getRotateZ(), mat); + else if (op.isRotate3D()) + applyRotate3D(op.getRotate3D(), mat); + else + continue; // Unsupported operation, skip it. + + count++; + } + return count; } - else + + private: + // Helper method to resolve LengthPercentage to pixels with element size context + float resolveLengthPercentage(const LengthPercentage &value, float elementDimension) const { - return 0.0f; // fallback for calc values + if (value.isLength()) + { + return value.getLength().px(); + } + else if (value.isPercentage()) + { + // For transforms, percentage is relative to the element's own size + return value.getPercentage().computeWithBase(elementDimension); + } + else + { + return 0.0f; // fallback for calc values + } } - } - void applyMatrix(const generics::GenericMatrix src_matrix, glm::mat4 &target_mat) const - { - auto a = src_matrix.a().value; - auto b = src_matrix.b().value; - auto c = src_matrix.c().value; - auto d = src_matrix.d().value; - auto e = src_matrix.e().value; - auto f = src_matrix.f().value; - // clang-format off + void applyMatrix(const generics::GenericMatrix src_matrix, glm::mat4 &target_mat) const + { + auto a = src_matrix.a().value; + auto b = src_matrix.b().value; + auto c = src_matrix.c().value; + auto d = src_matrix.d().value; + auto e = src_matrix.e().value; + auto f = src_matrix.f().value; + // clang-format off target_mat = glm::mat4(a, b, 0.0f, 0.0f, c, d, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, e, f, 0.0f, 1.0f); - // clang-format on - } - void applyMatrix3D(const generics::GenericMatrix3D src_matrix, glm::mat4 &target_mat) const - { - target_mat = glm::mat4(src_matrix.m11().value, - src_matrix.m12().value, - src_matrix.m13().value, - src_matrix.m14().value, - src_matrix.m21().value, - src_matrix.m22().value, - src_matrix.m23().value, - src_matrix.m24().value, - src_matrix.m31().value, - src_matrix.m32().value, - src_matrix.m33().value, - src_matrix.m34().value, - src_matrix.m41().value, - src_matrix.m42().value, - src_matrix.m43().value, - src_matrix.m44().value); - } - void applySkew(const generics::GenericSkew &src_skew, glm::mat4 &target_mat) const - { - // TODO(yorkie): Implement the skew application logic. - std::cerr << "skew() transformation is not implemented yet." << std::endl; - } - void applySkewX(const generics::GenericSkewX &src_skew_x, glm::mat4 &target_mat) const - { - // TODO(yorkie): Implement the skew X application logic. - std::cerr << "skewX() transformation is not implemented yet." << std::endl; - } - void applySkewY(const generics::GenericSkewY &src_skew_y, glm::mat4 &target_mat) const - { - // TODO(yorkie): Implement the skew Y application logic. - std::cerr << "skewY() transformation is not implemented yet." << std::endl; - } - void applyTranslate(const generics::GenericTranslate &src_translate, - glm::mat4 &target_mat, - const glm::vec2 &elementSize) const - { - auto translate_x = pixelToMeter(resolveLengthPercentage(src_translate.x(), elementSize.x)); - auto translate_y = pixelToMeter(resolveLengthPercentage(src_translate.y(), elementSize.y)); - target_mat = glm::translate(target_mat, glm::vec3(translate_x, -translate_y, 0.0f)); - } - void applyTranslateX(const generics::GenericTranslateX &src_translate_x, - glm::mat4 &target_mat, - const glm::vec2 &elementSize) const - { - auto translate_x = pixelToMeter(resolveLengthPercentage(src_translate_x.x(), elementSize.x)); - target_mat = glm::translate(target_mat, glm::vec3(translate_x, 0.0f, 0.0f)); - } - void applyTranslateY(const generics::GenericTranslateY &src_translate_y, - glm::mat4 &target_mat, - const glm::vec2 &elementSize) const - { - auto translate_y = pixelToMeter(resolveLengthPercentage(src_translate_y.y(), elementSize.y)); - target_mat = glm::translate(target_mat, glm::vec3(0.0f, -translate_y, 0.0f)); - } - void applyTranslateZ(const generics::GenericTranslateZ &src_translate_z, - glm::mat4 &target_mat) const - { - auto translate_z = pixelToMeter(src_translate_z.z().px()); - target_mat = glm::translate(target_mat, glm::vec3(0.0f, 0.0f, translate_z)); - } - void applyTranslate3D(const generics::GenericTranslate3D &src_translate_3d, + // clang-format on + } + void applyMatrix3D(const generics::GenericMatrix3D src_matrix, glm::mat4 &target_mat) const + { + target_mat = glm::mat4(src_matrix.m11().value, + src_matrix.m12().value, + src_matrix.m13().value, + src_matrix.m14().value, + src_matrix.m21().value, + src_matrix.m22().value, + src_matrix.m23().value, + src_matrix.m24().value, + src_matrix.m31().value, + src_matrix.m32().value, + src_matrix.m33().value, + src_matrix.m34().value, + src_matrix.m41().value, + src_matrix.m42().value, + src_matrix.m43().value, + src_matrix.m44().value); + } + void applySkew(const generics::GenericSkew &src_skew, glm::mat4 &target_mat) const + { + // TODO(yorkie): Implement the skew application logic. + std::cerr << "skew() transformation is not implemented yet." << std::endl; + } + void applySkewX(const generics::GenericSkewX &src_skew_x, glm::mat4 &target_mat) const + { + // TODO(yorkie): Implement the skew X application logic. + std::cerr << "skewX() transformation is not implemented yet." << std::endl; + } + void applySkewY(const generics::GenericSkewY &src_skew_y, glm::mat4 &target_mat) const + { + // TODO(yorkie): Implement the skew Y application logic. + std::cerr << "skewY() transformation is not implemented yet." << std::endl; + } + void applyTranslate(const generics::GenericTranslate &src_translate, glm::mat4 &target_mat, const glm::vec2 &elementSize) const - { - auto translate_x = pixelToMeter(resolveLengthPercentage(src_translate_3d.x(), elementSize.x)); - auto translate_y = pixelToMeter(resolveLengthPercentage(src_translate_3d.y(), elementSize.y)); - auto translate_z = pixelToMeter(src_translate_3d.z().px()); - target_mat = glm::translate(target_mat, glm::vec3(translate_x, -translate_y, translate_z)); - } - void applyScale(const generics::GenericScale &src_scale, glm::mat4 &target_mat) const - { - auto scale_value = src_scale.number().value; - target_mat = glm::scale(target_mat, glm::vec3(scale_value, scale_value, scale_value)); - } - void applyScaleX(const generics::GenericScaleX &src_scale_x, glm::mat4 &target_mat) const - { - auto scale_x = src_scale_x.x().value; - target_mat = glm::scale(target_mat, glm::vec3(scale_x, 1.0f, 1.0f)); - } - void applyScaleY(const generics::GenericScaleY &src_scale_y, glm::mat4 &target_mat) const - { - auto scale_y = src_scale_y.y().value; - target_mat = glm::scale(target_mat, glm::vec3(1.0f, scale_y, 1.0f)); - } - void applyScaleZ(const generics::GenericScaleZ &src_scale_z, glm::mat4 &target_mat) const - { - auto scale_z = src_scale_z.z().value; - target_mat = glm::scale(target_mat, glm::vec3(1.0f, 1.0f, scale_z)); - } - void applyScale3D(const generics::GenericScale3D &src_scale_3d, glm::mat4 &target_mat) const - { - auto scale_x = src_scale_3d.x().value; - auto scale_y = src_scale_3d.y().value; - auto scale_z = src_scale_3d.z().value; - target_mat = glm::scale(target_mat, glm::vec3(scale_x, scale_y, scale_z)); - } - void applyRotate(const generics::GenericRotate &src_rotate, glm::mat4 &target_mat) const - { - auto angle_rad = src_rotate.angle().radians(); - target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(0.0f, 0.0f, 1.0f)); - } - void applyRotateX(const generics::GenericRotateX &src_rotate_x, glm::mat4 &target_mat) const - { - auto angle_rad = src_rotate_x.angle().radians(); - target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(1.0f, 0.0f, 0.0f)); - } - void applyRotateY(const generics::GenericRotateY &src_rotate_y, glm::mat4 &target_mat) const - { - auto angle_rad = src_rotate_y.angle().radians(); - target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(0.0f, 1.0f, 0.0f)); - } - void applyRotateZ(const generics::GenericRotateZ &src_rotate_z, glm::mat4 &target_mat) const - { - auto angle_rad = src_rotate_z.angle().radians(); - target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(0.0f, 0.0f, 1.0f)); - } - void applyRotate3D(const generics::GenericRotate3D &src_rotate_3d, glm::mat4 &target_mat) const - { - auto angle_rad = src_rotate_3d.angle().radians(); - auto x = src_rotate_3d.x().value; - auto y = src_rotate_3d.y().value; - auto z = src_rotate_3d.z().value; - target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(x, y, z)); - } - }; -} + { + auto translate_x = pixelToMeter(resolveLengthPercentage(src_translate.x(), elementSize.x)); + auto translate_y = pixelToMeter(resolveLengthPercentage(src_translate.y(), elementSize.y)); + target_mat = glm::translate(target_mat, glm::vec3(translate_x, -translate_y, 0.0f)); + } + void applyTranslateX(const generics::GenericTranslateX &src_translate_x, + glm::mat4 &target_mat, + const glm::vec2 &elementSize) const + { + auto translate_x = pixelToMeter(resolveLengthPercentage(src_translate_x.x(), elementSize.x)); + target_mat = glm::translate(target_mat, glm::vec3(translate_x, 0.0f, 0.0f)); + } + void applyTranslateY(const generics::GenericTranslateY &src_translate_y, + glm::mat4 &target_mat, + const glm::vec2 &elementSize) const + { + auto translate_y = pixelToMeter(resolveLengthPercentage(src_translate_y.y(), elementSize.y)); + target_mat = glm::translate(target_mat, glm::vec3(0.0f, -translate_y, 0.0f)); + } + void applyTranslateZ(const generics::GenericTranslateZ &src_translate_z, + glm::mat4 &target_mat) const + { + auto translate_z = pixelToMeter(src_translate_z.z().px()); + target_mat = glm::translate(target_mat, glm::vec3(0.0f, 0.0f, translate_z)); + } + void applyTranslate3D(const generics::GenericTranslate3D &src_translate_3d, + glm::mat4 &target_mat, + const glm::vec2 &elementSize) const + { + auto translate_x = pixelToMeter(resolveLengthPercentage(src_translate_3d.x(), elementSize.x)); + auto translate_y = pixelToMeter(resolveLengthPercentage(src_translate_3d.y(), elementSize.y)); + auto translate_z = pixelToMeter(src_translate_3d.z().px()); + target_mat = glm::translate(target_mat, glm::vec3(translate_x, -translate_y, translate_z)); + } + void applyScale(const generics::GenericScale &src_scale, glm::mat4 &target_mat) const + { + auto scale_value = src_scale.number().value; + target_mat = glm::scale(target_mat, glm::vec3(scale_value, scale_value, scale_value)); + } + void applyScaleX(const generics::GenericScaleX &src_scale_x, glm::mat4 &target_mat) const + { + auto scale_x = src_scale_x.x().value; + target_mat = glm::scale(target_mat, glm::vec3(scale_x, 1.0f, 1.0f)); + } + void applyScaleY(const generics::GenericScaleY &src_scale_y, glm::mat4 &target_mat) const + { + auto scale_y = src_scale_y.y().value; + target_mat = glm::scale(target_mat, glm::vec3(1.0f, scale_y, 1.0f)); + } + void applyScaleZ(const generics::GenericScaleZ &src_scale_z, glm::mat4 &target_mat) const + { + auto scale_z = src_scale_z.z().value; + target_mat = glm::scale(target_mat, glm::vec3(1.0f, 1.0f, scale_z)); + } + void applyScale3D(const generics::GenericScale3D &src_scale_3d, glm::mat4 &target_mat) const + { + auto scale_x = src_scale_3d.x().value; + auto scale_y = src_scale_3d.y().value; + auto scale_z = src_scale_3d.z().value; + target_mat = glm::scale(target_mat, glm::vec3(scale_x, scale_y, scale_z)); + } + void applyRotate(const generics::GenericRotate &src_rotate, glm::mat4 &target_mat) const + { + auto angle_rad = src_rotate.angle().radians(); + target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(0.0f, 0.0f, 1.0f)); + } + void applyRotateX(const generics::GenericRotateX &src_rotate_x, glm::mat4 &target_mat) const + { + auto angle_rad = src_rotate_x.angle().radians(); + target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(1.0f, 0.0f, 0.0f)); + } + void applyRotateY(const generics::GenericRotateY &src_rotate_y, glm::mat4 &target_mat) const + { + auto angle_rad = src_rotate_y.angle().radians(); + target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(0.0f, 1.0f, 0.0f)); + } + void applyRotateZ(const generics::GenericRotateZ &src_rotate_z, glm::mat4 &target_mat) const + { + auto angle_rad = src_rotate_z.angle().radians(); + target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(0.0f, 0.0f, 1.0f)); + } + void applyRotate3D(const generics::GenericRotate3D &src_rotate_3d, glm::mat4 &target_mat) const + { + auto angle_rad = src_rotate_3d.angle().radians(); + auto x = src_rotate_3d.x().value; + auto y = src_rotate_3d.y().value; + auto z = src_rotate_3d.z().value; + target_mat = glm::rotate(target_mat, angle_rad, glm::vec3(x, y, z)); + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/computed/url.hpp b/src/client/cssom/values/computed/url.hpp index 96b7aa233..a0e7bf0f6 100644 --- a/src/client/cssom/values/computed/url.hpp +++ b/src/client/cssom/values/computed/url.hpp @@ -3,7 +3,10 @@ #include #include -namespace client_cssom::values::computed +namespace endor { - using UrlOrNone = generics::GenericUrlOrNone; -} + namespace client_cssom::values::computed + { + using UrlOrNone = generics::GenericUrlOrNone; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/animation.hpp b/src/client/cssom/values/generics/animation.hpp index 30cd51af9..41cc2c1a8 100644 --- a/src/client/cssom/values/generics/animation.hpp +++ b/src/client/cssom/values/generics/animation.hpp @@ -5,84 +5,87 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - class GenericTransitionProperty : public ToCss + namespace client_cssom::values::generics { - protected: - enum Tag : uint8_t + class GenericTransitionProperty : public ToCss { - kNonCustom, - kCustom, - kUnsupported, - }; - using PropertyIdVariant = std::variant; + protected: + enum Tag : uint8_t + { + kNonCustom, + kCustom, + kUnsupported, + }; + using PropertyIdVariant = std::variant; - public: - GenericTransitionProperty() - : tag_(kCustom) - , property_id_(CustomPropertyId("none")) - { - } - GenericTransitionProperty(NonCustomPropertyId non_custom_property_id) - : tag_(kNonCustom) - , property_id_(non_custom_property_id) - { - } - GenericTransitionProperty(CustomPropertyId custom_property_id) - : tag_(kCustom) - , property_id_(custom_property_id) - { - } + public: + GenericTransitionProperty() + : tag_(kCustom) + , property_id_(CustomPropertyId("none")) + { + } + GenericTransitionProperty(NonCustomPropertyId non_custom_property_id) + : tag_(kNonCustom) + , property_id_(non_custom_property_id) + { + } + GenericTransitionProperty(CustomPropertyId custom_property_id) + : tag_(kCustom) + , property_id_(custom_property_id) + { + } - public: - std::string toCss() const override - { - if (tag_ == kNonCustom) - return std::get(property_id_); - else if (tag_ == kCustom) - return std::get(property_id_); - return "none"; // Unsupported case. - } - - protected: - Tag tag_; - PropertyIdVariant property_id_; - }; + public: + std::string toCss() const override + { + if (tag_ == kNonCustom) + return std::get(property_id_); + else if (tag_ == kCustom) + return std::get(property_id_); + return "none"; // Unsupported case. + } - class GenericTransitionBehavior - { - private: - enum Tag : uint8_t - { - // Transitions will not be started for discrete properties, only for interpolable properties. - kNormal, - // Transitions will be started for discrete properties as well as interpolable properties. - kAllowDiscrete, + protected: + Tag tag_; + PropertyIdVariant property_id_; }; - public: - static GenericTransitionBehavior Normal() - { - return GenericTransitionBehavior(kNormal); - } - static GenericTransitionBehavior AllowDiscrete() + class GenericTransitionBehavior { - return GenericTransitionBehavior(kAllowDiscrete); - } + private: + enum Tag : uint8_t + { + // Transitions will not be started for discrete properties, only for interpolable properties. + kNormal, + // Transitions will be started for discrete properties as well as interpolable properties. + kAllowDiscrete, + }; - Tag tag() const - { - return tag_; - } + public: + static GenericTransitionBehavior Normal() + { + return GenericTransitionBehavior(kNormal); + } + static GenericTransitionBehavior AllowDiscrete() + { + return GenericTransitionBehavior(kAllowDiscrete); + } - private: - GenericTransitionBehavior(Tag tag) - : tag_(tag) - { - } + Tag tag() const + { + return tag_; + } + + private: + GenericTransitionBehavior(Tag tag) + : tag_(tag) + { + } - private: - Tag tag_; - }; -} + private: + Tag tag_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/background.hpp b/src/client/cssom/values/generics/background.hpp index 2d64bd779..ce168e835 100644 --- a/src/client/cssom/values/generics/background.hpp +++ b/src/client/cssom/values/generics/background.hpp @@ -4,820 +4,823 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericBackgroundBlendMode : public ToCss + namespace client_cssom::values::generics { - protected: - enum Tag : uint8_t + template + class GenericBackgroundBlendMode : public ToCss { - kNormal = 0, - kMultiply, - kScreen, - kOverlay, - kDarken, - kLighten, - kColorDodge, - kColorBurn, - kHardLight, - kSoftLight, - kDifference, - kExclusion, - kHue, - kSaturation, - kColor, - kLuminosity, - }; - - public: - static T Normal() - { - return T(kNormal); - } - static T Multiply() - { - return T(kMultiply); - } - static T Screen() - { - return T(kScreen); - } - static T Overlay() - { - return T(kOverlay); - } - static T Darken() - { - return T(kDarken); - } - static T Lighten() - { - return T(kLighten); - } - static T ColorDodge() - { - return T(kColorDodge); - } - static T ColorBurn() - { - return T(kColorBurn); - } - static T HardLight() - { - return T(kHardLight); - } - static T SoftLight() - { - return T(kSoftLight); - } - static T Difference() - { - return T(kDifference); - } - static T Exclusion() - { - return T(kExclusion); - } - static T Hue() - { - return T(kHue); - } - static T Saturation() - { - return T(kSaturation); - } - static T Color() - { - return T(kColor); - } - static T Luminosity() - { - return T(kLuminosity); - } - - public: - GenericBackgroundBlendMode() - : tag_(kNormal) - { - } + protected: + enum Tag : uint8_t + { + kNormal = 0, + kMultiply, + kScreen, + kOverlay, + kDarken, + kLighten, + kColorDodge, + kColorBurn, + kHardLight, + kSoftLight, + kDifference, + kExclusion, + kHue, + kSaturation, + kColor, + kLuminosity, + }; + + public: + static T Normal() + { + return T(kNormal); + } + static T Multiply() + { + return T(kMultiply); + } + static T Screen() + { + return T(kScreen); + } + static T Overlay() + { + return T(kOverlay); + } + static T Darken() + { + return T(kDarken); + } + static T Lighten() + { + return T(kLighten); + } + static T ColorDodge() + { + return T(kColorDodge); + } + static T ColorBurn() + { + return T(kColorBurn); + } + static T HardLight() + { + return T(kHardLight); + } + static T SoftLight() + { + return T(kSoftLight); + } + static T Difference() + { + return T(kDifference); + } + static T Exclusion() + { + return T(kExclusion); + } + static T Hue() + { + return T(kHue); + } + static T Saturation() + { + return T(kSaturation); + } + static T Color() + { + return T(kColor); + } + static T Luminosity() + { + return T(kLuminosity); + } - protected: - GenericBackgroundBlendMode(Tag tag) - : tag_(tag) - { - } + public: + GenericBackgroundBlendMode() + : tag_(kNormal) + { + } - public: - std::string toCss() const override - { - switch (tag_) - { - case kNormal: - return "normal"; - case kMultiply: - return "multiply"; - case kScreen: - return "screen"; - case kOverlay: - return "overlay"; - case kDarken: - return "darken"; - case kLighten: - return "lighten"; - case kColorDodge: - return "color-dodge"; - case kColorBurn: - return "color-burn"; - case kHardLight: - return "hard-light"; - case kSoftLight: - return "soft-light"; - case kDifference: - return "difference"; - case kExclusion: - return "exclusion"; - case kHue: - return "hue"; - case kSaturation: - return "saturation"; - case kColor: - return "color"; - case kLuminosity: - return "luminosity"; - } - return ""; - } + protected: + GenericBackgroundBlendMode(Tag tag) + : tag_(tag) + { + } - inline bool isNormal() const - { - return tag_ == kNormal; - } - inline bool isMultiply() const - { - return tag_ == kMultiply; - } - inline bool isScreen() const - { - return tag_ == kScreen; - } - inline bool isOverlay() const - { - return tag_ == kOverlay; - } - inline bool isDarken() const - { - return tag_ == kDarken; - } - inline bool isLighten() const - { - return tag_ == kLighten; - } - inline bool isColorDodge() const - { - return tag_ == kColorDodge; - } - inline bool isColorBurn() const - { - return tag_ == kColorBurn; - } - inline bool isHardLight() const - { - return tag_ == kHardLight; - } - inline bool isSoftLight() const - { - return tag_ == kSoftLight; - } - inline bool isDifference() const - { - return tag_ == kDifference; - } - inline bool isExclusion() const - { - return tag_ == kExclusion; - } - inline bool isHue() const - { - return tag_ == kHue; - } - inline bool isSaturation() const - { - return tag_ == kSaturation; - } - inline bool isColor() const - { - return tag_ == kColor; - } - inline bool isLuminosity() const - { - return tag_ == kLuminosity; - } + public: + std::string toCss() const override + { + switch (tag_) + { + case kNormal: + return "normal"; + case kMultiply: + return "multiply"; + case kScreen: + return "screen"; + case kOverlay: + return "overlay"; + case kDarken: + return "darken"; + case kLighten: + return "lighten"; + case kColorDodge: + return "color-dodge"; + case kColorBurn: + return "color-burn"; + case kHardLight: + return "hard-light"; + case kSoftLight: + return "soft-light"; + case kDifference: + return "difference"; + case kExclusion: + return "exclusion"; + case kHue: + return "hue"; + case kSaturation: + return "saturation"; + case kColor: + return "color"; + case kLuminosity: + return "luminosity"; + } + return ""; + } - protected: - Tag tag_; - }; + inline bool isNormal() const + { + return tag_ == kNormal; + } + inline bool isMultiply() const + { + return tag_ == kMultiply; + } + inline bool isScreen() const + { + return tag_ == kScreen; + } + inline bool isOverlay() const + { + return tag_ == kOverlay; + } + inline bool isDarken() const + { + return tag_ == kDarken; + } + inline bool isLighten() const + { + return tag_ == kLighten; + } + inline bool isColorDodge() const + { + return tag_ == kColorDodge; + } + inline bool isColorBurn() const + { + return tag_ == kColorBurn; + } + inline bool isHardLight() const + { + return tag_ == kHardLight; + } + inline bool isSoftLight() const + { + return tag_ == kSoftLight; + } + inline bool isDifference() const + { + return tag_ == kDifference; + } + inline bool isExclusion() const + { + return tag_ == kExclusion; + } + inline bool isHue() const + { + return tag_ == kHue; + } + inline bool isSaturation() const + { + return tag_ == kSaturation; + } + inline bool isColor() const + { + return tag_ == kColor; + } + inline bool isLuminosity() const + { + return tag_ == kLuminosity; + } - template - class GenericBackgroundClip : public ToCss - { - protected: - enum Tag : uint8_t - { - kBorderBox = 0, - kPaddingBox, - kContentBox, - kText, + protected: + Tag tag_; }; - public: - static T BorderBox() - { - return T(kBorderBox); - } - static T PaddingBox() - { - return T(kPaddingBox); - } - static T ContentBox() - { - return T(kContentBox); - } - static T Text() - { - return T(kText); - } - - public: - GenericBackgroundClip() - : tag_(kBorderBox) + template + class GenericBackgroundClip : public ToCss { - } + protected: + enum Tag : uint8_t + { + kBorderBox = 0, + kPaddingBox, + kContentBox, + kText, + }; + + public: + static T BorderBox() + { + return T(kBorderBox); + } + static T PaddingBox() + { + return T(kPaddingBox); + } + static T ContentBox() + { + return T(kContentBox); + } + static T Text() + { + return T(kText); + } - protected: - GenericBackgroundClip(Tag tag) - : tag_(tag) - { - } + public: + GenericBackgroundClip() + : tag_(kBorderBox) + { + } - public: - std::string toCss() const override - { - switch (tag_) - { - case kBorderBox: - return "border-box"; - case kPaddingBox: - return "padding-box"; - case kContentBox: - return "content-box"; - case kText: - return "text"; - } - return ""; - } + protected: + GenericBackgroundClip(Tag tag) + : tag_(tag) + { + } - inline bool isBorderBox() const - { - return tag_ == kBorderBox; - } - inline bool isPaddingBox() const - { - return tag_ == kPaddingBox; - } - inline bool isContentBox() const - { - return tag_ == kContentBox; - } - inline bool isText() const - { - return tag_ == kText; - } + public: + std::string toCss() const override + { + switch (tag_) + { + case kBorderBox: + return "border-box"; + case kPaddingBox: + return "padding-box"; + case kContentBox: + return "content-box"; + case kText: + return "text"; + } + return ""; + } - protected: - Tag tag_; - }; + inline bool isBorderBox() const + { + return tag_ == kBorderBox; + } + inline bool isPaddingBox() const + { + return tag_ == kPaddingBox; + } + inline bool isContentBox() const + { + return tag_ == kContentBox; + } + inline bool isText() const + { + return tag_ == kText; + } - template - class GenericBackgroundOrigin : public ToCss - { - protected: - enum Tag : uint8_t - { - kPaddingBox = 0, - kBorderBox, - kContentBox, + protected: + Tag tag_; }; - public: - static T PaddingBox() - { - return T(kPaddingBox); - } - static T BorderBox() - { - return T(kBorderBox); - } - static T ContentBox() + template + class GenericBackgroundOrigin : public ToCss { - return T(kContentBox); - } + protected: + enum Tag : uint8_t + { + kPaddingBox = 0, + kBorderBox, + kContentBox, + }; - public: - GenericBackgroundOrigin() - : tag_(kPaddingBox) - { - } + public: + static T PaddingBox() + { + return T(kPaddingBox); + } + static T BorderBox() + { + return T(kBorderBox); + } + static T ContentBox() + { + return T(kContentBox); + } - protected: - GenericBackgroundOrigin(Tag tag) - : tag_(tag) - { - } + public: + GenericBackgroundOrigin() + : tag_(kPaddingBox) + { + } - public: - std::string toCss() const override - { - switch (tag_) + protected: + GenericBackgroundOrigin(Tag tag) + : tag_(tag) { - case kPaddingBox: - return "padding-box"; - case kBorderBox: - return "border-box"; - case kContentBox: - return "content-box"; } - return ""; - } - inline bool isPaddingBox() const - { - return tag_ == kPaddingBox; - } - inline bool isBorderBox() const - { - return tag_ == kBorderBox; - } - inline bool isContentBox() const - { - return tag_ == kContentBox; - } + public: + std::string toCss() const override + { + switch (tag_) + { + case kPaddingBox: + return "padding-box"; + case kBorderBox: + return "border-box"; + case kContentBox: + return "content-box"; + } + return ""; + } - protected: - Tag tag_; - }; + inline bool isPaddingBox() const + { + return tag_ == kPaddingBox; + } + inline bool isBorderBox() const + { + return tag_ == kBorderBox; + } + inline bool isContentBox() const + { + return tag_ == kContentBox; + } - template - class GenericBackgroundRepeat : public ToCss - { - protected: - enum Tag : uint8_t - { - kRepeat = 0, - kRepeatX, - kRepeatY, - kNoRepeat, - kSpace, - kRound, + protected: + Tag tag_; }; - public: - static T Repeat() - { - return T(kRepeat); - } - static T RepeatX() - { - return T(kRepeatX); - } - static T RepeatY() + template + class GenericBackgroundRepeat : public ToCss { - return T(kRepeatY); - } - static T NoRepeat() - { - return T(kNoRepeat); - } - static T Space() - { - return T(kSpace); - } - static T Round() - { - return T(kRound); - } - - public: - GenericBackgroundRepeat() - : tag_(kRepeat) - { - } + protected: + enum Tag : uint8_t + { + kRepeat = 0, + kRepeatX, + kRepeatY, + kNoRepeat, + kSpace, + kRound, + }; + + public: + static T Repeat() + { + return T(kRepeat); + } + static T RepeatX() + { + return T(kRepeatX); + } + static T RepeatY() + { + return T(kRepeatY); + } + static T NoRepeat() + { + return T(kNoRepeat); + } + static T Space() + { + return T(kSpace); + } + static T Round() + { + return T(kRound); + } - protected: - GenericBackgroundRepeat(Tag tag) - : tag_(tag) - { - } + public: + GenericBackgroundRepeat() + : tag_(kRepeat) + { + } - public: - std::string toCss() const override - { - switch (tag_) - { - case kRepeat: - return "repeat"; - case kRepeatX: - return "repeat-x"; - case kRepeatY: - return "repeat-y"; - case kNoRepeat: - return "no-repeat"; - case kSpace: - return "space"; - case kRound: - return "round"; - } - return ""; - } + protected: + GenericBackgroundRepeat(Tag tag) + : tag_(tag) + { + } - inline bool isRepeat() const - { - return tag_ == kRepeat; - } - inline bool isRepeatX() const - { - return tag_ == kRepeatX; - } - inline bool isRepeatY() const - { - return tag_ == kRepeatY; - } - inline bool isNoRepeat() const - { - return tag_ == kNoRepeat; - } - inline bool isSpace() const - { - return tag_ == kSpace; - } - inline bool isRound() const - { - return tag_ == kRound; - } + public: + std::string toCss() const override + { + switch (tag_) + { + case kRepeat: + return "repeat"; + case kRepeatX: + return "repeat-x"; + case kRepeatY: + return "repeat-y"; + case kNoRepeat: + return "no-repeat"; + case kSpace: + return "space"; + case kRound: + return "round"; + } + return ""; + } - protected: - Tag tag_; - }; + inline bool isRepeat() const + { + return tag_ == kRepeat; + } + inline bool isRepeatX() const + { + return tag_ == kRepeatX; + } + inline bool isRepeatY() const + { + return tag_ == kRepeatY; + } + inline bool isNoRepeat() const + { + return tag_ == kNoRepeat; + } + inline bool isSpace() const + { + return tag_ == kSpace; + } + inline bool isRound() const + { + return tag_ == kRound; + } - template - class GenericBackgroundSize : public ToCss - { - protected: - enum Tag : uint8_t - { - kAuto = 0, - kLength, - kPercentage, - kCover, - kContain, - kTwoValues, + protected: + Tag tag_; }; - public: - static T Auto() - { - return T(kAuto); - } - static T Length(float value) + template + class GenericBackgroundSize : public ToCss { - return T(kLength, value); - } - static T Percentage(float value) - { - return T(kPercentage, value); - } - static T Cover() - { - return T(kCover); - } - static T Contain() - { - return T(kContain); - } - static T TwoValues(float width, float height) - { - return T(kTwoValues, width, height); - } - - public: - GenericBackgroundSize() - : tag_(kAuto) - , width_(0.0f) - , height_(0.0f) - { - } - - protected: - GenericBackgroundSize(Tag tag, float width = 0.0f, float height = 0.0f) - : tag_(tag) - , width_(width) - , height_(height) - { - } + protected: + enum Tag : uint8_t + { + kAuto = 0, + kLength, + kPercentage, + kCover, + kContain, + kTwoValues, + }; + + public: + static T Auto() + { + return T(kAuto); + } + static T Length(float value) + { + return T(kLength, value); + } + static T Percentage(float value) + { + return T(kPercentage, value); + } + static T Cover() + { + return T(kCover); + } + static T Contain() + { + return T(kContain); + } + static T TwoValues(float width, float height) + { + return T(kTwoValues, width, height); + } - public: - std::string toCss() const override - { - switch (tag_) - { - case kAuto: - return "auto"; - case kLength: - return std::to_string(width_) + "px"; - case kPercentage: - return std::to_string(width_) + "%"; - case kCover: - return "cover"; - case kContain: - return "contain"; - case kTwoValues: - return std::to_string(width_) + "px " + std::to_string(height_) + "px"; - } - return ""; - } + public: + GenericBackgroundSize() + : tag_(kAuto) + , width_(0.0f) + , height_(0.0f) + { + } - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline bool isLength() const - { - return tag_ == kLength; - } - inline bool isPercentage() const - { - return tag_ == kPercentage; - } - inline bool isCover() const - { - return tag_ == kCover; - } - inline bool isContain() const - { - return tag_ == kContain; - } - inline bool isTwoValues() const - { - return tag_ == kTwoValues; - } + protected: + GenericBackgroundSize(Tag tag, float width = 0.0f, float height = 0.0f) + : tag_(tag) + , width_(width) + , height_(height) + { + } - inline float getWidth() const - { - return width_; - } - inline float getHeight() const - { - return height_; - } + public: + std::string toCss() const override + { + switch (tag_) + { + case kAuto: + return "auto"; + case kLength: + return std::to_string(width_) + "px"; + case kPercentage: + return std::to_string(width_) + "%"; + case kCover: + return "cover"; + case kContain: + return "contain"; + case kTwoValues: + return std::to_string(width_) + "px " + std::to_string(height_) + "px"; + } + return ""; + } - protected: - Tag tag_; - float width_; - float height_; - }; + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline bool isLength() const + { + return tag_ == kLength; + } + inline bool isPercentage() const + { + return tag_ == kPercentage; + } + inline bool isCover() const + { + return tag_ == kCover; + } + inline bool isContain() const + { + return tag_ == kContain; + } + inline bool isTwoValues() const + { + return tag_ == kTwoValues; + } - enum class BackgroundPositionKeyword - { - kNone = 0, - kCenterKeyword, - kLeftKeyword, - kRightKeyword, - kTopKeyword, - kBottomKeyword, - }; - - inline std::string to_string(const BackgroundPositionKeyword &keyword) - { - switch (keyword) - { - case BackgroundPositionKeyword::kCenterKeyword: - return "center"; - case BackgroundPositionKeyword::kLeftKeyword: - return "left"; - case BackgroundPositionKeyword::kRightKeyword: - return "right"; - case BackgroundPositionKeyword::kTopKeyword: - return "top"; - case BackgroundPositionKeyword::kBottomKeyword: - return "bottom"; - case BackgroundPositionKeyword::kNone: - default: - return ""; - } - } + inline float getWidth() const + { + return width_; + } + inline float getHeight() const + { + return height_; + } - template - class GenericBackgroundPosition : public ToCss - { - protected: - enum Tag : uint8_t - { - kCenter = 0, - kLeft, - kRight, - kTop, - kBottom, - kLength, - kPercentage, - kTwoValues, - kThreeValues, - kFourValues, + protected: + Tag tag_; + float width_; + float height_; }; - public: - static T Default() - { - return T(kTwoValues, 0, 0); - } - static T Center() - { - return T(kCenter); - } - static T Left() - { - return T(kLeft); - } - static T Right() - { - return T(kRight); - } - static T Top() - { - return T(kTop); - } - static T Bottom() - { - return T(kBottom); - } - static T Length(float value) - { - return T(kLength, value); - } - static T Percentage(float value) - { - return T(kPercentage, value); - } - static T TwoValues(float x, float y) - { - return T(kTwoValues, x, y); - } - static T ThreeValues(BackgroundPositionKeyword hKeyword, - float hOffset, - BackgroundPositionKeyword vKeyword) - { - return T(kThreeValues, 0.0f, 0.0f, hKeyword, hOffset, vKeyword, 0.0f); - } - static T FourValues(BackgroundPositionKeyword hKeyword, - float hOffset, - BackgroundPositionKeyword vKeyword, - float vOffset) - { - return T(kFourValues, 0.0f, 0.0f, hKeyword, hOffset, vKeyword, vOffset); - } - - public: - GenericBackgroundPosition() - : tag_(kCenter) - , x_(0.0f) - , y_(0.0f) - , h_keyword_(BackgroundPositionKeyword::kNone) - , h_offset_(0.0f) - , v_keyword_(BackgroundPositionKeyword::kNone) - , v_offset_(0.0f) + enum class BackgroundPositionKeyword { - } - - protected: - GenericBackgroundPosition(Tag tag, - float x = 0.0f, - float y = 0.0f, - BackgroundPositionKeyword h_keyword = BackgroundPositionKeyword::kNone, - float h_offset = 0.0f, - BackgroundPositionKeyword v_keyword = BackgroundPositionKeyword::kNone, - float v_offset = 0.0f) - : tag_(tag) - , x_(x) - , y_(y) - , h_keyword_(h_keyword) - , h_offset_(h_offset) - , v_keyword_(v_keyword) - , v_offset_(v_offset) - { - } + kNone = 0, + kCenterKeyword, + kLeftKeyword, + kRightKeyword, + kTopKeyword, + kBottomKeyword, + }; - public: - std::string toCss() const override + inline std::string to_string(const BackgroundPositionKeyword &keyword) { - switch (tag_) + switch (keyword) { - case kCenter: + case BackgroundPositionKeyword::kCenterKeyword: return "center"; - case kLeft: + case BackgroundPositionKeyword::kLeftKeyword: return "left"; - case kRight: + case BackgroundPositionKeyword::kRightKeyword: return "right"; - case kTop: + case BackgroundPositionKeyword::kTopKeyword: return "top"; - case kBottom: + case BackgroundPositionKeyword::kBottomKeyword: return "bottom"; - case kLength: - return std::to_string(x_) + "px"; - case kPercentage: - return std::to_string(x_) + "%"; - case kTwoValues: - return std::to_string(x_) + "px " + std::to_string(y_) + "px"; - case kThreeValues: - return to_string(h_keyword_) + " " + std::to_string(h_offset_) + "px " + to_string(v_keyword_); - case kFourValues: - return to_string(h_keyword_) + " " + std::to_string(h_offset_) + "px " + - to_string(v_keyword_) + " " + std::to_string(v_offset_) + "px"; - } - return ""; + case BackgroundPositionKeyword::kNone: + default: + return ""; + } } - inline bool isCenter() const - { - return tag_ == kCenter; - } - inline bool isLeft() const - { - return tag_ == kLeft; - } - inline bool isRight() const - { - return tag_ == kRight; - } - inline bool isTop() const - { - return tag_ == kTop; - } - inline bool isBottom() const - { - return tag_ == kBottom; - } - inline bool isLength() const - { - return tag_ == kLength; - } - inline bool isPercentage() const - { - return tag_ == kPercentage; - } - inline bool isTwoValues() const - { - return tag_ == kTwoValues; - } - inline bool isThreeValues() const - { - return tag_ == kThreeValues; - } - inline bool isFourValues() const + template + class GenericBackgroundPosition : public ToCss { - return tag_ == kFourValues; - } + protected: + enum Tag : uint8_t + { + kCenter = 0, + kLeft, + kRight, + kTop, + kBottom, + kLength, + kPercentage, + kTwoValues, + kThreeValues, + kFourValues, + }; + + public: + static T Default() + { + return T(kTwoValues, 0, 0); + } + static T Center() + { + return T(kCenter); + } + static T Left() + { + return T(kLeft); + } + static T Right() + { + return T(kRight); + } + static T Top() + { + return T(kTop); + } + static T Bottom() + { + return T(kBottom); + } + static T Length(float value) + { + return T(kLength, value); + } + static T Percentage(float value) + { + return T(kPercentage, value); + } + static T TwoValues(float x, float y) + { + return T(kTwoValues, x, y); + } + static T ThreeValues(BackgroundPositionKeyword hKeyword, + float hOffset, + BackgroundPositionKeyword vKeyword) + { + return T(kThreeValues, 0.0f, 0.0f, hKeyword, hOffset, vKeyword, 0.0f); + } + static T FourValues(BackgroundPositionKeyword hKeyword, + float hOffset, + BackgroundPositionKeyword vKeyword, + float vOffset) + { + return T(kFourValues, 0.0f, 0.0f, hKeyword, hOffset, vKeyword, vOffset); + } - inline float getX() const - { - return x_; - } - inline float getY() const - { - return y_; - } - inline BackgroundPositionKeyword getHorizontalKeyword() const - { - return h_keyword_; - } - inline float getHorizontalOffset() const - { - return h_offset_; - } - inline BackgroundPositionKeyword getVerticalKeyword() const - { - return v_keyword_; - } - inline float getVerticalOffset() const - { - return v_offset_; - } + public: + GenericBackgroundPosition() + : tag_(kCenter) + , x_(0.0f) + , y_(0.0f) + , h_keyword_(BackgroundPositionKeyword::kNone) + , h_offset_(0.0f) + , v_keyword_(BackgroundPositionKeyword::kNone) + , v_offset_(0.0f) + { + } + + protected: + GenericBackgroundPosition(Tag tag, + float x = 0.0f, + float y = 0.0f, + BackgroundPositionKeyword h_keyword = BackgroundPositionKeyword::kNone, + float h_offset = 0.0f, + BackgroundPositionKeyword v_keyword = BackgroundPositionKeyword::kNone, + float v_offset = 0.0f) + : tag_(tag) + , x_(x) + , y_(y) + , h_keyword_(h_keyword) + , h_offset_(h_offset) + , v_keyword_(v_keyword) + , v_offset_(v_offset) + { + } + + public: + std::string toCss() const override + { + switch (tag_) + { + case kCenter: + return "center"; + case kLeft: + return "left"; + case kRight: + return "right"; + case kTop: + return "top"; + case kBottom: + return "bottom"; + case kLength: + return std::to_string(x_) + "px"; + case kPercentage: + return std::to_string(x_) + "%"; + case kTwoValues: + return std::to_string(x_) + "px " + std::to_string(y_) + "px"; + case kThreeValues: + return to_string(h_keyword_) + " " + std::to_string(h_offset_) + "px " + to_string(v_keyword_); + case kFourValues: + return to_string(h_keyword_) + " " + std::to_string(h_offset_) + "px " + + to_string(v_keyword_) + " " + std::to_string(v_offset_) + "px"; + } + return ""; + } + + inline bool isCenter() const + { + return tag_ == kCenter; + } + inline bool isLeft() const + { + return tag_ == kLeft; + } + inline bool isRight() const + { + return tag_ == kRight; + } + inline bool isTop() const + { + return tag_ == kTop; + } + inline bool isBottom() const + { + return tag_ == kBottom; + } + inline bool isLength() const + { + return tag_ == kLength; + } + inline bool isPercentage() const + { + return tag_ == kPercentage; + } + inline bool isTwoValues() const + { + return tag_ == kTwoValues; + } + inline bool isThreeValues() const + { + return tag_ == kThreeValues; + } + inline bool isFourValues() const + { + return tag_ == kFourValues; + } - protected: - Tag tag_; - float x_; - float y_; - float h_offset_; - float v_offset_; - BackgroundPositionKeyword h_keyword_; - BackgroundPositionKeyword v_keyword_; - }; -} + inline float getX() const + { + return x_; + } + inline float getY() const + { + return y_; + } + inline BackgroundPositionKeyword getHorizontalKeyword() const + { + return h_keyword_; + } + inline float getHorizontalOffset() const + { + return h_offset_; + } + inline BackgroundPositionKeyword getVerticalKeyword() const + { + return v_keyword_; + } + inline float getVerticalOffset() const + { + return v_offset_; + } + + protected: + Tag tag_; + float x_; + float y_; + float h_offset_; + float v_offset_; + BackgroundPositionKeyword h_keyword_; + BackgroundPositionKeyword v_keyword_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/border.hpp b/src/client/cssom/values/generics/border.hpp index 679e460a0..523c61cf2 100644 --- a/src/client/cssom/values/generics/border.hpp +++ b/src/client/cssom/values/generics/border.hpp @@ -3,271 +3,274 @@ #include #include "./rect.hpp" -namespace client_cssom::values::generics +namespace endor { - using BorderEdge = Edge; - - enum class BorderCorner + namespace client_cssom::values::generics { - kTopLeft, - kTopRight, - kBottomRight, - kBottomLeft - }; + using BorderEdge = Edge; - inline std::string to_string(const BorderCorner &corner) - { - switch (corner) + enum class BorderCorner { - case BorderCorner::kTopLeft: - return "border-top-left-radius"; - case BorderCorner::kTopRight: - return "border-top-right-radius"; - case BorderCorner::kBottomRight: - return "border-bottom-right-radius"; - case BorderCorner::kBottomLeft: - return "border-bottom-left-radius"; - default: - return ""; - } - } - - template - class GenericBorderStyle : public ToCss - { - protected: - enum Tag - { - kHidden, - kNone, - kInset, - kGroove, - kOutset, - kRidge, - kDotted, - kDashed, - kSolid, - kDouble, + kTopLeft, + kTopRight, + kBottomRight, + kBottomLeft }; - public: - static T Hidden() - { - return T(kHidden); - } - static T None() - { - return T(kNone); - } - static T Inset() - { - return T(kInset); - } - static T Groove() - { - return T(kGroove); - } - static T Outset() - { - return T(kOutset); - } - static T Ridge() - { - return T(kRidge); - } - static T Dotted() - { - return T(kDotted); - } - static T Dashed() + inline std::string to_string(const BorderCorner &corner) { - return T(kDashed); - } - static T Solid() - { - return T(kSolid); - } - static T Double() - { - return T(kDouble); + switch (corner) + { + case BorderCorner::kTopLeft: + return "border-top-left-radius"; + case BorderCorner::kTopRight: + return "border-top-right-radius"; + case BorderCorner::kBottomRight: + return "border-bottom-right-radius"; + case BorderCorner::kBottomLeft: + return "border-bottom-left-radius"; + default: + return ""; + } } - public: - GenericBorderStyle() - : tag_(kNone) + template + class GenericBorderStyle : public ToCss { - } + protected: + enum Tag + { + kHidden, + kNone, + kInset, + kGroove, + kOutset, + kRidge, + kDotted, + kDashed, + kSolid, + kDouble, + }; - protected: - GenericBorderStyle(Tag tag) - : tag_(tag) - { - } + public: + static T Hidden() + { + return T(kHidden); + } + static T None() + { + return T(kNone); + } + static T Inset() + { + return T(kInset); + } + static T Groove() + { + return T(kGroove); + } + static T Outset() + { + return T(kOutset); + } + static T Ridge() + { + return T(kRidge); + } + static T Dotted() + { + return T(kDotted); + } + static T Dashed() + { + return T(kDashed); + } + static T Solid() + { + return T(kSolid); + } + static T Double() + { + return T(kDouble); + } - public: - inline bool isNoneOrHidden() const - { - return tag_ == kNone || tag_ == kHidden; - } - inline bool isInset() const - { - return tag_ == kInset; - } - inline bool isGroove() const - { - return tag_ == kGroove; - } - inline bool isOutset() const - { - return tag_ == kOutset; - } - inline bool isRidge() const - { - return tag_ == kRidge; - } - inline bool isDotted() const - { - return tag_ == kDotted; - } - inline bool isDashed() const - { - return tag_ == kDashed; - } - inline bool isSolid() const - { - return tag_ == kSolid; - } - inline bool isDouble() const - { - return tag_ == kDouble; - } + public: + GenericBorderStyle() + : tag_(kNone) + { + } - std::string toCss() const override - { - switch (tag_) - { - case kHidden: - return "hidden"; - case kNone: - return "none"; - case kInset: - return "inset"; - case kGroove: - return "groove"; - case kOutset: - return "outset"; - case kRidge: - return "ridge"; - case kDotted: - return "dotted"; - case kDashed: - return "dashed"; - case kSolid: - return "solid"; - case kDouble: - return "double"; - default: - assert(false && "Invalid border style"); - return ""; + protected: + GenericBorderStyle(Tag tag) + : tag_(tag) + { } - } - protected: - Tag tag_; - }; + public: + inline bool isNoneOrHidden() const + { + return tag_ == kNone || tag_ == kHidden; + } + inline bool isInset() const + { + return tag_ == kInset; + } + inline bool isGroove() const + { + return tag_ == kGroove; + } + inline bool isOutset() const + { + return tag_ == kOutset; + } + inline bool isRidge() const + { + return tag_ == kRidge; + } + inline bool isDotted() const + { + return tag_ == kDotted; + } + inline bool isDashed() const + { + return tag_ == kDashed; + } + inline bool isSolid() const + { + return tag_ == kSolid; + } + inline bool isDouble() const + { + return tag_ == kDouble; + } - template - class GenericBorderCornerRadius - { - public: - GenericBorderCornerRadius() = default; - GenericBorderCornerRadius(const L &value) - : value_(value) - { - } + std::string toCss() const override + { + switch (tag_) + { + case kHidden: + return "hidden"; + case kNone: + return "none"; + case kInset: + return "inset"; + case kGroove: + return "groove"; + case kOutset: + return "outset"; + case kRidge: + return "ridge"; + case kDotted: + return "dotted"; + case kDashed: + return "dashed"; + case kSolid: + return "solid"; + case kDouble: + return "double"; + default: + assert(false && "Invalid border style"); + return ""; + } + } + + protected: + Tag tag_; + }; - public: - const L &lengthPercentage() const + template + class GenericBorderCornerRadius { - return value_; - } + public: + GenericBorderCornerRadius() = default; + GenericBorderCornerRadius(const L &value) + : value_(value) + { + } - protected: - L value_; - }; + public: + const L &lengthPercentage() const + { + return value_; + } - template - class GenericBorderRadius - { - public: - GenericBorderRadius() = default; - GenericBorderRadius(const C &top_left, - const C &top_right, - const C &bottom_right, - const C &bottom_left) - : top_left_(top_left) - , top_right_(top_right) - , bottom_right_(bottom_right) - , bottom_left_(bottom_left) - { - } + protected: + L value_; + }; - public: - const C &topLeft() const - { - return top_left_; - } - C &topLeft() - { - return top_left_; - } - const C &topRight() const - { - return top_right_; - } - C &topRight() - { - return top_right_; - } - const C &bottomLeft() const - { - return bottom_left_; - } - C &bottomLeft() - { - return bottom_left_; - } - const C &bottomRight() const - { - return bottom_right_; - } - C &bottomRight() - { - return bottom_right_; - } + template + class GenericBorderRadius + { + public: + GenericBorderRadius() = default; + GenericBorderRadius(const C &top_left, + const C &top_right, + const C &bottom_right, + const C &bottom_left) + : top_left_(top_left) + , top_right_(top_right) + , bottom_right_(bottom_right) + , bottom_left_(bottom_left) + { + } - // Support for accessing corners using BorderCorner enum - const C &operator[](const BorderCorner &corner) const - { - switch (corner) + public: + const C &topLeft() const { - case BorderCorner::kTopLeft: return top_left_; - case BorderCorner::kTopRight: + } + C &topLeft() + { + return top_left_; + } + const C &topRight() const + { return top_right_; - case BorderCorner::kBottomLeft: + } + C &topRight() + { + return top_right_; + } + const C &bottomLeft() const + { return bottom_left_; - case BorderCorner::kBottomRight: + } + C &bottomLeft() + { + return bottom_left_; + } + const C &bottomRight() const + { + return bottom_right_; + } + C &bottomRight() + { return bottom_right_; - default: - throw std::out_of_range("Invalid border corner"); } - } - protected: - C top_left_; - C top_right_; - C bottom_left_; - C bottom_right_; - }; -} + // Support for accessing corners using BorderCorner enum + const C &operator[](const BorderCorner &corner) const + { + switch (corner) + { + case BorderCorner::kTopLeft: + return top_left_; + case BorderCorner::kTopRight: + return top_right_; + case BorderCorner::kBottomLeft: + return bottom_left_; + case BorderCorner::kBottomRight: + return bottom_right_; + default: + throw std::out_of_range("Invalid border corner"); + } + } + + protected: + C top_left_; + C top_right_; + C bottom_left_; + C bottom_right_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/calc.hpp b/src/client/cssom/values/generics/calc.hpp index a8a20fcfb..d5f32c163 100644 --- a/src/client/cssom/values/generics/calc.hpp +++ b/src/client/cssom/values/generics/calc.hpp @@ -2,49 +2,52 @@ #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericCalcNode + namespace client_cssom::values::generics { - private: - enum Tag + template + class GenericCalcNode { - kLeaf, - kNegate, - kInvert, - kSum, - kProduct, - kMinMax, - kClamp, - kRound, - kModRem, - kHypot, - kAbs, - kSign, - kAnchor, - kAnchorSize - }; + private: + enum Tag + { + kLeaf, + kNegate, + kInvert, + kSum, + kProduct, + kMinMax, + kClamp, + kRound, + kModRem, + kHypot, + kAbs, + kSign, + kAnchor, + kAnchorSize + }; - struct LeafVariant - { - L leaf; - }; - struct NegateVariant - { - std::unique_ptr> node; - }; - struct InvertVariant - { - std::unique_ptr> node; - }; + struct LeafVariant + { + L leaf; + }; + struct NegateVariant + { + std::unique_ptr> node; + }; + struct InvertVariant + { + std::unique_ptr> node; + }; - using NodeVariant = std::variant; + using NodeVariant = std::variant; - private: - Tag tag_; - NodeVariant node_; - }; -} + private: + Tag tag_; + NodeVariant node_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/color.hpp b/src/client/cssom/values/generics/color.hpp index b82cb4dc1..915632bea 100644 --- a/src/client/cssom/values/generics/color.hpp +++ b/src/client/cssom/values/generics/color.hpp @@ -6,153 +6,156 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericColor : public ToCss + namespace client_cssom::values::generics { - private: - enum Tag : uint8_t + template + class GenericColor : public ToCss { - kAbsolute, - kColorFunction, - kCurrentColor, - // TODO: Color mix - }; + private: + enum Tag : uint8_t + { + kAbsolute, + kColorFunction, + kCurrentColor, + // TODO: Color mix + }; - struct AbsoluteColorVariant - { - Tag tag; - glm::u32vec4 rgba; + struct AbsoluteColorVariant + { + Tag tag; + glm::u32vec4 rgba; + + static AbsoluteColorVariant From(const glm::vec4 &rgba) + { + return AbsoluteColorVariant{kAbsolute, rgba}; + } + static AbsoluteColorVariant From(const SkColor color) + { + return AbsoluteColorVariant{kAbsolute, glm::u32vec4(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color), SkColorGetA(color))}; + } + + inline uint32_t r() const + { + return rgba.r; + } + inline uint32_t g() const + { + return rgba.g; + } + inline uint32_t b() const + { + return rgba.b; + } + inline uint32_t a() const + { + return rgba.a; + } + }; - static AbsoluteColorVariant From(const glm::vec4 &rgba) + struct ColorFunctionVariant { - return AbsoluteColorVariant{kAbsolute, rgba}; + Tag tag; + void *color_function_ptr; + }; + + struct CurrentColorVariant + { + Tag tag; + }; + + using Variant = std::variant; + + public: + // Returns an absolute color from the given RGBA values. + static inline T Absolute(const glm::vec4 &rgba) + { + return T(AbsoluteColorVariant::From(rgba)); } - static AbsoluteColorVariant From(const SkColor color) + // Returns an absolute color from the given RGBA values. + static inline T Absolute(float r, float g, float b, float a) { - return AbsoluteColorVariant{kAbsolute, glm::u32vec4(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color), SkColorGetA(color))}; + return T(Absolute(glm::vec4(r, g, b, a))); + } + // Returns a color that represents current color. + static inline T CurrentColor() + { + return T(CurrentColorVariant{Tag::kCurrentColor}); } - inline uint32_t r() const + protected: + GenericColor(const SkColor color = SK_ColorTRANSPARENT) + : variant_(AbsoluteColorVariant::From(color)) { - return rgba.r; } - inline uint32_t g() const + GenericColor(const AbsoluteColorVariant &color) + : variant_(color) { - return rgba.g; } - inline uint32_t b() const + GenericColor(const ColorFunctionVariant &color_function) + : variant_(color_function) { - return rgba.b; } - inline uint32_t a() const + GenericColor(const CurrentColorVariant ¤t_color) + : variant_(current_color) { - return rgba.a; } - }; - - struct ColorFunctionVariant - { - Tag tag; - void *color_function_ptr; - }; - struct CurrentColorVariant - { - Tag tag; - }; - - using Variant = std::variant; - - public: - // Returns an absolute color from the given RGBA values. - static inline T Absolute(const glm::vec4 &rgba) - { - return T(AbsoluteColorVariant::From(rgba)); - } - // Returns an absolute color from the given RGBA values. - static inline T Absolute(float r, float g, float b, float a) - { - return T(Absolute(glm::vec4(r, g, b, a))); - } - // Returns a color that represents current color. - static inline T CurrentColor() - { - return T(CurrentColorVariant{Tag::kCurrentColor}); - } - - protected: - GenericColor(const SkColor color = SK_ColorTRANSPARENT) - : variant_(AbsoluteColorVariant::From(color)) - { - } - GenericColor(const AbsoluteColorVariant &color) - : variant_(color) - { - } - GenericColor(const ColorFunctionVariant &color_function) - : variant_(color_function) - { - } - GenericColor(const CurrentColorVariant ¤t_color) - : variant_(current_color) - { - } - - public: - inline bool isAbsolute() const - { - return std::holds_alternative(variant_); - } - inline bool isColorFunction() const - { - return std::holds_alternative(variant_); - } - inline bool isCurrentColor() const - { - return std::holds_alternative(variant_); - } - - inline SkColor getAbsoluteColor() const - { - if (isAbsolute()) + public: + inline bool isAbsolute() const { - const auto &color = std::get(variant_); - return SkColorSetARGB(color.a(), color.r(), color.g(), color.b()); + return std::holds_alternative(variant_); } - return SK_ColorTRANSPARENT; // Default to transparent for unknown - } - - std::string toCss() const override - { - std::stringstream ss; - if (isAbsolute()) + inline bool isColorFunction() const { - const auto &color = std::get(variant_); - ss << "rgba(" << color.r() << ", " << color.g() << ", " << color.b() << ", " << color.a() << ")"; + return std::holds_alternative(variant_); } - else if (isCurrentColor()) + inline bool isCurrentColor() const { - ss << "currentColor"; + return std::holds_alternative(variant_); } - else + + inline SkColor getAbsoluteColor() const { - ss << "unknown"; + if (isAbsolute()) + { + const auto &color = std::get(variant_); + return SkColorSetARGB(color.a(), color.r(), color.g(), color.b()); + } + return SK_ColorTRANSPARENT; // Default to transparent for unknown } - return ss.str(); - } - // Output to ostream - friend std::ostream &operator<<(std::ostream &os, const GenericColor &color) - { - os << color.toCss(); - return os; - } + std::string toCss() const override + { + std::stringstream ss; + if (isAbsolute()) + { + const auto &color = std::get(variant_); + ss << "rgba(" << color.r() << ", " << color.g() << ", " << color.b() << ", " << color.a() << ")"; + } + else if (isCurrentColor()) + { + ss << "currentColor"; + } + else + { + ss << "unknown"; + } + return ss.str(); + } - private: - Variant variant_; - }; -} + // Output to ostream + friend std::ostream &operator<<(std::ostream &os, const GenericColor &color) + { + os << color.toCss(); + return os; + } + + private: + Variant variant_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/common.hpp b/src/client/cssom/values/generics/common.hpp index 2ef67ef97..2422f9ead 100644 --- a/src/client/cssom/values/generics/common.hpp +++ b/src/client/cssom/values/generics/common.hpp @@ -1,17 +1,20 @@ #pragma once -namespace client_cssom::values::generics +namespace endor { - template - class NonNegative : public T + namespace client_cssom::values::generics { - using T::T; - - public: - NonNegative(T value) - : T(value) + template + class NonNegative : public T { - // TODO(yorkie): set the value to zero if the value is negative. - } - }; -} + using T::T; + + public: + NonNegative(T value) + : T(value) + { + // TODO(yorkie): set the value to zero if the value is negative. + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/easing.hpp b/src/client/cssom/values/generics/easing.hpp index ade6f1a27..6ffa29abe 100644 --- a/src/client/cssom/values/generics/easing.hpp +++ b/src/client/cssom/values/generics/easing.hpp @@ -5,120 +5,123 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericTimingFunction : public ToCss + namespace client_cssom::values::generics { - protected: - enum Tag : uint8_t + template + class GenericTimingFunction : public ToCss { - kKeyword, - kCubicBezier, - // TODO(yorkie): Implement steps() and other timing functions. - }; - enum TimingKeyword : uint8_t - { - kLinear, - kEase, - kEaseIn, - kEaseOut, - kEaseInOut, - }; - struct CubicBezierDescriptor - { - float x1, y1, x2, y2; - CubicBezierDescriptor(float x1, float y1, float x2, float y2) - : x1(x1) - , y1(y1) - , x2(x2) - , y2(y2) + protected: + enum Tag : uint8_t { - } - }; - using TimingFunctionVariant = std::variant; + kKeyword, + kCubicBezier, + // TODO(yorkie): Implement steps() and other timing functions. + }; + enum TimingKeyword : uint8_t + { + kLinear, + kEase, + kEaseIn, + kEaseOut, + kEaseInOut, + }; + struct CubicBezierDescriptor + { + float x1, y1, x2, y2; + CubicBezierDescriptor(float x1, float y1, float x2, float y2) + : x1(x1) + , y1(y1) + , x2(x2) + , y2(y2) + { + } + }; + using TimingFunctionVariant = std::variant; - public: - static T Linear() - { - return T(kLinear); - } - static T Ease() - { - return T(kEase); - } - static T EaseIn() - { - return T(kEaseIn); - } - static T EaseOut() - { - return T(kEaseOut); - } - static T EaseInOut() - { - return T(kEaseInOut); - } - static T CubicBezier(float x1, float y1, float x2, float y2) - { - return T(x1, y1, x2, y2); - } + public: + static T Linear() + { + return T(kLinear); + } + static T Ease() + { + return T(kEase); + } + static T EaseIn() + { + return T(kEaseIn); + } + static T EaseOut() + { + return T(kEaseOut); + } + static T EaseInOut() + { + return T(kEaseInOut); + } + static T CubicBezier(float x1, float y1, float x2, float y2) + { + return T(x1, y1, x2, y2); + } - protected: - GenericTimingFunction() - : tag_(kKeyword) - , timing_function_(kLinear) - { - } - GenericTimingFunction(TimingKeyword keyword) - : tag_(kKeyword) - , timing_function_(keyword) - { - } - GenericTimingFunction(float x1, float y1, float x2, float y2) - : tag_(kCubicBezier) - , timing_function_(CubicBezierDescriptor(x1, y1, x2, y2)) - { - } - // TODO(yorkie): Implement steps() and other timing functions. + protected: + GenericTimingFunction() + : tag_(kKeyword) + , timing_function_(kLinear) + { + } + GenericTimingFunction(TimingKeyword keyword) + : tag_(kKeyword) + , timing_function_(keyword) + { + } + GenericTimingFunction(float x1, float y1, float x2, float y2) + : tag_(kCubicBezier) + , timing_function_(CubicBezierDescriptor(x1, y1, x2, y2)) + { + } + // TODO(yorkie): Implement steps() and other timing functions. - public: - const TimingFunctionVariant &timing_function() const - { - return timing_function_; - } - std::string toCss() const override - { - switch (tag_) + public: + const TimingFunctionVariant &timing_function() const + { + return timing_function_; + } + std::string toCss() const override { - case kKeyword: - switch (std::get(timing_function_)) + switch (tag_) { - case kLinear: - return "linear"; - case kEase: - return "ease"; - case kEaseIn: - return "ease-in"; - case kEaseOut: - return "ease-out"; - case kEaseInOut: - return "ease-in-out"; + case kKeyword: + switch (std::get(timing_function_)) + { + case kLinear: + return "linear"; + case kEase: + return "ease"; + case kEaseIn: + return "ease-in"; + case kEaseOut: + return "ease-out"; + case kEaseInOut: + return "ease-in-out"; + } + break; + case kCubicBezier: + { + const auto &cubic = std::get(timing_function_); + return "cubic-bezier(" + std::to_string(cubic.x1) + ", " + std::to_string(cubic.y1) + ", " + + std::to_string(cubic.x2) + ", " + std::to_string(cubic.y2) + ")"; } - break; - case kCubicBezier: - { - const auto &cubic = std::get(timing_function_); - return "cubic-bezier(" + std::to_string(cubic.x1) + ", " + std::to_string(cubic.y1) + ", " + - std::to_string(cubic.x2) + ", " + std::to_string(cubic.y2) + ")"; - } - // TODO(yorkie): Implement steps() and other timing functions. + // TODO(yorkie): Implement steps() and other timing functions. + } + return ""; } - return ""; - } - protected: - Tag tag_; - TimingFunctionVariant timing_function_; - }; -} + protected: + Tag tag_; + TimingFunctionVariant timing_function_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/filter.hpp b/src/client/cssom/values/generics/filter.hpp index 75d8637bc..a3cc9dcbb 100644 --- a/src/client/cssom/values/generics/filter.hpp +++ b/src/client/cssom/values/generics/filter.hpp @@ -7,335 +7,338 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - // Base class for filter function parameters - struct FilterFunctionValue + namespace client_cssom::values::generics { - double value = 0.0; - std::string unit; - - FilterFunctionValue() = default; - FilterFunctionValue(double v, const std::string &u = "") - : value(v) - , unit(u) - { - } - - std::string toString() const + // Base class for filter function parameters + struct FilterFunctionValue { - std::ostringstream oss; - oss << std::fixed; + double value = 0.0; + std::string unit; - if (unit.empty()) + FilterFunctionValue() = default; + FilterFunctionValue(double v, const std::string &u = "") + : value(v) + , unit(u) { - oss << std::setprecision(1) << value; } - else + + std::string toString() const { - if (unit == "%") - oss << std::setprecision(0) << (value * 100); - else if (unit == "deg" || unit == "px") - oss << std::setprecision(0) << value; + std::ostringstream oss; + oss << std::fixed; + + if (unit.empty()) + { + oss << std::setprecision(1) << value; + } else - oss << std::setprecision(2) << value; - oss << unit; + { + if (unit == "%") + oss << std::setprecision(0) << (value * 100); + else if (unit == "deg" || unit == "px") + oss << std::setprecision(0) << value; + else + oss << std::setprecision(2) << value; + oss << unit; + } + return oss.str(); } - return oss.str(); - } - }; - - template - class GenericFilterFunction : public ToCss - { - protected: - enum Tag : uint8_t - { - kNone = 0, - kBlur, - kBrightness, - kContrast, - kDropShadow, - kGrayscale, - kHueRotate, - kInvert, - kOpacity, - kSaturate, - kSepia, }; - public: - static T None() - { - return T(kNone); - } - static T Blur(const FilterFunctionValue &length = FilterFunctionValue(0.0, "px")) - { - T result(kBlur); - result.parameters_.push_back(length); - return result; - } - static T Brightness(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kBrightness); - result.parameters_.push_back(value); - return result; - } - static T Contrast(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kContrast); - result.parameters_.push_back(value); - return result; - } - static T DropShadow(const std::string &shadow_value = "") - { - T result(kDropShadow); - result.raw_value_ = shadow_value; - return result; - } - static T Grayscale(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kGrayscale); - result.parameters_.push_back(value); - return result; - } - static T HueRotate(const FilterFunctionValue &angle = FilterFunctionValue(0.0, "deg")) - { - T result(kHueRotate); - result.parameters_.push_back(angle); - return result; - } - static T Invert(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kInvert); - result.parameters_.push_back(value); - return result; - } - static T Opacity(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kOpacity); - result.parameters_.push_back(value); - return result; - } - static T Saturate(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kSaturate); - result.parameters_.push_back(value); - return result; - } - static T Sepia(const FilterFunctionValue &value = FilterFunctionValue(1.0)) - { - T result(kSepia); - result.parameters_.push_back(value); - return result; - } - - protected: - GenericFilterFunction(Tag tag) - : tag_(tag) - { - } - - public: - GenericFilterFunction() - : tag_(kNone) - { - } - inline bool isNone() const - { - return tag_ == kNone; - } - inline bool isBlur() const - { - return tag_ == kBlur; - } - inline bool isBrightness() const - { - return tag_ == kBrightness; - } - inline bool isContrast() const - { - return tag_ == kContrast; - } - inline bool isDropShadow() const - { - return tag_ == kDropShadow; - } - inline bool isGrayscale() const - { - return tag_ == kGrayscale; - } - inline bool isHueRotate() const - { - return tag_ == kHueRotate; - } - inline bool isInvert() const + template + class GenericFilterFunction : public ToCss { - return tag_ == kInvert; - } - inline bool isOpacity() const - { - return tag_ == kOpacity; - } - inline bool isSaturate() const - { - return tag_ == kSaturate; - } - inline bool isSepia() const - { - return tag_ == kSepia; - } - - // Access parameters - const std::vector &getParameters() const - { - return parameters_; - } - - const std::string &getRawValue() const - { - return raw_value_; - } + protected: + enum Tag : uint8_t + { + kNone = 0, + kBlur, + kBrightness, + kContrast, + kDropShadow, + kGrayscale, + kHueRotate, + kInvert, + kOpacity, + kSaturate, + kSepia, + }; - std::string toCss() const override - { - std::string result; - switch (tag_) - { - case kNone: - return "none"; - case kBlur: - result = "blur("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + public: + static T None() + { + return T(kNone); + } + static T Blur(const FilterFunctionValue &length = FilterFunctionValue(0.0, "px")) + { + T result(kBlur); + result.parameters_.push_back(length); return result; - case kBrightness: - result = "brightness("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Brightness(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kBrightness); + result.parameters_.push_back(value); return result; - case kContrast: - result = "contrast("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Contrast(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kContrast); + result.parameters_.push_back(value); return result; - case kDropShadow: - result = "drop-shadow("; - result += raw_value_; - result += ")"; + } + static T DropShadow(const std::string &shadow_value = "") + { + T result(kDropShadow); + result.raw_value_ = shadow_value; return result; - case kGrayscale: - result = "grayscale("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Grayscale(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kGrayscale); + result.parameters_.push_back(value); return result; - case kHueRotate: - result = "hue-rotate("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T HueRotate(const FilterFunctionValue &angle = FilterFunctionValue(0.0, "deg")) + { + T result(kHueRotate); + result.parameters_.push_back(angle); return result; - case kInvert: - result = "invert("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Invert(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kInvert); + result.parameters_.push_back(value); return result; - case kOpacity: - result = "opacity("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Opacity(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kOpacity); + result.parameters_.push_back(value); return result; - case kSaturate: - result = "saturate("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Saturate(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kSaturate); + result.parameters_.push_back(value); return result; - case kSepia: - result = "sepia("; - if (!parameters_.empty()) - result += parameters_[0].toString(); - result += ")"; + } + static T Sepia(const FilterFunctionValue &value = FilterFunctionValue(1.0)) + { + T result(kSepia); + result.parameters_.push_back(value); return result; - default: - return "none"; } - } - protected: - Tag tag_; - std::vector parameters_; - std::string raw_value_; // For complex values like drop-shadow - }; + protected: + GenericFilterFunction(Tag tag) + : tag_(tag) + { + } - template - class GenericFilter : public ToCss - { - public: - GenericFilter() - : is_none_(true) - { - } + public: + GenericFilterFunction() + : tag_(kNone) + { + } + inline bool isNone() const + { + return tag_ == kNone; + } + inline bool isBlur() const + { + return tag_ == kBlur; + } + inline bool isBrightness() const + { + return tag_ == kBrightness; + } + inline bool isContrast() const + { + return tag_ == kContrast; + } + inline bool isDropShadow() const + { + return tag_ == kDropShadow; + } + inline bool isGrayscale() const + { + return tag_ == kGrayscale; + } + inline bool isHueRotate() const + { + return tag_ == kHueRotate; + } + inline bool isInvert() const + { + return tag_ == kInvert; + } + inline bool isOpacity() const + { + return tag_ == kOpacity; + } + inline bool isSaturate() const + { + return tag_ == kSaturate; + } + inline bool isSepia() const + { + return tag_ == kSepia; + } - static T None() - { - T filter; - filter.is_none_ = true; - return filter; - } + // Access parameters + const std::vector &getParameters() const + { + return parameters_; + } - inline bool isNone() const - { - return is_none_; - } + const std::string &getRawValue() const + { + return raw_value_; + } - // Add filter functions to the list - void addFunction(const F &func) - { - if (is_none_) + std::string toCss() const override { - is_none_ = false; - filter_functions_.clear(); + std::string result; + switch (tag_) + { + case kNone: + return "none"; + case kBlur: + result = "blur("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kBrightness: + result = "brightness("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kContrast: + result = "contrast("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kDropShadow: + result = "drop-shadow("; + result += raw_value_; + result += ")"; + return result; + case kGrayscale: + result = "grayscale("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kHueRotate: + result = "hue-rotate("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kInvert: + result = "invert("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kOpacity: + result = "opacity("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kSaturate: + result = "saturate("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + case kSepia: + result = "sepia("; + if (!parameters_.empty()) + result += parameters_[0].toString(); + result += ")"; + return result; + default: + return "none"; + } } - filter_functions_.push_back(func); - } - // Get filter functions - const std::vector &getFunctions() const - { - return filter_functions_; - } + protected: + Tag tag_; + std::vector parameters_; + std::string raw_value_; // For complex values like drop-shadow + }; - // Set filter functions - void setFunctions(const std::vector &functions) + template + class GenericFilter : public ToCss { - filter_functions_ = functions; - is_none_ = functions.empty(); - } + public: + GenericFilter() + : is_none_(true) + { + } - std::string toCss() const override - { - if (is_none_ || filter_functions_.empty()) - return "none"; + static T None() + { + T filter; + filter.is_none_ = true; + return filter; + } - std::string result; - for (size_t i = 0; i < filter_functions_.size(); ++i) + inline bool isNone() const { - if (i > 0) - result += " "; - result += filter_functions_[i].toCss(); + return is_none_; } - return result; - } - protected: - bool is_none_; - std::vector filter_functions_; - }; -} + // Add filter functions to the list + void addFunction(const F &func) + { + if (is_none_) + { + is_none_ = false; + filter_functions_.clear(); + } + filter_functions_.push_back(func); + } + + // Get filter functions + const std::vector &getFunctions() const + { + return filter_functions_; + } + + // Set filter functions + void setFunctions(const std::vector &functions) + { + filter_functions_ = functions; + is_none_ = functions.empty(); + } + + std::string toCss() const override + { + if (is_none_ || filter_functions_.empty()) + return "none"; + + std::string result; + for (size_t i = 0; i < filter_functions_.size(); ++i) + { + if (i > 0) + result += " "; + result += filter_functions_[i].toCss(); + } + return result; + } + + protected: + bool is_none_; + std::vector filter_functions_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/flex.hpp b/src/client/cssom/values/generics/flex.hpp index 85837a091..a04050569 100644 --- a/src/client/cssom/values/generics/flex.hpp +++ b/src/client/cssom/values/generics/flex.hpp @@ -3,92 +3,95 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericFlexDirection + namespace client_cssom::values::generics { - protected: - enum Tag + template + class GenericFlexDirection { - kRow = 0, - kRowReverse, - kColumn, - kColumnReverse, - }; - - public: - static T Row() - { - return T(Tag::kRow); - } - static T RowReverse() - { - return T(Tag::kRowReverse); - } - static T Column() - { - return T(Tag::kColumn); - } - static T ColumnReverse() - { - return T(Tag::kColumnReverse); - } + protected: + enum Tag + { + kRow = 0, + kRowReverse, + kColumn, + kColumnReverse, + }; - public: - GenericFlexDirection() - : tag_(kRow) - { - } + public: + static T Row() + { + return T(Tag::kRow); + } + static T RowReverse() + { + return T(Tag::kRowReverse); + } + static T Column() + { + return T(Tag::kColumn); + } + static T ColumnReverse() + { + return T(Tag::kColumnReverse); + } - private: - GenericFlexDirection(Tag tag) - : tag_(tag) - { - } + public: + GenericFlexDirection() + : tag_(kRow) + { + } - protected: - Tag tag_; - }; + private: + GenericFlexDirection(Tag tag) + : tag_(tag) + { + } - template - class GenericFlexWrap - { - protected: - enum Tag - { - kNoWrap = 0, - kWrap, - kWrapReverse, + protected: + Tag tag_; }; - public: - static T NoWrap() - { - return T(Tag::kNoWrap); - } - static T Wrap() - { - return T(Tag::kWrap); - } - static T WrapReverse() + template + class GenericFlexWrap { - return T(Tag::kWrapReverse); - } + protected: + enum Tag + { + kNoWrap = 0, + kWrap, + kWrapReverse, + }; - public: - GenericFlexWrap() - : tag_(kNoWrap) - { - } + public: + static T NoWrap() + { + return T(Tag::kNoWrap); + } + static T Wrap() + { + return T(Tag::kWrap); + } + static T WrapReverse() + { + return T(Tag::kWrapReverse); + } - private: - GenericFlexWrap(Tag tag) - : tag_(tag) - { - } + public: + GenericFlexWrap() + : tag_(kNoWrap) + { + } - protected: - Tag tag_; - }; -} + private: + GenericFlexWrap(Tag tag) + : tag_(tag) + { + } + + protected: + Tag tag_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/font.hpp b/src/client/cssom/values/generics/font.hpp index f71e7d0c7..7bed6c61f 100644 --- a/src/client/cssom/values/generics/font.hpp +++ b/src/client/cssom/values/generics/font.hpp @@ -3,140 +3,143 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - template - class FontStyle + namespace client_cssom::values::generics { - protected: - enum Tag : uint8_t - { - kNormal, - kItalic, - kOblique, - }; + template + class FontStyle + { + protected: + enum Tag : uint8_t + { + kNormal, + kItalic, + kOblique, + }; - public: - static T Normal() - { - return T(kNormal); - } - static T Italic() - { - return T(kItalic); - } - static T Oblique(AngleType angle) - { - return T(kOblique, angle); - } - - protected: - FontStyle(Tag tag = kNormal, std::optional oblique_angle = std::nullopt) - : tag_(tag) - , oblique_angle_(oblique_angle) - { - } + public: + static T Normal() + { + return T(kNormal); + } + static T Italic() + { + return T(kItalic); + } + static T Oblique(AngleType angle) + { + return T(kOblique, angle); + } - protected: - Tag tag_; - std::optional oblique_angle_; - }; + protected: + FontStyle(Tag tag = kNormal, std::optional oblique_angle = std::nullopt) + : tag_(tag) + , oblique_angle_(oblique_angle) + { + } - template - class GenericLineHeight - { - private: - enum Tag : uint8_t - { - kNormal, - kNumber, - kLength, + protected: + Tag tag_; + std::optional oblique_angle_; }; - using ValueVariant = std::variant; - public: - static T Normal() - { - return T(kNormal); - } - static T Number(N number) - { - return T(kNumber, number); - } - static T Length(L length) - { - return T(kLength, length); - } + template + class GenericLineHeight + { + private: + enum Tag : uint8_t + { + kNormal, + kNumber, + kLength, + }; + using ValueVariant = std::variant; - public: - inline bool isNormal() const - { - return tag_ == kNormal; - } - inline bool isNumber() const - { - return tag_ == kNumber; - } - inline bool isLength() const - { - return tag_ == kLength; - } + public: + static T Normal() + { + return T(kNormal); + } + static T Number(N number) + { + return T(kNumber, number); + } + static T Length(L length) + { + return T(kLength, length); + } - N getNumber() - { - if (isNumber()) - return std::get(value_); - throw std::bad_variant_access(); - } - const N &getNumber() const - { - if (isNumber()) - return std::get(value_); - throw std::bad_variant_access(); - } - L getLength() - { - if (isLength()) - return std::get(value_); - throw std::bad_variant_access(); - } - const L &getLength() const - { - if (isLength()) - return std::get(value_); - throw std::bad_variant_access(); - } + public: + inline bool isNormal() const + { + return tag_ == kNormal; + } + inline bool isNumber() const + { + return tag_ == kNumber; + } + inline bool isLength() const + { + return tag_ == kLength; + } - protected: - GenericLineHeight() - : tag_(Tag::kNormal) - , value_(std::monostate{}) - { - } - GenericLineHeight(Tag tag, ValueVariant value = std::monostate{}) - : tag_(tag) - , value_(value) - { - } + N getNumber() + { + if (isNumber()) + return std::get(value_); + throw std::bad_variant_access(); + } + const N &getNumber() const + { + if (isNumber()) + return std::get(value_); + throw std::bad_variant_access(); + } + L getLength() + { + if (isLength()) + return std::get(value_); + throw std::bad_variant_access(); + } + const L &getLength() const + { + if (isLength()) + return std::get(value_); + throw std::bad_variant_access(); + } - void setNormal() - { - tag_ = Tag::kNormal; - value_ = std::monostate{}; - } - void setNumber(N number) - { - tag_ = Tag::kNumber; - value_ = number; - } - void setLength(L length) - { - tag_ = Tag::kLength; - value_ = length; - } + protected: + GenericLineHeight() + : tag_(Tag::kNormal) + , value_(std::monostate{}) + { + } + GenericLineHeight(Tag tag, ValueVariant value = std::monostate{}) + : tag_(tag) + , value_(value) + { + } + + void setNormal() + { + tag_ = Tag::kNormal; + value_ = std::monostate{}; + } + void setNumber(N number) + { + tag_ = Tag::kNumber; + value_ = number; + } + void setLength(L length) + { + tag_ = Tag::kLength; + value_ = length; + } - private: - Tag tag_; - ValueVariant value_; - }; -} + private: + Tag tag_; + ValueVariant value_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/image.hpp b/src/client/cssom/values/generics/image.hpp index 3db3e9566..f0fe40907 100644 --- a/src/client/cssom/values/generics/image.hpp +++ b/src/client/cssom/values/generics/image.hpp @@ -5,272 +5,275 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - enum class LineDirection + namespace client_cssom::values::generics { - kToLeft, - kToRight, - kToTop, - kToBottom, - kToTopLeft, - kToTopRight, - kToBottomLeft, - kToBottomRight - }; - - enum class RadialGradientShape - { - kCircle, - kEllipse - }; - - enum class RadialGradientSize - { - kClosestSide, - kClosestCorner, - kFarthestSide, - kFarthestCorner - }; - - class GenericGradientItemBase - { - public: - enum ItemType + enum class LineDirection { - kSimpleColorStop, - kComplexColorStop, - kInterpolationHint, + kToLeft, + kToRight, + kToTop, + kToBottom, + kToTopLeft, + kToTopRight, + kToBottomLeft, + kToBottomRight }; - public: - virtual ~GenericGradientItemBase() = default; - - public: - ItemType type; - }; - - template - class GenericGradientItem : public GenericGradientItemBase - { - public: - struct SimpleColorStop + enum class RadialGradientShape { - Color color; + kCircle, + kEllipse }; - struct ComplexColorStop + + enum class RadialGradientSize { - Color color; - LP length_percentage; // Length or percentage for the stop position. + kClosestSide, + kClosestCorner, + kFarthestSide, + kFarthestCorner }; - struct InterpolationHint + + class GenericGradientItemBase { - LP length_percentage; // Length or percentage for the hint position. - }; + public: + enum ItemType + { + kSimpleColorStop, + kComplexColorStop, + kInterpolationHint, + }; - private: - using ItemValue = std::variant; + public: + virtual ~GenericGradientItemBase() = default; - public: - ItemValue value; - }; + public: + ItemType type; + }; - template - class GenericGradient : public ToCss - { - public: - class LinearGradient + template + class GenericGradientItem : public GenericGradientItemBase { public: - using GradientItem = GenericGradientItem; + struct SimpleColorStop + { + Color color; + }; + struct ComplexColorStop + { + Color color; + LP length_percentage; // Length or percentage for the stop position. + }; + struct InterpolationHint + { + LP length_percentage; // Length or percentage for the hint position. + }; + + private: + using ItemValue = std::variant; - LineDirection direction = LineDirection::kToRight; - std::vector items; + public: + ItemValue value; }; - class RadialGradient + template + class GenericGradient : public ToCss { public: - using GradientItem = GenericGradientItem; - - RadialGradientShape shape = RadialGradientShape::kEllipse; - RadialGradientSize size = RadialGradientSize::kFarthestCorner; - std::vector items; - }; + class LinearGradient + { + public: + using GradientItem = GenericGradientItem; - // TODO(yorkie): add conic gradient when needed. + LineDirection direction = LineDirection::kToRight; + std::vector items; + }; - using GradientType = std::variant; + class RadialGradient + { + public: + using GradientItem = GenericGradientItem; - public: - bool repeating = false; - GradientType gradient_type; + RadialGradientShape shape = RadialGradientShape::kEllipse; + RadialGradientSize size = RadialGradientSize::kFarthestCorner; + std::vector items; + }; - // Constructor for linear gradient - GenericGradient(const LinearGradient &linear) - : gradient_type(linear) - { - } + // TODO(yorkie): add conic gradient when needed. - // Constructor for radial gradient - GenericGradient(const RadialGradient &radial) - : gradient_type(radial) - { - } + using GradientType = std::variant; - // Default constructor (creates linear gradient) - GenericGradient() - : gradient_type(LinearGradient{}) - { - } + public: + bool repeating = false; + GradientType gradient_type; - // CSS serialization implementation - std::string toCss() const override - { - if (std::holds_alternative(gradient_type)) + // Constructor for linear gradient + GenericGradient(const LinearGradient &linear) + : gradient_type(linear) { - const auto &linearGrad = std::get(gradient_type); - std::string functionName = repeating ? "repeating-linear-gradient" : "linear-gradient"; - - std::string direction; - switch (linearGrad.direction) - { - case LineDirection::kToRight: - direction = "to right"; - break; - case LineDirection::kToLeft: - direction = "to left"; - break; - case LineDirection::kToTop: - direction = "to top"; - break; - case LineDirection::kToBottom: - direction = "to bottom"; - break; - case LineDirection::kToTopLeft: - direction = "to top left"; - break; - case LineDirection::kToTopRight: - direction = "to top right"; - break; - case LineDirection::kToBottomLeft: - direction = "to bottom left"; - break; - case LineDirection::kToBottomRight: - direction = "to bottom right"; - break; - } - - // Serialize color stops - std::string colorStops = serializeColorStops(linearGrad.items); - if (colorStops.empty()) - { - colorStops = "transparent, transparent"; // Fallback - } + } - return functionName + "(" + direction + ", " + colorStops + ")"; + // Constructor for radial gradient + GenericGradient(const RadialGradient &radial) + : gradient_type(radial) + { } - else if (std::holds_alternative(gradient_type)) + + // Default constructor (creates linear gradient) + GenericGradient() + : gradient_type(LinearGradient{}) { - const auto &radialGrad = std::get(gradient_type); - std::string functionName = repeating ? "repeating-radial-gradient" : "radial-gradient"; - std::string shape = (radialGrad.shape == RadialGradientShape::kCircle) ? "circle" : "ellipse"; + } - std::string size; - switch (radialGrad.size) + // CSS serialization implementation + std::string toCss() const override + { + if (std::holds_alternative(gradient_type)) { - case RadialGradientSize::kClosestSide: - size = "closest-side"; - break; - case RadialGradientSize::kClosestCorner: - size = "closest-corner"; - break; - case RadialGradientSize::kFarthestSide: - size = "farthest-side"; - break; - case RadialGradientSize::kFarthestCorner: - size = "farthest-corner"; - break; + const auto &linearGrad = std::get(gradient_type); + std::string functionName = repeating ? "repeating-linear-gradient" : "linear-gradient"; + + std::string direction; + switch (linearGrad.direction) + { + case LineDirection::kToRight: + direction = "to right"; + break; + case LineDirection::kToLeft: + direction = "to left"; + break; + case LineDirection::kToTop: + direction = "to top"; + break; + case LineDirection::kToBottom: + direction = "to bottom"; + break; + case LineDirection::kToTopLeft: + direction = "to top left"; + break; + case LineDirection::kToTopRight: + direction = "to top right"; + break; + case LineDirection::kToBottomLeft: + direction = "to bottom left"; + break; + case LineDirection::kToBottomRight: + direction = "to bottom right"; + break; + } + + // Serialize color stops + std::string colorStops = serializeColorStops(linearGrad.items); + if (colorStops.empty()) + { + colorStops = "transparent, transparent"; // Fallback + } + + return functionName + "(" + direction + ", " + colorStops + ")"; } - - // Serialize color stops - std::string colorStops = serializeColorStops(radialGrad.items); - if (colorStops.empty()) + else if (std::holds_alternative(gradient_type)) { - colorStops = "transparent, transparent"; // Fallback + const auto &radialGrad = std::get(gradient_type); + std::string functionName = repeating ? "repeating-radial-gradient" : "radial-gradient"; + std::string shape = (radialGrad.shape == RadialGradientShape::kCircle) ? "circle" : "ellipse"; + + std::string size; + switch (radialGrad.size) + { + case RadialGradientSize::kClosestSide: + size = "closest-side"; + break; + case RadialGradientSize::kClosestCorner: + size = "closest-corner"; + break; + case RadialGradientSize::kFarthestSide: + size = "farthest-side"; + break; + case RadialGradientSize::kFarthestCorner: + size = "farthest-corner"; + break; + } + + // Serialize color stops + std::string colorStops = serializeColorStops(radialGrad.items); + if (colorStops.empty()) + { + colorStops = "transparent, transparent"; // Fallback + } + + std::string shapeSize = shape; + if (!size.empty()) + { + shapeSize += " " + size; + } + + return functionName + "(" + shapeSize + ", " + colorStops + ")"; } - std::string shapeSize = shape; - if (!size.empty()) + return "none"; + } + + private: + // Helper method to serialize color stops + template + std::string serializeColorStops(const ItemVector &items) const + { + std::string colorStops; + for (size_t i = 0; i < items.size(); ++i) { - shapeSize += " " + size; + if (i > 0) + colorStops += ", "; + + const auto &item = items[i]; + if (item.type == GenericGradientItemBase::kSimpleColorStop) + { + const auto &colorStop = std::get::SimpleColorStop>(item.value); + colorStops += colorStop.color.toCss(); + } + else if (item.type == GenericGradientItemBase::kComplexColorStop) + { + const auto &colorStop = std::get::ComplexColorStop>(item.value); + colorStops += colorStop.color.toCss() + " " + colorStop.length_percentage.toCss(); + } + else if (item.type == GenericGradientItemBase::kInterpolationHint) + { + const auto &hint = std::get::InterpolationHint>(item.value); + colorStops += hint.length_percentage.toCss(); + } } - - return functionName + "(" + shapeSize + ", " + colorStops + ")"; + return colorStops; } + }; - return "none"; - } - - private: - // Helper method to serialize color stops - template - std::string serializeColorStops(const ItemVector &items) const + template + class GenericImage : public std::variant, public ToCss { - std::string colorStops; - for (size_t i = 0; i < items.size(); ++i) + public: + // CSS serialization + std::string toCss() const override { - if (i > 0) - colorStops += ", "; - - const auto &item = items[i]; - if (item.type == GenericGradientItemBase::kSimpleColorStop) - { - const auto &colorStop = std::get::SimpleColorStop>(item.value); - colorStops += colorStop.color.toCss(); - } - else if (item.type == GenericGradientItemBase::kComplexColorStop) + if (std::holds_alternative(*this)) { - const auto &colorStop = std::get::ComplexColorStop>(item.value); - colorStops += colorStop.color.toCss() + " " + colorStop.length_percentage.toCss(); + return "none"; } - else if (item.type == GenericGradientItemBase::kInterpolationHint) + else if (std::holds_alternative(*this)) { - const auto &hint = std::get::InterpolationHint>(item.value); - colorStops += hint.length_percentage.toCss(); + const auto &url_or_none = std::get(*this); + if (url_or_none.url.has_value()) + { + return "url(\"" + url_or_none.url.value() + "\")"; + } + return "none"; } - } - return colorStops; - } - }; - - template - class GenericImage : public std::variant, public ToCss - { - public: - // CSS serialization - std::string toCss() const override - { - if (std::holds_alternative(*this)) - { - return "none"; - } - else if (std::holds_alternative(*this)) - { - const auto &url_or_none = std::get(*this); - if (url_or_none.url.has_value()) + else if (std::holds_alternative(*this)) { - return "url(\"" + url_or_none.url.value() + "\")"; + // Call the gradient's toCss() method + const auto &gradient = std::get(*this); + return gradient.toCss(); } return "none"; } - else if (std::holds_alternative(*this)) - { - // Call the gradient's toCss() method - const auto &gradient = std::get(*this); - return gradient.toCss(); - } - return "none"; - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/length.hpp b/src/client/cssom/values/generics/length.hpp index 807275893..d3f5c3486 100644 --- a/src/client/cssom/values/generics/length.hpp +++ b/src/client/cssom/values/generics/length.hpp @@ -2,282 +2,285 @@ #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericLengthPercentageOrAuto + namespace client_cssom::values::generics { - private: - enum Tag : uint8_t - { - kLengthPercentage, - kAuto, - }; + template + class GenericLengthPercentageOrAuto + { + private: + enum Tag : uint8_t + { + kLengthPercentage, + kAuto, + }; - public: - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline bool isLengthPercentage() const - { - return tag_ == kLengthPercentage; - } + public: + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline bool isLengthPercentage() const + { + return tag_ == kLengthPercentage; + } - inline LengthPercent lengthPercent() const - { - assert(tag_ == kLengthPercentage && "The tag is not length percentage."); - return length_percent_.value(); - } - - private: - Tag tag_; - std::optional length_percent_; - }; + inline LengthPercent lengthPercent() const + { + assert(tag_ == kLengthPercentage && "The tag is not length percentage."); + return length_percent_.value(); + } - template - class GenericSize - { - private: - enum Tag : uint8_t - { - kLengthPercentage, - kAuto, - kMaxContent, - kMinContent, - kFitContent, - kStretch, + private: + Tag tag_; + std::optional length_percent_; }; - public: - static T Auto() - { - return T(kAuto); - } - static T MaxContent() - { - return T(kMaxContent); - } - static T MinContent() - { - return T(kMinContent); - } - static T FitContent() - { - return T(kFitContent); - } - static T Stretch() - { - return T(kStretch); - } - static T LengthPercentage(LengthPercent length_percent) - { - return T(kLengthPercentage, length_percent); - } + template + class GenericSize + { + private: + enum Tag : uint8_t + { + kLengthPercentage, + kAuto, + kMaxContent, + kMinContent, + kFitContent, + kStretch, + }; - public: - GenericSize() - : tag_(kAuto) - , length_percent_(std::nullopt) - { - } - GenericSize(Tag tag, std::optional length_percent = std::nullopt) - : tag_(tag) - , length_percent_(length_percent) - { - } + public: + static T Auto() + { + return T(kAuto); + } + static T MaxContent() + { + return T(kMaxContent); + } + static T MinContent() + { + return T(kMinContent); + } + static T FitContent() + { + return T(kFitContent); + } + static T Stretch() + { + return T(kStretch); + } + static T LengthPercentage(LengthPercent length_percent) + { + return T(kLengthPercentage, length_percent); + } - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline bool isLengthPercentage() const - { - return tag_ == kLengthPercentage; - } - inline bool isMaxContent() const - { - return tag_ == kMaxContent; - } - inline bool isMinContent() const - { - return tag_ == kMinContent; - } - inline bool isFitContent() const - { - return tag_ == kFitContent; - } - inline bool isStretch() const - { - return tag_ == kStretch; - } + public: + GenericSize() + : tag_(kAuto) + , length_percent_(std::nullopt) + { + } + GenericSize(Tag tag, std::optional length_percent = std::nullopt) + : tag_(tag) + , length_percent_(length_percent) + { + } - inline LengthPercent lengthPercent() const - { - assert(tag_ == kLengthPercentage && "The tag is not length percentage."); - return length_percent_.value(); - } + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline bool isLengthPercentage() const + { + return tag_ == kLengthPercentage; + } + inline bool isMaxContent() const + { + return tag_ == kMaxContent; + } + inline bool isMinContent() const + { + return tag_ == kMinContent; + } + inline bool isFitContent() const + { + return tag_ == kFitContent; + } + inline bool isStretch() const + { + return tag_ == kStretch; + } - protected: - void setAuto() - { - tag_ = kAuto; - length_percent_ = std::nullopt; - } - void setLengthPercentage(LengthPercent length_percent) - { - tag_ = kLengthPercentage; - length_percent_ = length_percent; - } + inline LengthPercent lengthPercent() const + { + assert(tag_ == kLengthPercentage && "The tag is not length percentage."); + return length_percent_.value(); + } - private: - Tag tag_; - std::optional length_percent_; - }; + protected: + void setAuto() + { + tag_ = kAuto; + length_percent_ = std::nullopt; + } + void setLengthPercentage(LengthPercent length_percent) + { + tag_ = kLengthPercentage; + length_percent_ = length_percent; + } - template - class GenericMaxSize - { - private: - enum Tag : uint8_t - { - kLengthPercentage, - kNone, - kMaxContent, - kMinContent, - kFitContent, - kStretch, + private: + Tag tag_; + std::optional length_percent_; }; - public: - static T None() - { - return T(kNone); - } - static T MaxContent() - { - return T(kMaxContent); - } - static T MinContent() - { - return T(kMinContent); - } - static T FitContent() - { - return T(kFitContent); - } - static T Stretch() - { - return T(kStretch); - } + template + class GenericMaxSize + { + private: + enum Tag : uint8_t + { + kLengthPercentage, + kNone, + kMaxContent, + kMinContent, + kFitContent, + kStretch, + }; - private: - GenericMaxSize(Tag tag) - : tag_(tag) - { - } + public: + static T None() + { + return T(kNone); + } + static T MaxContent() + { + return T(kMaxContent); + } + static T MinContent() + { + return T(kMinContent); + } + static T FitContent() + { + return T(kFitContent); + } + static T Stretch() + { + return T(kStretch); + } - public: - inline bool isNone() const - { - return tag_ == kNone; - } - inline bool isMaxContent() const - { - return tag_ == kMaxContent; - } - inline bool isMinContent() const - { - return tag_ == kMinContent; - } - inline bool isFitContent() const - { - return tag_ == kFitContent; - } - inline bool isStretch() const - { - return tag_ == kStretch; - } - inline bool isLengthPercentage() const - { - return tag_ == kLengthPercentage; - } + private: + GenericMaxSize(Tag tag) + : tag_(tag) + { + } - inline LengthPercent lengthPercent() const - { - assert(tag_ == kLengthPercentage && "The tag is not length percentage."); - return length_percent_.value(); - } + public: + inline bool isNone() const + { + return tag_ == kNone; + } + inline bool isMaxContent() const + { + return tag_ == kMaxContent; + } + inline bool isMinContent() const + { + return tag_ == kMinContent; + } + inline bool isFitContent() const + { + return tag_ == kFitContent; + } + inline bool isStretch() const + { + return tag_ == kStretch; + } + inline bool isLengthPercentage() const + { + return tag_ == kLengthPercentage; + } - private: - Tag tag_; - std::optional length_percent_; - }; + inline LengthPercent lengthPercent() const + { + assert(tag_ == kLengthPercentage && "The tag is not length percentage."); + return length_percent_.value(); + } - template - class GenericMargin - { - private: - enum Tag : uint8_t - { - kLengthPercentage, - kAuto, + private: + Tag tag_; + std::optional length_percent_; }; - public: - static T Auto() - { - return T(kAuto); - } - static T LengthPercentage(LP length_percent) - { - return T(kLengthPercentage, length_percent); - } + template + class GenericMargin + { + private: + enum Tag : uint8_t + { + kLengthPercentage, + kAuto, + }; - public: - GenericMargin() - : tag_(kLengthPercentage) - , length_percent_(LP(0.0f)) - { - } + public: + static T Auto() + { + return T(kAuto); + } + static T LengthPercentage(LP length_percent) + { + return T(kLengthPercentage, length_percent); + } - private: - GenericMargin(Tag tag, std::optional length_percent = std::nullopt) - : tag_(tag) - , length_percent_(length_percent) - { - } + public: + GenericMargin() + : tag_(kLengthPercentage) + , length_percent_(LP(0.0f)) + { + } - public: - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline bool isLengthPercentage() const - { - return tag_ == kLengthPercentage; - } + private: + GenericMargin(Tag tag, std::optional length_percent = std::nullopt) + : tag_(tag) + , length_percent_(length_percent) + { + } - inline LP lengthPercent() const - { - assert(tag_ == kLengthPercentage && "The tag is not length percentage."); - return length_percent_.value(); - } + public: + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline bool isLengthPercentage() const + { + return tag_ == kLengthPercentage; + } - protected: - void setAuto() - { - tag_ = kAuto; - length_percent_ = std::nullopt; - } - void setLengthPercentage(LP length_percent) - { - tag_ = kLengthPercentage; - length_percent_ = length_percent; - } + inline LP lengthPercent() const + { + assert(tag_ == kLengthPercentage && "The tag is not length percentage."); + return length_percent_.value(); + } + + protected: + void setAuto() + { + tag_ = kAuto; + length_percent_ = std::nullopt; + } + void setLengthPercentage(LP length_percent) + { + tag_ = kLengthPercentage; + length_percent_ = length_percent; + } - private: - Tag tag_; - std::optional length_percent_; - }; -} + private: + Tag tag_; + std::optional length_percent_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/position.hpp b/src/client/cssom/values/generics/position.hpp index 446e31f94..b276c838d 100644 --- a/src/client/cssom/values/generics/position.hpp +++ b/src/client/cssom/values/generics/position.hpp @@ -6,229 +6,232 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - class PositionType : public ToLayoutValue + namespace client_cssom::values::generics { - protected: - enum Tag : uint8_t + class PositionType : public ToLayoutValue { - kStatic = 0, - kRelative, - kAbsolute, - kFixed, - kSticky, - }; - - public: - static PositionType Static() - { - return PositionType(kStatic); - } - static PositionType Relative() - { - return PositionType(kRelative); - } - static PositionType Absolute() - { - return PositionType(kAbsolute); - } - static PositionType Fixed() - { - return PositionType(kFixed); - } - static PositionType Sticky() - { - return PositionType(kSticky); - } - - public: - PositionType() - : tag_(kStatic) - { - } + protected: + enum Tag : uint8_t + { + kStatic = 0, + kRelative, + kAbsolute, + kFixed, + kSticky, + }; + + public: + static PositionType Static() + { + return PositionType(kStatic); + } + static PositionType Relative() + { + return PositionType(kRelative); + } + static PositionType Absolute() + { + return PositionType(kAbsolute); + } + static PositionType Fixed() + { + return PositionType(kFixed); + } + static PositionType Sticky() + { + return PositionType(kSticky); + } - private: - PositionType(Tag tag) - : tag_(tag) - { - } + public: + PositionType() + : tag_(kStatic) + { + } - public: - inline bool isStatic() const - { - return tag_ == kStatic; - } - inline bool isRelative() const - { - return tag_ == kRelative; - } - inline bool isAbsolute() const - { - return tag_ == kAbsolute; - } - inline bool isFixed() const - { - return tag_ == kFixed; - } - inline bool isSticky() const - { - return tag_ == kSticky; - } + private: + PositionType(Tag tag) + : tag_(tag) + { + } - inline crates::layout2::styles::Position toLayoutValue() const - { - switch (tag_) + public: + inline bool isStatic() const + { + return tag_ == kStatic; + } + inline bool isRelative() const + { + return tag_ == kRelative; + } + inline bool isAbsolute() const + { + return tag_ == kAbsolute; + } + inline bool isFixed() const { - case kStatic: - case kRelative: - return crates::layout2::styles::Position::Relative(); - case kAbsolute: - return crates::layout2::styles::Position::Absolute(); - default: - // TODO(yorkie): support fixed and sticky. - return crates::layout2::styles::Position::Relative(); + return tag_ == kFixed; + } + inline bool isSticky() const + { + return tag_ == kSticky; } - } - protected: - Tag tag_; - }; + inline crates::layout2::styles::Position toLayoutValue() const + { + switch (tag_) + { + case kStatic: + case kRelative: + return crates::layout2::styles::Position::Relative(); + case kAbsolute: + return crates::layout2::styles::Position::Absolute(); + default: + // TODO(yorkie): support fixed and sticky. + return crates::layout2::styles::Position::Relative(); + } + } - class PositionComponent - { - virtual bool isCenter() const = 0; - }; + protected: + Tag tag_; + }; - template - requires transmute::common::derived_from && - transmute::common::derived_from - class GenericPosition - { - public: - GenericPosition(H horizontal, V vertical) - : horizontal(horizontal) - , vertical(vertical) + class PositionComponent { - } + virtual bool isCenter() const = 0; + }; - public: - bool isCenter() const + template + requires transmute::common::derived_from && + transmute::common::derived_from + class GenericPosition { - return horizontal.isCenter() && vertical.isCenter(); - } + public: + GenericPosition(H horizontal, V vertical) + : horizontal(horizontal) + , vertical(vertical) + { + } - public: - H horizontal; - V vertical; - }; + public: + bool isCenter() const + { + return horizontal.isCenter() && vertical.isCenter(); + } - template - class GenericPositionOrAuto - { - private: - enum Tag : uint8_t - { - kPosition, - kAuto, + public: + H horizontal; + V vertical; }; - public: - static GenericPositionOrAuto Position(Pos position) + template + class GenericPositionOrAuto { - return GenericPositionOrAuto(kPosition, position); - } - static GenericPositionOrAuto Auto() - { - return GenericPositionOrAuto(kAuto); - } + private: + enum Tag : uint8_t + { + kPosition, + kAuto, + }; - private: - GenericPositionOrAuto(Tag tag, std::optional position = std::nullopt) - : tag_(tag) - { - } + public: + static GenericPositionOrAuto Position(Pos position) + { + return GenericPositionOrAuto(kPosition, position); + } + static GenericPositionOrAuto Auto() + { + return GenericPositionOrAuto(kAuto); + } - public: - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline Pos getPosition() const - { - assert(tag_ == kPosition && "The tag is not position."); - return position_.value(); - } + private: + GenericPositionOrAuto(Tag tag, std::optional position = std::nullopt) + : tag_(tag) + { + } - private: - Tag tag_; - std::optional position_ = std::nullopt; - }; + public: + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline Pos getPosition() const + { + assert(tag_ == kPosition && "The tag is not position."); + return position_.value(); + } - template - class GenericInset - { - private: - enum Tag : uint8_t - { - kLengthPercentage, - kAuto, + private: + Tag tag_; + std::optional position_ = std::nullopt; }; - public: - static T Auto() + template + class GenericInset { - return T(kAuto); - } - static T LengthPercentage(LP length_percent) - { - return T(kLengthPercentage, length_percent); - } + private: + enum Tag : uint8_t + { + kLengthPercentage, + kAuto, + }; - public: - GenericInset() - : tag_(kAuto) - , length_percent_(std::nullopt) - { - } + public: + static T Auto() + { + return T(kAuto); + } + static T LengthPercentage(LP length_percent) + { + return T(kLengthPercentage, length_percent); + } - private: - GenericInset(Tag tag, std::optional length_percent = std::nullopt) - : tag_(tag) - , length_percent_(length_percent) - { - } + public: + GenericInset() + : tag_(kAuto) + , length_percent_(std::nullopt) + { + } - public: - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline bool isLengthPercentage() const - { - return tag_ == kLengthPercentage; - } + private: + GenericInset(Tag tag, std::optional length_percent = std::nullopt) + : tag_(tag) + , length_percent_(length_percent) + { + } - inline LP lengthPercent() const - { - assert(tag_ == kLengthPercentage && "The tag is not length percentage."); - return length_percent_.value(); - } + public: + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline bool isLengthPercentage() const + { + return tag_ == kLengthPercentage; + } - protected: - void setAuto() - { - tag_ = kAuto; - length_percent_ = std::nullopt; - } - void setLengthPercentage(LP length_percent) - { - tag_ = kLengthPercentage; - length_percent_ = length_percent; - } - - private: - Tag tag_; - std::optional length_percent_; - }; -} + inline LP lengthPercent() const + { + assert(tag_ == kLengthPercentage && "The tag is not length percentage."); + return length_percent_.value(); + } + + protected: + void setAuto() + { + tag_ = kAuto; + length_percent_ = std::nullopt; + } + void setLengthPercentage(LP length_percent) + { + tag_ = kLengthPercentage; + length_percent_ = length_percent; + } + + private: + Tag tag_; + std::optional length_percent_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/rect.hpp b/src/client/cssom/values/generics/rect.hpp index 46d883b6c..eaddd3a58 100644 --- a/src/client/cssom/values/generics/rect.hpp +++ b/src/client/cssom/values/generics/rect.hpp @@ -1,110 +1,113 @@ #pragma once -namespace client_cssom::values::generics +namespace endor { - enum class Edge + namespace client_cssom::values::generics { - kTop, - kRight, - kBottom, - kLeft - }; - - template - class Rect - { - public: - static Rect All(T value) + enum class Edge { - return Rect(value, value, value, value); - } - static Rect Default() - { - return Rect(T(), T(), T(), T()); - } + kTop, + kRight, + kBottom, + kLeft + }; - public: - Rect(T top, T right, T bottom, T left) - : top_(top) - , right_(right) - , bottom_(bottom) - , left_(left) - { - } - - public: - inline const T &top() const - { - return top_; - } - inline T &top() - { - return top_; - } - inline const T &right() const + template + class Rect { - return right_; - } - inline T &right() - { - return right_; - } - inline const T &bottom() const - { - return bottom_; - } - inline T &bottom() - { - return bottom_; - } - inline const T &left() const - { - return left_; - } - inline T &left() - { - return left_; - } + public: + static Rect All(T value) + { + return Rect(value, value, value, value); + } + static Rect Default() + { + return Rect(T(), T(), T(), T()); + } - inline void setTop(const T &value) - { - top_ = value; - } - inline void setRight(const T &value) - { - right_ = value; - } - inline void setBottom(const T &value) - { - bottom_ = value; - } - inline void setLeft(const T &value) - { - left_ = value; - } + public: + Rect(T top, T right, T bottom, T left) + : top_(top) + , right_(right) + , bottom_(bottom) + , left_(left) + { + } - const T &operator[](Edge edge) const - { - switch (edge) + public: + inline const T &top() const + { + return top_; + } + inline T &top() { - case Edge::kTop: return top_; - case Edge::kRight: + } + inline const T &right() const + { + return right_; + } + inline T &right() + { return right_; - case Edge::kBottom: + } + inline const T &bottom() const + { return bottom_; - case Edge::kLeft: + } + inline T &bottom() + { + return bottom_; + } + inline const T &left() const + { + return left_; + } + inline T &left() + { return left_; - default: - assert(false && "Invalid edge"); - return top(); // Dummy return to avoid compiler warning. } - } - protected: - T top_; - T right_; - T bottom_; - T left_; - }; -} + inline void setTop(const T &value) + { + top_ = value; + } + inline void setRight(const T &value) + { + right_ = value; + } + inline void setBottom(const T &value) + { + bottom_ = value; + } + inline void setLeft(const T &value) + { + left_ = value; + } + + const T &operator[](Edge edge) const + { + switch (edge) + { + case Edge::kTop: + return top_; + case Edge::kRight: + return right_; + case Edge::kBottom: + return bottom_; + case Edge::kLeft: + return left_; + default: + assert(false && "Invalid edge"); + return top(); // Dummy return to avoid compiler warning. + } + } + + protected: + T top_; + T right_; + T bottom_; + T left_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/time.hpp b/src/client/cssom/values/generics/time.hpp index 3aaaa7e87..d22a701a5 100644 --- a/src/client/cssom/values/generics/time.hpp +++ b/src/client/cssom/values/generics/time.hpp @@ -3,28 +3,31 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - class GenericTime : public ToCss + namespace client_cssom::values::generics { - public: - GenericTime(CSSFloat seconds = 0.0f) - : seconds_(seconds) + class GenericTime : public ToCss { - } + public: + GenericTime(CSSFloat seconds = 0.0f) + : seconds_(seconds) + { + } - public: - CSSFloat seconds() const - { - return seconds_; - } + public: + CSSFloat seconds() const + { + return seconds_; + } - std::string toCss() const override - { - return seconds_.toCss() + "s"; - } + std::string toCss() const override + { + return seconds_.toCss() + "s"; + } - protected: - CSSFloat seconds_; - }; -} + protected: + CSSFloat seconds_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/transform.hpp b/src/client/cssom/values/generics/transform.hpp index a003c862c..eb0f1d39d 100644 --- a/src/client/cssom/values/generics/transform.hpp +++ b/src/client/cssom/values/generics/transform.hpp @@ -4,911 +4,914 @@ #include #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericMatrix + namespace client_cssom::values::generics { - public: - GenericMatrix(Number a, Number b, Number c, Number d, Number e, Number f) - : a_(a) - , b_(b) - , c_(c) - , d_(d) - , e_(e) - , f_(f) - { - } - - public: - const Number &a() const - { - return a_; - } - const Number &b() const - { - return b_; - } - const Number &c() const - { - return c_; - } - const Number &d() const - { - return d_; - } - const Number &e() const - { - return e_; - } - const Number &f() const - { - return f_; - } - - private: - Number a_; - Number b_; - Number c_; - Number d_; - Number e_; - Number f_; - }; - - template - class GenericMatrix3D - { - public: - GenericMatrix3D(Number m11, Number m12, Number m13, Number m14, Number m21, Number m22, Number m23, Number m24, Number m31, Number m32, Number m33, Number m34, Number m41, Number m42, Number m43, Number m44) - : m11_(m11) - , m12_(m12) - , m13_(m13) - , m14_(m14) - , m21_(m21) - , m22_(m22) - , m23_(m23) - , m24_(m24) - , m31_(m31) - , m32_(m32) - , m33_(m33) - , m34_(m34) - , m41_(m41) - , m42_(m42) - , m43_(m43) - , m44_(m44) - { - } - - public: - const Number &m11() const - { - return m11_; - } - const Number &m12() const - { - return m12_; - } - const Number &m13() const - { - return m13_; - } - const Number &m14() const - { - return m14_; - } - const Number &m21() const - { - return m21_; - } - const Number &m22() const - { - return m22_; - } - const Number &m23() const - { - return m23_; - } - const Number &m24() const - { - return m24_; - } - const Number &m31() const - { - return m31_; - } - const Number &m32() const - { - return m32_; - } - const Number &m33() const - { - return m33_; - } - const Number &m34() const - { - return m34_; - } - const Number &m41() const - { - return m41_; - } - const Number &m42() const - { - return m42_; - } - const Number &m43() const - { - return m43_; - } - const Number &m44() const - { - return m44_; - } - - private: - Number m11_; - Number m12_; - Number m13_; - Number m14_; - Number m21_; - Number m22_; - Number m23_; - Number m24_; - Number m31_; - Number m32_; - Number m33_; - Number m34_; - Number m41_; - Number m42_; - Number m43_; - Number m44_; - }; - - template - class GenericSkew - { - public: - GenericSkew(Angle x, Angle y) - : x_(x) - , y_(y) - { - } - - public: - const Angle &x() const - { - return x_; - } - const Angle &y() const - { - return y_; - } - - private: - Angle x_; - Angle y_; - }; - - template - class GenericSkewX - { - public: - GenericSkewX(Angle angle) - : angle_(angle) - { - } - - public: - const Angle &angle() const - { - return angle_; - } - - private: - Angle angle_; - }; - - template - class GenericSkewY - { - public: - GenericSkewY(Angle angle) - : angle_(angle) - { - } - - public: - const Angle &angle() const - { - return angle_; - } - - private: - Angle angle_; - }; - - template - class GenericTranslate - { - public: - GenericTranslate(LengthPercentage x, LengthPercentage y) - : x_(x) - , y_(y) - { - } + template + class GenericMatrix + { + public: + GenericMatrix(Number a, Number b, Number c, Number d, Number e, Number f) + : a_(a) + , b_(b) + , c_(c) + , d_(d) + , e_(e) + , f_(f) + { + } + + public: + const Number &a() const + { + return a_; + } + const Number &b() const + { + return b_; + } + const Number &c() const + { + return c_; + } + const Number &d() const + { + return d_; + } + const Number &e() const + { + return e_; + } + const Number &f() const + { + return f_; + } + + private: + Number a_; + Number b_; + Number c_; + Number d_; + Number e_; + Number f_; + }; - public: - const LengthPercentage &x() const - { - return x_; - } - const LengthPercentage &y() const - { - return y_; - } + template + class GenericMatrix3D + { + public: + GenericMatrix3D(Number m11, Number m12, Number m13, Number m14, Number m21, Number m22, Number m23, Number m24, Number m31, Number m32, Number m33, Number m34, Number m41, Number m42, Number m43, Number m44) + : m11_(m11) + , m12_(m12) + , m13_(m13) + , m14_(m14) + , m21_(m21) + , m22_(m22) + , m23_(m23) + , m24_(m24) + , m31_(m31) + , m32_(m32) + , m33_(m33) + , m34_(m34) + , m41_(m41) + , m42_(m42) + , m43_(m43) + , m44_(m44) + { + } + + public: + const Number &m11() const + { + return m11_; + } + const Number &m12() const + { + return m12_; + } + const Number &m13() const + { + return m13_; + } + const Number &m14() const + { + return m14_; + } + const Number &m21() const + { + return m21_; + } + const Number &m22() const + { + return m22_; + } + const Number &m23() const + { + return m23_; + } + const Number &m24() const + { + return m24_; + } + const Number &m31() const + { + return m31_; + } + const Number &m32() const + { + return m32_; + } + const Number &m33() const + { + return m33_; + } + const Number &m34() const + { + return m34_; + } + const Number &m41() const + { + return m41_; + } + const Number &m42() const + { + return m42_; + } + const Number &m43() const + { + return m43_; + } + const Number &m44() const + { + return m44_; + } + + private: + Number m11_; + Number m12_; + Number m13_; + Number m14_; + Number m21_; + Number m22_; + Number m23_; + Number m24_; + Number m31_; + Number m32_; + Number m33_; + Number m34_; + Number m41_; + Number m42_; + Number m43_; + Number m44_; + }; - private: - LengthPercentage x_; - LengthPercentage y_; - }; + template + class GenericSkew + { + public: + GenericSkew(Angle x, Angle y) + : x_(x) + , y_(y) + { + } + + public: + const Angle &x() const + { + return x_; + } + const Angle &y() const + { + return y_; + } + + private: + Angle x_; + Angle y_; + }; - template - class GenericTranslateX - { - public: - GenericTranslateX(LengthPercentage x) - : x_(x) + template + class GenericSkewX { - } + public: + GenericSkewX(Angle angle) + : angle_(angle) + { + } - public: - const LengthPercentage &x() const - { - return x_; - } + public: + const Angle &angle() const + { + return angle_; + } - private: - LengthPercentage x_; - }; + private: + Angle angle_; + }; - template - class GenericTranslateY - { - public: - GenericTranslateY(LengthPercentage y) - : y_(y) + template + class GenericSkewY { - } + public: + GenericSkewY(Angle angle) + : angle_(angle) + { + } - public: - const LengthPercentage &y() const - { - return y_; - } + public: + const Angle &angle() const + { + return angle_; + } - private: - LengthPercentage y_; - }; + private: + Angle angle_; + }; - template - class GenericTranslateZ - { - public: - GenericTranslateZ(Length z) - : z_(z) - { - } + template + class GenericTranslate + { + public: + GenericTranslate(LengthPercentage x, LengthPercentage y) + : x_(x) + , y_(y) + { + } + + public: + const LengthPercentage &x() const + { + return x_; + } + const LengthPercentage &y() const + { + return y_; + } + + private: + LengthPercentage x_; + LengthPercentage y_; + }; - public: - const Length &z() const + template + class GenericTranslateX { - return z_; - } + public: + GenericTranslateX(LengthPercentage x) + : x_(x) + { + } - private: - Length z_; - }; + public: + const LengthPercentage &x() const + { + return x_; + } - template - class GenericTranslate3D - { - public: - GenericTranslate3D(LengthPercentage x, LengthPercentage y, Length z) - : x_(x) - , y_(y) - , z_(z) - { - } + private: + LengthPercentage x_; + }; - public: - const LengthPercentage &x() const - { - return x_; - } - const LengthPercentage &y() const - { - return y_; - } - const Length &z() const + template + class GenericTranslateY { - return z_; - } + public: + GenericTranslateY(LengthPercentage y) + : y_(y) + { + } - private: - LengthPercentage x_; - LengthPercentage y_; - Length z_; - }; + public: + const LengthPercentage &y() const + { + return y_; + } - template - class GenericScale - { - public: - GenericScale(Number number) - : number_(number) - { - } + private: + LengthPercentage y_; + }; - public: - const Number &number() const + template + class GenericTranslateZ { - return number_; - } - - private: - Number number_; - }; + public: + GenericTranslateZ(Length z) + : z_(z) + { + } - template - class GenericScaleX - { - public: - GenericScaleX(Number x) - : x_(x) - { - } + public: + const Length &z() const + { + return z_; + } - public: - const Number &x() const - { - return x_; - } + private: + Length z_; + }; - private: - Number x_; - }; + template + class GenericTranslate3D + { + public: + GenericTranslate3D(LengthPercentage x, LengthPercentage y, Length z) + : x_(x) + , y_(y) + , z_(z) + { + } + + public: + const LengthPercentage &x() const + { + return x_; + } + const LengthPercentage &y() const + { + return y_; + } + const Length &z() const + { + return z_; + } + + private: + LengthPercentage x_; + LengthPercentage y_; + Length z_; + }; - template - class GenericScaleY - { - public: - GenericScaleY(Number y) - : y_(y) + template + class GenericScale { - } + public: + GenericScale(Number number) + : number_(number) + { + } - public: - const Number &y() const - { - return y_; - } + public: + const Number &number() const + { + return number_; + } - private: - Number y_; - }; + private: + Number number_; + }; - template - class GenericScaleZ - { - public: - GenericScaleZ(Number z) - : z_(z) + template + class GenericScaleX { - } + public: + GenericScaleX(Number x) + : x_(x) + { + } - public: - const Number &z() const - { - return z_; - } + public: + const Number &x() const + { + return x_; + } - private: - Number z_; - }; + private: + Number x_; + }; - template - class GenericScale3D - { - public: - GenericScale3D(Number x, Number y, Number z) - : x_(x) - , y_(y) - , z_(z) + template + class GenericScaleY { - } + public: + GenericScaleY(Number y) + : y_(y) + { + } - public: - const Number &x() const - { - return x_; - } - const Number &y() const - { - return y_; - } - const Number &z() const - { - return z_; - } + public: + const Number &y() const + { + return y_; + } - private: - Number x_; - Number y_; - Number z_; - }; + private: + Number y_; + }; - template - class GenericRotate - { - public: - GenericRotate(Angle angle) - : angle_(angle) + template + class GenericScaleZ { - } + public: + GenericScaleZ(Number z) + : z_(z) + { + } - public: - const Angle &angle() const - { - return angle_; - } + public: + const Number &z() const + { + return z_; + } - private: - Angle angle_; - }; + private: + Number z_; + }; - template - class GenericRotateX - { - public: - GenericRotateX(Angle angle) - : angle_(angle) - { - } + template + class GenericScale3D + { + public: + GenericScale3D(Number x, Number y, Number z) + : x_(x) + , y_(y) + , z_(z) + { + } + + public: + const Number &x() const + { + return x_; + } + const Number &y() const + { + return y_; + } + const Number &z() const + { + return z_; + } + + private: + Number x_; + Number y_; + Number z_; + }; - public: - const Angle &angle() const + template + class GenericRotate { - return angle_; - } + public: + GenericRotate(Angle angle) + : angle_(angle) + { + } - private: - Angle angle_; - }; + public: + const Angle &angle() const + { + return angle_; + } - template - class GenericRotateY - { - public: - GenericRotateY(Angle angle) - : angle_(angle) - { - } + private: + Angle angle_; + }; - public: - const Angle &angle() const + template + class GenericRotateX { - return angle_; - } + public: + GenericRotateX(Angle angle) + : angle_(angle) + { + } - private: - Angle angle_; - }; + public: + const Angle &angle() const + { + return angle_; + } - template - class GenericRotateZ - { - public: - GenericRotateZ(Angle angle) - : angle_(angle) - { - } + private: + Angle angle_; + }; - public: - const Angle &angle() const + template + class GenericRotateY { - return angle_; - } - - private: - Angle angle_; - }; + public: + GenericRotateY(Angle angle) + : angle_(angle) + { + } - template - class GenericRotate3D - { - public: - GenericRotate3D(Number x, Number y, Number z, Angle angle) - : x_(x) - , y_(y) - , z_(z) - , angle_(angle) - { - } + public: + const Angle &angle() const + { + return angle_; + } - public: - const Number &x() const - { - return x_; - } - const Number &y() const - { - return y_; - } - const Number &z() const - { - return z_; - } - const Angle &angle() const - { - return angle_; - } - - private: - Number x_; - Number y_; - Number z_; - Angle angle_; - }; - - template - class GenericTransformOperation - { - protected: - enum Tag - { - kEmpty, - kMatrix, - kMatrix3D, - kSkew, - kSkewX, - kSkewY, - kTranslate, - kTranslateX, - kTranslateY, - kTranslateZ, - kTranslate3D, - kScale, - kScaleX, - kScaleY, - kScaleZ, - kScale3D, - kRotate, - kRotateX, - kRotateY, - kRotateZ, - kRotate3D + private: + Angle angle_; }; - using TransformOperationVariant = std::variant< - std::monostate, // Placeholder for empty state - GenericMatrix, - GenericMatrix3D, - GenericSkew, - GenericSkewX, - GenericSkewY, - GenericTranslate, - GenericTranslateX, - GenericTranslateY, - GenericTranslateZ, - GenericTranslate3D, - GenericScale, - GenericScaleX, - GenericScaleY, - GenericScaleZ, - GenericScale3D, - GenericRotate, - GenericRotateX, - GenericRotateY, - GenericRotateZ, - GenericRotate3D>; - - public: - static T Matrix(Number a, Number b, Number c, Number d, Number e, Number f) - { - return T(kMatrix, GenericMatrix(a, b, c, d, e, f)); - } - static T Matrix3D(Number m11, Number m12, Number m13, Number m14, Number m21, Number m22, Number m23, Number m24, Number m31, Number m32, Number m33, Number m34, Number m41, Number m42, Number m43, Number m44) - { - return T(kMatrix3D, GenericMatrix3D(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44)); - } - static T Skew(Angle x, Angle y) - { - return T(kSkew, GenericSkew(x, y)); - } - static T SkewX(Angle angle) - { - return T(kSkewX, GenericSkewX(angle)); - } - static T SkewY(Angle angle) - { - return T(kSkewY, GenericSkewY(angle)); - } - static T Translate(LengthPercentage x, LengthPercentage y) - { - return T(kTranslate, GenericTranslate(x, y)); - } - static T TranslateX(LengthPercentage x) - { - return T(kTranslateX, GenericTranslateX(x)); - } - static T TranslateY(LengthPercentage y) - { - return T(kTranslateY, GenericTranslateY(y)); - } - static T TranslateZ(Length z) - { - return T(kTranslateZ, GenericTranslateZ(z)); - } - static T Translate3D(LengthPercentage x, LengthPercentage y, Length z) - { - return T(kTranslate3D, GenericTranslate3D(x, y, z)); - } - static T Scale(Number x, Number y) - { - return T(kScale, GenericScale(x)); - } - static T ScaleX(Number x) - { - return T(kScaleX, GenericScaleX(x)); - } - static T ScaleY(Number y) - { - return T(kScaleY, GenericScaleY(y)); - } - static T ScaleZ(Number z) - { - return T(kScaleZ, GenericScaleZ(z)); - } - static T Scale3D(Number x, Number y, Number z) - { - return T(kScale3D, GenericScale3D(x, y, z)); - } - static T Rotate(Angle angle) - { - return T(kRotate, GenericRotate(angle)); - } - static T RotateX(Angle angle) - { - return T(kRotateX, GenericRotateX(angle)); - } - static T RotateY(Angle angle) - { - return T(kRotateY, GenericRotateY(angle)); - } - static T RotateZ(Angle angle) - { - return T(kRotateZ, GenericRotateZ(angle)); - } - static T Rotate3D(Number x, Number y, Number z, Angle angle) - { - return T(kRotate3D, GenericRotate3D(x, y, z, angle)); - } - - public: - GenericTransformOperation() - : tag_(kEmpty) - , // Default to matrix - operation_(std::monostate()) // Default to identity matrix - { - } - GenericTransformOperation(const GenericTransformOperation &other) - : tag_(other.tag_) - , operation_(other.operation_) - { - } - protected: - GenericTransformOperation(Tag tag, TransformOperationVariant operation) - : tag_(tag) - , operation_(std::move(operation)) - { - } - - public: - inline const bool isEmpty() const - { - return std::holds_alternative(operation_); - } - inline const bool isMatrix() const - { - return tag_ == kMatrix; - } - inline const bool isMatrix3D() const - { - return tag_ == kMatrix3D; - } - inline const bool isSkew() const - { - return tag_ == kSkew; - } - inline const bool isSkewX() const + template + class GenericRotateZ { - return tag_ == kSkewX; - } - inline const bool isSkewY() const - { - return tag_ == kSkewY; - } - inline const bool isTranslate() const - { - return tag_ == kTranslate; - } - inline const bool isTranslateX() const - { - return tag_ == kTranslateX; - } - inline const bool isTranslateY() const - { - return tag_ == kTranslateY; - } - inline const bool isTranslateZ() const - { - return tag_ == kTranslateZ; - } - inline const bool isTranslate3D() const - { - return tag_ == kTranslate3D; - } - inline const bool isScale() const - { - return tag_ == kScale; - } - inline const bool isScaleX() const - { - return tag_ == kScaleX; - } - inline const bool isScaleY() const - { - return tag_ == kScaleY; - } - inline const bool isScaleZ() const - { - return tag_ == kScaleZ; - } - inline const bool isScale3D() const - { - return tag_ == kScale3D; - } - inline const bool isRotate() const - { - return tag_ == kRotate; - } - inline const bool isRotateX() const - { - return tag_ == kRotateX; - } - inline const bool isRotateY() const - { - return tag_ == kRotateY; - } - inline const bool isRotateZ() const - { - return tag_ == kRotateZ; - } - inline const bool isRotate3D() const - { - return tag_ == kRotate3D; - } + public: + GenericRotateZ(Angle angle) + : angle_(angle) + { + } - const GenericMatrix &getMatrix() const - { - return std::get>(operation_); - } - const GenericMatrix3D &getMatrix3D() const - { - return std::get>(operation_); - } - const GenericSkew &getSkew() const - { - return std::get>(operation_); - } - const GenericSkewX &getSkewX() const - { - return std::get>(operation_); - } - const GenericSkewY &getSkewY() const - { - return std::get>(operation_); - } - const GenericTranslate &getTranslate() const - { - return std::get>(operation_); - } - const GenericTranslateX &getTranslateX() const - { - return std::get>(operation_); - } - const GenericTranslateY &getTranslateY() const - { - return std::get>(operation_); - } - const GenericTranslateZ &getTranslateZ() const - { - return std::get>(operation_); - } - const GenericTranslate3D &getTranslate3D() const - { - return std::get>(operation_); - } - const GenericScale &getScale() const - { - return std::get>(operation_); - } - const GenericScaleX &getScaleX() const - { - return std::get>(operation_); - } - const GenericScaleY &getScaleY() const - { - return std::get>(operation_); - } - const GenericScaleZ &getScaleZ() const - { - return std::get>(operation_); - } - const GenericScale3D &getScale3D() const - { - return std::get>(operation_); - } - const GenericRotate &getRotate() const - { - return std::get>(operation_); - } - const GenericRotateX &getRotateX() const - { - return std::get>(operation_); - } - const GenericRotateY &getRotateY() const - { - return std::get>(operation_); - } - const GenericRotateZ &getRotateZ() const - { - return std::get>(operation_); - } - const GenericRotate3D &getRotate3D() const - { - return std::get>(operation_); - } + public: + const Angle &angle() const + { + return angle_; + } - private: - Tag tag_; - TransformOperationVariant operation_; - }; + private: + Angle angle_; + }; - template - class GenericTransform - { - public: - GenericTransform() - : operations_() - { - } + template + class GenericRotate3D + { + public: + GenericRotate3D(Number x, Number y, Number z, Angle angle) + : x_(x) + , y_(y) + , z_(z) + , angle_(angle) + { + } + + public: + const Number &x() const + { + return x_; + } + const Number &y() const + { + return y_; + } + const Number &z() const + { + return z_; + } + const Angle &angle() const + { + return angle_; + } + + private: + Number x_; + Number y_; + Number z_; + Angle angle_; + }; - public: - inline const bool empty() const - { - return operations_.empty(); - } - inline std::vector &operations() - { - return operations_; - } - inline const std::vector &operations() const - { - return operations_; - } + template + class GenericTransformOperation + { + protected: + enum Tag + { + kEmpty, + kMatrix, + kMatrix3D, + kSkew, + kSkewX, + kSkewY, + kTranslate, + kTranslateX, + kTranslateY, + kTranslateZ, + kTranslate3D, + kScale, + kScaleX, + kScaleY, + kScaleZ, + kScale3D, + kRotate, + kRotateX, + kRotateY, + kRotateZ, + kRotate3D + }; + using TransformOperationVariant = std::variant< + std::monostate, // Placeholder for empty state + GenericMatrix, + GenericMatrix3D, + GenericSkew, + GenericSkewX, + GenericSkewY, + GenericTranslate, + GenericTranslateX, + GenericTranslateY, + GenericTranslateZ, + GenericTranslate3D, + GenericScale, + GenericScaleX, + GenericScaleY, + GenericScaleZ, + GenericScale3D, + GenericRotate, + GenericRotateX, + GenericRotateY, + GenericRotateZ, + GenericRotate3D>; + + public: + static T Matrix(Number a, Number b, Number c, Number d, Number e, Number f) + { + return T(kMatrix, GenericMatrix(a, b, c, d, e, f)); + } + static T Matrix3D(Number m11, Number m12, Number m13, Number m14, Number m21, Number m22, Number m23, Number m24, Number m31, Number m32, Number m33, Number m34, Number m41, Number m42, Number m43, Number m44) + { + return T(kMatrix3D, GenericMatrix3D(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44)); + } + static T Skew(Angle x, Angle y) + { + return T(kSkew, GenericSkew(x, y)); + } + static T SkewX(Angle angle) + { + return T(kSkewX, GenericSkewX(angle)); + } + static T SkewY(Angle angle) + { + return T(kSkewY, GenericSkewY(angle)); + } + static T Translate(LengthPercentage x, LengthPercentage y) + { + return T(kTranslate, GenericTranslate(x, y)); + } + static T TranslateX(LengthPercentage x) + { + return T(kTranslateX, GenericTranslateX(x)); + } + static T TranslateY(LengthPercentage y) + { + return T(kTranslateY, GenericTranslateY(y)); + } + static T TranslateZ(Length z) + { + return T(kTranslateZ, GenericTranslateZ(z)); + } + static T Translate3D(LengthPercentage x, LengthPercentage y, Length z) + { + return T(kTranslate3D, GenericTranslate3D(x, y, z)); + } + static T Scale(Number x, Number y) + { + return T(kScale, GenericScale(x)); + } + static T ScaleX(Number x) + { + return T(kScaleX, GenericScaleX(x)); + } + static T ScaleY(Number y) + { + return T(kScaleY, GenericScaleY(y)); + } + static T ScaleZ(Number z) + { + return T(kScaleZ, GenericScaleZ(z)); + } + static T Scale3D(Number x, Number y, Number z) + { + return T(kScale3D, GenericScale3D(x, y, z)); + } + static T Rotate(Angle angle) + { + return T(kRotate, GenericRotate(angle)); + } + static T RotateX(Angle angle) + { + return T(kRotateX, GenericRotateX(angle)); + } + static T RotateY(Angle angle) + { + return T(kRotateY, GenericRotateY(angle)); + } + static T RotateZ(Angle angle) + { + return T(kRotateZ, GenericRotateZ(angle)); + } + static T Rotate3D(Number x, Number y, Number z, Angle angle) + { + return T(kRotate3D, GenericRotate3D(x, y, z, angle)); + } + + public: + GenericTransformOperation() + : tag_(kEmpty) + , // Default to matrix + operation_(std::monostate()) // Default to identity matrix + { + } + GenericTransformOperation(const GenericTransformOperation &other) + : tag_(other.tag_) + , operation_(other.operation_) + { + } + + protected: + GenericTransformOperation(Tag tag, TransformOperationVariant operation) + : tag_(tag) + , operation_(std::move(operation)) + { + } + + public: + inline const bool isEmpty() const + { + return std::holds_alternative(operation_); + } + inline const bool isMatrix() const + { + return tag_ == kMatrix; + } + inline const bool isMatrix3D() const + { + return tag_ == kMatrix3D; + } + inline const bool isSkew() const + { + return tag_ == kSkew; + } + inline const bool isSkewX() const + { + return tag_ == kSkewX; + } + inline const bool isSkewY() const + { + return tag_ == kSkewY; + } + inline const bool isTranslate() const + { + return tag_ == kTranslate; + } + inline const bool isTranslateX() const + { + return tag_ == kTranslateX; + } + inline const bool isTranslateY() const + { + return tag_ == kTranslateY; + } + inline const bool isTranslateZ() const + { + return tag_ == kTranslateZ; + } + inline const bool isTranslate3D() const + { + return tag_ == kTranslate3D; + } + inline const bool isScale() const + { + return tag_ == kScale; + } + inline const bool isScaleX() const + { + return tag_ == kScaleX; + } + inline const bool isScaleY() const + { + return tag_ == kScaleY; + } + inline const bool isScaleZ() const + { + return tag_ == kScaleZ; + } + inline const bool isScale3D() const + { + return tag_ == kScale3D; + } + inline const bool isRotate() const + { + return tag_ == kRotate; + } + inline const bool isRotateX() const + { + return tag_ == kRotateX; + } + inline const bool isRotateY() const + { + return tag_ == kRotateY; + } + inline const bool isRotateZ() const + { + return tag_ == kRotateZ; + } + inline const bool isRotate3D() const + { + return tag_ == kRotate3D; + } + + const GenericMatrix &getMatrix() const + { + return std::get>(operation_); + } + const GenericMatrix3D &getMatrix3D() const + { + return std::get>(operation_); + } + const GenericSkew &getSkew() const + { + return std::get>(operation_); + } + const GenericSkewX &getSkewX() const + { + return std::get>(operation_); + } + const GenericSkewY &getSkewY() const + { + return std::get>(operation_); + } + const GenericTranslate &getTranslate() const + { + return std::get>(operation_); + } + const GenericTranslateX &getTranslateX() const + { + return std::get>(operation_); + } + const GenericTranslateY &getTranslateY() const + { + return std::get>(operation_); + } + const GenericTranslateZ &getTranslateZ() const + { + return std::get>(operation_); + } + const GenericTranslate3D &getTranslate3D() const + { + return std::get>(operation_); + } + const GenericScale &getScale() const + { + return std::get>(operation_); + } + const GenericScaleX &getScaleX() const + { + return std::get>(operation_); + } + const GenericScaleY &getScaleY() const + { + return std::get>(operation_); + } + const GenericScaleZ &getScaleZ() const + { + return std::get>(operation_); + } + const GenericScale3D &getScale3D() const + { + return std::get>(operation_); + } + const GenericRotate &getRotate() const + { + return std::get>(operation_); + } + const GenericRotateX &getRotateX() const + { + return std::get>(operation_); + } + const GenericRotateY &getRotateY() const + { + return std::get>(operation_); + } + const GenericRotateZ &getRotateZ() const + { + return std::get>(operation_); + } + const GenericRotate3D &getRotate3D() const + { + return std::get>(operation_); + } + + private: + Tag tag_; + TransformOperationVariant operation_; + }; - protected: - std::vector operations_; - }; -} + template + class GenericTransform + { + public: + GenericTransform() + : operations_() + { + } + + public: + inline const bool empty() const + { + return operations_.empty(); + } + inline std::vector &operations() + { + return operations_; + } + inline const std::vector &operations() const + { + return operations_; + } + + protected: + std::vector operations_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/generics/url.hpp b/src/client/cssom/values/generics/url.hpp index f250da813..54b12c148 100644 --- a/src/client/cssom/values/generics/url.hpp +++ b/src/client/cssom/values/generics/url.hpp @@ -2,35 +2,38 @@ #include -namespace client_cssom::values::generics +namespace endor { - template - class GenericUrlOrNone + namespace client_cssom::values::generics { - using T = GenericUrlOrNone; - - public: - static T None() - { - return T(); - } - static T Url(const U &url) + template + class GenericUrlOrNone { - return T(url); - } + using T = GenericUrlOrNone; - private: - GenericUrlOrNone(std::optional url = std::nullopt) - : url(url) - { - } + public: + static T None() + { + return T(); + } + static T Url(const U &url) + { + return T(url); + } - inline bool isNone() const - { - return !url.has_value(); - } + private: + GenericUrlOrNone(std::optional url = std::nullopt) + : url(url) + { + } + + inline bool isNone() const + { + return !url.has_value(); + } - public: - std::optional url = std::nullopt; - }; -} + public: + std::optional url = std::nullopt; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/align.hpp b/src/client/cssom/values/specified/align.hpp index 743b0bc99..5f0291eef 100644 --- a/src/client/cssom/values/specified/align.hpp +++ b/src/client/cssom/values/specified/align.hpp @@ -6,200 +6,340 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class AlignFlags : public ToCss + namespace client_cssom::values::specified { - public: - enum Tag : uint8_t + class AlignFlags : public ToCss { - kAuto = 0, - kNormal, - kStart, - kEnd, - kFlexStart, - kFlexEnd, - kCenter, - kLeft, - kRight, - kBaseline, - kFirstBaseline, - kLastBaseline, - kStretch, - kSelfStart, - kSelfEnd, - kSpaceBetween, - kSpaceAround, - kSpaceEvenly, - kAnchorCenter, - }; + public: + enum Tag : uint8_t + { + kAuto = 0, + kNormal, + kStart, + kEnd, + kFlexStart, + kFlexEnd, + kCenter, + kLeft, + kRight, + kBaseline, + kFirstBaseline, + kLastBaseline, + kStretch, + kSelfStart, + kSelfEnd, + kSpaceBetween, + kSpaceAround, + kSpaceEvenly, + kAnchorCenter, + }; + + // Additional flags stored in the upper bits. + static constexpr uint8_t LEGACY = 1 << 5; + static constexpr uint8_t SAFE = 1 << 6; + static constexpr uint8_t UNSAFE = 1 << 7; + + // Mask for the above additional flags. + static constexpr uint8_t ADDITIONAL_MASK = 0b11100000; + + public: + AlignFlags(Tag tag = kAuto, std::optional extra_flags = std::nullopt) + { + set(tag, extra_flags); + } - // Additional flags stored in the upper bits. - static constexpr uint8_t LEGACY = 1 << 5; - static constexpr uint8_t SAFE = 1 << 6; - static constexpr uint8_t UNSAFE = 1 << 7; + public: + inline void set(Tag tag, std::optional extra_flags = std::nullopt) + { + flag_bits_ = static_cast(tag); + if (extra_flags.has_value()) + flag_bits_ |= (*extra_flags & ADDITIONAL_MASK); + } + inline Tag tag() const + { + return static_cast(flag_bits_ & ~ADDITIONAL_MASK); + } + inline uint8_t extraFlags() const + { + return flag_bits_ & ADDITIONAL_MASK; + } + inline bool isLegacy() const + { + return extraFlags() == LEGACY; + } + inline bool isSafe() const + { + return extraFlags() == SAFE; + } + inline bool isUnsafe() const + { + return extraFlags() == UNSAFE; + } - // Mask for the above additional flags. - static constexpr uint8_t ADDITIONAL_MASK = 0b11100000; + std::string toCss() const override + { + std::stringstream ss; + auto extra_flags = extraFlags(); + if (extra_flags == LEGACY) + ss << "legacy "; + else if (extra_flags == SAFE) + ss << "safe "; + else if (extra_flags == UNSAFE) + ss << "unsafe "; + + switch (tag()) + { + case kAuto: + ss << "auto"; + break; + case kNormal: + ss << "normal"; + break; + case kStart: + ss << "start"; + break; + case kEnd: + ss << "end"; + break; + case kFlexStart: + ss << "flex-start"; + break; + case kFlexEnd: + ss << "flex-end"; + break; + case kCenter: + ss << "center"; + break; + case kLeft: + ss << "left"; + break; + case kRight: + ss << "right"; + break; + case kBaseline: + ss << "baseline"; + break; + case kFirstBaseline: + ss << "first-baseline"; + break; + case kLastBaseline: + ss << "last-baseline"; + break; + case kStretch: + ss << "stretch"; + break; + case kSelfStart: + ss << "self-start"; + break; + case kSelfEnd: + ss << "self-end"; + break; + case kSpaceBetween: + ss << "space-between"; + break; + case kSpaceAround: + ss << "space-around"; + break; + case kSpaceEvenly: + ss << "space-evenly"; + break; + case kAnchorCenter: + ss << "anchor-center"; + break; + default: + ss << "unknown"; + break; + } + return ss.str(); + } - public: - AlignFlags(Tag tag = kAuto, std::optional extra_flags = std::nullopt) - { - set(tag, extra_flags); - } + private: + uint8_t flag_bits_; + }; - public: - inline void set(Tag tag, std::optional extra_flags = std::nullopt) - { - flag_bits_ = static_cast(tag); - if (extra_flags.has_value()) - flag_bits_ |= (*extra_flags & ADDITIONAL_MASK); - } - inline Tag tag() const - { - return static_cast(flag_bits_ & ~ADDITIONAL_MASK); - } - inline uint8_t extraFlags() const - { - return flag_bits_ & ADDITIONAL_MASK; - } - inline bool isLegacy() const + enum class AxisDirection { - return extraFlags() == LEGACY; - } - inline bool isSafe() const - { - return extraFlags() == SAFE; - } - inline bool isUnsafe() const - { - return extraFlags() == UNSAFE; - } + // Block direction + kBlock, + // Inline direction + kInline, + }; - std::string toCss() const override + template + class ContentDistribution : public AlignFlags, + public Parse { - std::stringstream ss; - auto extra_flags = extraFlags(); - if (extra_flags == LEGACY) - ss << "legacy "; - else if (extra_flags == SAFE) - ss << "safe "; - else if (extra_flags == UNSAFE) - ss << "unsafe "; - - switch (tag()) + friend class Parse; + using AlignFlags::AlignFlags; + + public: + static T Normal() { - case kAuto: - ss << "auto"; - break; - case kNormal: - ss << "normal"; - break; - case kStart: - ss << "start"; - break; - case kEnd: - ss << "end"; - break; - case kFlexStart: - ss << "flex-start"; - break; - case kFlexEnd: - ss << "flex-end"; - break; - case kCenter: - ss << "center"; - break; - case kLeft: - ss << "left"; - break; - case kRight: - ss << "right"; - break; - case kBaseline: - ss << "baseline"; - break; - case kFirstBaseline: - ss << "first-baseline"; - break; - case kLastBaseline: - ss << "last-baseline"; - break; - case kStretch: - ss << "stretch"; - break; - case kSelfStart: - ss << "self-start"; - break; - case kSelfEnd: - ss << "self-end"; - break; - case kSpaceBetween: - ss << "space-between"; - break; - case kSpaceAround: - ss << "space-around"; - break; - case kSpaceEvenly: - ss << "space-evenly"; - break; - case kAnchorCenter: - ss << "anchor-center"; - break; - default: - ss << "unknown"; - break; + return T(kNormal); } - return ss.str(); - } - private: - uint8_t flag_bits_; - }; + protected: + bool parse(const std::string &input) override + { + if (axis == AxisDirection::kBlock) + { + if (input == "baseline") + { + set(kBaseline); + return true; + } + else if (input == "first baseline") + { + set(kFirstBaseline); + return true; + } + else if (input == "last baseline") + { + set(kLastBaseline); + return true; + } + } + else + { + if (input == "left") + { + set(kLeft); + return true; + } + else if (input == "right") + { + set(kRight); + return true; + } + } - enum class AxisDirection - { - // Block direction - kBlock, - // Inline direction - kInline, - }; - - template - class ContentDistribution : public AlignFlags, - public Parse - { - friend class Parse; - using AlignFlags::AlignFlags; + // Basic keywords + if (input == "normal") + set(kNormal); + else if (input == "stretch") + set(kStretch); + // Positional alignment + else if (input == "center") + set(kCenter); + else if (input == "start") + set(kStart); + else if (input == "end") + set(kEnd); + else if (input == "flex-start") + set(kFlexStart); + else if (input == "flex-end") + set(kFlexEnd); + // Distributed alignment + else if (input == "space-between") + set(kSpaceBetween); + else if (input == "space-around") + set(kSpaceAround); + else if (input == "space-evenly") + set(kSpaceEvenly); + // Overflow alignment + else if (input == "safe center") + set(kCenter, SAFE); + else if (input == "unsafe center") + set(kCenter, UNSAFE); + + return true; + } + }; + + class AlignContent : public ContentDistribution + { + using ContentDistribution::ContentDistribution; + }; - public: - static T Normal() + class JustifyContent : public ContentDistribution { - return T(kNormal); - } + using ContentDistribution::ContentDistribution; + }; - protected: - bool parse(const std::string &input) override + template + class SelfAlignment : public AlignFlags, + public Parse { - if (axis == AxisDirection::kBlock) + friend class Parse; + using AlignFlags::AlignFlags; + + public: + static T Auto() { - if (input == "baseline") - { + return T(kAuto); + } + + protected: + bool parse(const std::string &input) override + { + // Base keywords + if (input == "auto") + set(kAuto); + else if (input == "normal") + set(kNormal); + // Positional alignment + else if (input == "center") + set(kCenter); + else if (input == "start") + set(kStart); + else if (input == "end") + set(kEnd); + else if (input == "flex-start") + set(kFlexStart); + else if (input == "flex-end") + set(kFlexEnd); + else if (input == "self-start") + set(kSelfStart); + else if (input == "self-end") + set(kSelfEnd); + else if (input == "anchor-center") + set(kAnchorCenter); + // Baseline alignment + else if (input == "baseline") set(kBaseline); - return true; - } else if (input == "first baseline") - { set(kFirstBaseline); - return true; - } else if (input == "last baseline") - { set(kLastBaseline); + else if (input == "stretch") + set(kStretch); + // Overflow alignment + else if (input == "safe center") + set(kCenter, SAFE); + else if (input == "unsafe center") + set(kCenter, UNSAFE); + + return true; + } + + // Returns whether this value is valid for both axis directions. + bool isValidOnBothAxes() const + { + switch (tag()) + { + case kLeft: + case kRight: + return false; + default: return true; } } - else + }; + + class AlignSelf : public SelfAlignment + { + using SelfAlignment::SelfAlignment; + }; + + class JustifySelf : public SelfAlignment + { + friend class Parse; + using SelfAlignment::SelfAlignment; + + private: + bool parse(const std::string &input) override { if (input == "left") { @@ -211,268 +351,131 @@ namespace client_cssom::values::specified set(kRight); return true; } + return SelfAlignment::parse(input); } + }; - // Basic keywords - if (input == "normal") - set(kNormal); - else if (input == "stretch") - set(kStretch); - // Positional alignment - else if (input == "center") - set(kCenter); - else if (input == "start") - set(kStart); - else if (input == "end") - set(kEnd); - else if (input == "flex-start") - set(kFlexStart); - else if (input == "flex-end") - set(kFlexEnd); - // Distributed alignment - else if (input == "space-between") - set(kSpaceBetween); - else if (input == "space-around") - set(kSpaceAround); - else if (input == "space-evenly") - set(kSpaceEvenly); - // Overflow alignment - else if (input == "safe center") - set(kCenter, SAFE); - else if (input == "unsafe center") - set(kCenter, UNSAFE); - - return true; - } - }; - - class AlignContent : public ContentDistribution - { - using ContentDistribution::ContentDistribution; - }; - - class JustifyContent : public ContentDistribution - { - using ContentDistribution::ContentDistribution; - }; - - template - class SelfAlignment : public AlignFlags, - public Parse - { - friend class Parse; - using AlignFlags::AlignFlags; - - public: - static T Auto() + class AlignItems : public AlignFlags, + public Parse { - return T(kAuto); - } + friend class Parse; + using AlignFlags::AlignFlags; - protected: - bool parse(const std::string &input) override - { - // Base keywords - if (input == "auto") - set(kAuto); - else if (input == "normal") - set(kNormal); - // Positional alignment - else if (input == "center") - set(kCenter); - else if (input == "start") - set(kStart); - else if (input == "end") - set(kEnd); - else if (input == "flex-start") - set(kFlexStart); - else if (input == "flex-end") - set(kFlexEnd); - else if (input == "self-start") - set(kSelfStart); - else if (input == "self-end") - set(kSelfEnd); - else if (input == "anchor-center") - set(kAnchorCenter); - // Baseline alignment - else if (input == "baseline") - set(kBaseline); - else if (input == "first baseline") - set(kFirstBaseline); - else if (input == "last baseline") - set(kLastBaseline); - else if (input == "stretch") - set(kStretch); - // Overflow alignment - else if (input == "safe center") - set(kCenter, SAFE); - else if (input == "unsafe center") - set(kCenter, UNSAFE); - - return true; - } - - // Returns whether this value is valid for both axis directions. - bool isValidOnBothAxes() const - { - switch (tag()) + public: + static AlignItems Normal() { - case kLeft: - case kRight: - return false; - default: - return true; + return AlignItems(kNormal); } - } - }; - class AlignSelf : public SelfAlignment - { - using SelfAlignment::SelfAlignment; - }; + private: + bool parse(const std::string &input) override + { + // Basic keywords + if (input == "normal") + set(kNormal); + else if (input == "stretch") + set(kStretch); + // Positional alignment + else if (input == "center") + set(kCenter); + else if (input == "start") + set(kStart); + else if (input == "end") + set(kEnd); + else if (input == "flex-start") + set(kFlexStart); + else if (input == "flex-end") + set(kFlexEnd); + else if (input == "self-start") + set(kSelfStart); + else if (input == "self-end") + set(kSelfEnd); + else if (input == "anchor-center") + set(kAnchorCenter); + // Baseline alignment + else if (input == "baseline") + set(kBaseline); + else if (input == "first-baseline") + set(kFirstBaseline); + else if (input == "last-baseline") + set(kLastBaseline); + // Overflow alignment + else if (input == "safe center") + set(kCenter, SAFE); + else if (input == "unsafe center") + set(kCenter, UNSAFE); - class JustifySelf : public SelfAlignment - { - friend class Parse; - using SelfAlignment::SelfAlignment; + return true; + } + }; - private: - bool parse(const std::string &input) override + class JustifyItems : public AlignFlags, + public Parse { - if (input == "left") + friend class Parse; + using AlignFlags::AlignFlags; + + public: + static JustifyItems Legacy() { - set(kLeft); - return true; + return JustifyItems(kAuto, LEGACY); } - else if (input == "right") + static JustifyItems Normal() { - set(kRight); - return true; + return JustifyItems(kNormal); } - return SelfAlignment::parse(input); - } - }; - - class AlignItems : public AlignFlags, - public Parse - { - friend class Parse; - using AlignFlags::AlignFlags; - - public: - static AlignItems Normal() - { - return AlignItems(kNormal); - } - - private: - bool parse(const std::string &input) override - { - // Basic keywords - if (input == "normal") - set(kNormal); - else if (input == "stretch") - set(kStretch); - // Positional alignment - else if (input == "center") - set(kCenter); - else if (input == "start") - set(kStart); - else if (input == "end") - set(kEnd); - else if (input == "flex-start") - set(kFlexStart); - else if (input == "flex-end") - set(kFlexEnd); - else if (input == "self-start") - set(kSelfStart); - else if (input == "self-end") - set(kSelfEnd); - else if (input == "anchor-center") - set(kAnchorCenter); - // Baseline alignment - else if (input == "baseline") - set(kBaseline); - else if (input == "first-baseline") - set(kFirstBaseline); - else if (input == "last-baseline") - set(kLastBaseline); - // Overflow alignment - else if (input == "safe center") - set(kCenter, SAFE); - else if (input == "unsafe center") - set(kCenter, UNSAFE); - - return true; - } - }; - - class JustifyItems : public AlignFlags, - public Parse - { - friend class Parse; - using AlignFlags::AlignFlags; - public: - static JustifyItems Legacy() - { - return JustifyItems(kAuto, LEGACY); - } - static JustifyItems Normal() - { - return JustifyItems(kNormal); - } + private: + bool parse(const std::string &input) override + { + // Basic keywords + if (input == "normal") + set(kNormal); + else if (input == "stretch") + set(kStretch); + // Positional alignment + else if (input == "center") + set(kCenter); + else if (input == "start") + set(kStart); + else if (input == "end") + set(kEnd); + else if (input == "flex-start") + set(kFlexStart); + else if (input == "flex-end") + set(kFlexEnd); + else if (input == "self-start") + set(kSelfStart); + else if (input == "self-end") + set(kSelfEnd); + else if (input == "left") + set(kLeft); + else if (input == "right") + set(kRight); + else if (input == "anchor-center") + set(kAnchorCenter); + // Baseline alignment + else if (input == "baseline") + set(kBaseline); + else if (input == "first-baseline") + set(kFirstBaseline); + else if (input == "last-baseline") + set(kLastBaseline); + // Overflow alignment + else if (input == "safe center") + set(kCenter, SAFE); + else if (input == "unsafe center") + set(kCenter, UNSAFE); + // Legacy alignment + else if (input == "legacy right") + set(kRight, LEGACY); + else if (input == "legacy left") + set(kLeft, LEGACY); + else if (input == "legacy center") + set(kCenter, LEGACY); - private: - bool parse(const std::string &input) override - { - // Basic keywords - if (input == "normal") - set(kNormal); - else if (input == "stretch") - set(kStretch); - // Positional alignment - else if (input == "center") - set(kCenter); - else if (input == "start") - set(kStart); - else if (input == "end") - set(kEnd); - else if (input == "flex-start") - set(kFlexStart); - else if (input == "flex-end") - set(kFlexEnd); - else if (input == "self-start") - set(kSelfStart); - else if (input == "self-end") - set(kSelfEnd); - else if (input == "left") - set(kLeft); - else if (input == "right") - set(kRight); - else if (input == "anchor-center") - set(kAnchorCenter); - // Baseline alignment - else if (input == "baseline") - set(kBaseline); - else if (input == "first-baseline") - set(kFirstBaseline); - else if (input == "last-baseline") - set(kLastBaseline); - // Overflow alignment - else if (input == "safe center") - set(kCenter, SAFE); - else if (input == "unsafe center") - set(kCenter, UNSAFE); - // Legacy alignment - else if (input == "legacy right") - set(kRight, LEGACY); - else if (input == "legacy left") - set(kLeft, LEGACY); - else if (input == "legacy center") - set(kCenter, LEGACY); - - return true; - } - }; -} + return true; + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/angle.hpp b/src/client/cssom/values/specified/angle.hpp index d3d8adbc0..df2ca903e 100644 --- a/src/client/cssom/values/specified/angle.hpp +++ b/src/client/cssom/values/specified/angle.hpp @@ -5,205 +5,208 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class Angle; - class AngleDimension + namespace client_cssom::values::specified { - friend class Angle; - - private: - enum Tag - { - kDeg, - kGrad, - kRad, - kTurn, - }; - - static constexpr const char *UNIT_DEG = "deg"; - static constexpr const char *UNIT_GRAD = "grad"; - static constexpr const char *UNIT_RAD = "rad"; - static constexpr const char *UNIT_TURN = "turn"; - - public: - static AngleDimension Deg(float value) + class Angle; + class AngleDimension { - return AngleDimension(kDeg, value); - } - static AngleDimension Grad(float value) - { - return AngleDimension(kGrad, value); - } - static AngleDimension Rad(float value) - { - return AngleDimension(kRad, value); - } - static AngleDimension Turn(float value) - { - return AngleDimension(kTurn, value); - } + friend class Angle; - private: - AngleDimension(Tag tag, float value = 0.0f) - : tag_(tag) - , unitless_value_(value) - { - } + private: + enum Tag + { + kDeg, + kGrad, + kRad, + kTurn, + }; + + static constexpr const char *UNIT_DEG = "deg"; + static constexpr const char *UNIT_GRAD = "grad"; + static constexpr const char *UNIT_RAD = "rad"; + static constexpr const char *UNIT_TURN = "turn"; + + public: + static AngleDimension Deg(float value) + { + return AngleDimension(kDeg, value); + } + static AngleDimension Grad(float value) + { + return AngleDimension(kGrad, value); + } + static AngleDimension Rad(float value) + { + return AngleDimension(kRad, value); + } + static AngleDimension Turn(float value) + { + return AngleDimension(kTurn, value); + } - public: - inline bool isDeg() const - { - return tag_ == kDeg; - } - inline bool isGrad() const - { - return tag_ == kGrad; - } - inline bool isRad() const - { - return tag_ == kRad; - } - inline bool isTurn() const - { - return tag_ == kTurn; - } - inline bool isZero() const - { - return unitless_value_ == 0.0f; - } + private: + AngleDimension(Tag tag, float value = 0.0f) + : tag_(tag) + , unitless_value_(value) + { + } - inline float unitlessValue() const - { - return unitless_value_; - } - inline const char *unit() const - { - switch (tag_) - { - case kDeg: - return UNIT_DEG; - case kGrad: - return UNIT_GRAD; - case kRad: - return UNIT_RAD; - case kTurn: - return UNIT_TURN; - } - assert(false && "Invalid tag."); - } - - // Returns the amount of degrees this angle represents. - float toDegrees() const - { - const float DEG_PER_RAD = 180.0f / M_PI; - const float DEG_PER_TURN = 360.0f; - const float DEG_PER_GRAD = 360.0f / 400.0f; + public: + inline bool isDeg() const + { + return tag_ == kDeg; + } + inline bool isGrad() const + { + return tag_ == kGrad; + } + inline bool isRad() const + { + return tag_ == kRad; + } + inline bool isTurn() const + { + return tag_ == kTurn; + } + inline bool isZero() const + { + return unitless_value_ == 0.0f; + } - switch (tag_) + inline float unitlessValue() const { - case kDeg: return unitless_value_; - case kRad: - return unitless_value_ * DEG_PER_RAD; - case kTurn: - return unitless_value_ * DEG_PER_TURN; - case kGrad: - return unitless_value_ * DEG_PER_GRAD; - } - assert(false && "Invalid tag."); - } - - private: - Tag tag_; - float unitless_value_; - }; - - class Angle : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; - - public: - static Angle Zero() - { - return Angle(AngleDimension::Deg(0.0f), false); - } - static Angle Deg(float value) - { - return Angle(AngleDimension::Deg(value), false); - } - static Angle Grad(float value) - { - return Angle(AngleDimension::Grad(value), false); - } - static Angle Rad(float value) - { - return Angle(AngleDimension::Rad(value), false); - } - static Angle Turn(float value) - { - return Angle(AngleDimension::Turn(value), false); - } + } + inline const char *unit() const + { + switch (tag_) + { + case kDeg: + return UNIT_DEG; + case kGrad: + return UNIT_GRAD; + case kRad: + return UNIT_RAD; + case kTurn: + return UNIT_TURN; + } + assert(false && "Invalid tag."); + } - public: - Angle() = default; + // Returns the amount of degrees this angle represents. + float toDegrees() const + { + const float DEG_PER_RAD = 180.0f / M_PI; + const float DEG_PER_TURN = 360.0f; + const float DEG_PER_GRAD = 360.0f / 400.0f; + + switch (tag_) + { + case kDeg: + return unitless_value_; + case kRad: + return unitless_value_ * DEG_PER_RAD; + case kTurn: + return unitless_value_ * DEG_PER_TURN; + case kGrad: + return unitless_value_ * DEG_PER_GRAD; + } + assert(false && "Invalid tag."); + } - private: - Angle(AngleDimension value, bool was_calculated) - : value_(value) - , was_calculated_(was_calculated) - { - } + private: + Tag tag_; + float unitless_value_; + }; - private: - bool parse(const std::string &input) override + class Angle : public Parse, + public ToCss, + public ToComputedValue { - if (!isdigit(input[0])) - return false; + friend class Parse; - if (input.ends_with(AngleDimension::UNIT_DEG)) + public: + static Angle Zero() + { + return Angle(AngleDimension::Deg(0.0f), false); + } + static Angle Deg(float value) { - value_ = AngleDimension::Deg(std::stof(input.substr(0, input.length() - 3))); - return true; + return Angle(AngleDimension::Deg(value), false); } - if (input.ends_with(AngleDimension::UNIT_GRAD)) + static Angle Grad(float value) { - value_ = AngleDimension::Grad(std::stof(input.substr(0, input.length() - 5))); - return true; + return Angle(AngleDimension::Grad(value), false); } - if (input.ends_with(AngleDimension::UNIT_RAD)) + static Angle Rad(float value) { - value_ = AngleDimension::Rad(std::stof(input.substr(0, input.length() - 4))); - return true; + return Angle(AngleDimension::Rad(value), false); } - if (input.ends_with(AngleDimension::UNIT_TURN)) + static Angle Turn(float value) { - value_ = AngleDimension::Turn(std::stof(input.substr(0, input.length() - 5))); - return true; + return Angle(AngleDimension::Turn(value), false); } - return false; - } - public: - inline bool isZero() const - { - return value_.isZero(); - } + public: + Angle() = default; - std::string toCss() const override - { - return std::to_string(value_.unitlessValue()) + value_.unit(); - } - computed::Angle toComputedValue(computed::Context &) const override - { - float degrees = value_.toDegrees(); - return computed::Angle::FromDegrees(std::isinf(degrees) ? 0.0f : degrees); - } - - private: - AngleDimension value_ = AngleDimension::Deg(0.0f); - bool was_calculated_ = false; - }; -} + private: + Angle(AngleDimension value, bool was_calculated) + : value_(value) + , was_calculated_(was_calculated) + { + } + + private: + bool parse(const std::string &input) override + { + if (!isdigit(input[0])) + return false; + + if (input.ends_with(AngleDimension::UNIT_DEG)) + { + value_ = AngleDimension::Deg(std::stof(input.substr(0, input.length() - 3))); + return true; + } + if (input.ends_with(AngleDimension::UNIT_GRAD)) + { + value_ = AngleDimension::Grad(std::stof(input.substr(0, input.length() - 5))); + return true; + } + if (input.ends_with(AngleDimension::UNIT_RAD)) + { + value_ = AngleDimension::Rad(std::stof(input.substr(0, input.length() - 4))); + return true; + } + if (input.ends_with(AngleDimension::UNIT_TURN)) + { + value_ = AngleDimension::Turn(std::stof(input.substr(0, input.length() - 5))); + return true; + } + return false; + } + + public: + inline bool isZero() const + { + return value_.isZero(); + } + + std::string toCss() const override + { + return std::to_string(value_.unitlessValue()) + value_.unit(); + } + computed::Angle toComputedValue(computed::Context &) const override + { + float degrees = value_.toDegrees(); + return computed::Angle::FromDegrees(std::isinf(degrees) ? 0.0f : degrees); + } + + private: + AngleDimension value_ = AngleDimension::Deg(0.0f); + bool was_calculated_ = false; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/animation.hpp b/src/client/cssom/values/specified/animation.hpp index 561e1dd44..904809706 100644 --- a/src/client/cssom/values/specified/animation.hpp +++ b/src/client/cssom/values/specified/animation.hpp @@ -5,113 +5,116 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class TransitionProperty : public generics::GenericTransitionProperty, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - using generics::GenericTransitionProperty::GenericTransitionProperty; - - private: - bool parse(const std::string &input_str) override + class TransitionProperty : public generics::GenericTransitionProperty, + public Parse, + public ToComputedValue { - auto non_custom_property_id = NonCustomPropertyId::Parse(input_str); - if (non_custom_property_id.has_value()) + friend class Parse; + using generics::GenericTransitionProperty::GenericTransitionProperty; + + private: + bool parse(const std::string &input_str) override { - tag_ = kNonCustom; - property_id_ = *non_custom_property_id; + auto non_custom_property_id = NonCustomPropertyId::Parse(input_str); + if (non_custom_property_id.has_value()) + { + tag_ = kNonCustom; + property_id_ = *non_custom_property_id; + } + else + { + tag_ = kCustom; + property_id_ = CustomPropertyId(input_str); + } + return true; } - else + + public: + computed::TransitionProperty toComputedValue(computed::Context &context) const override { - tag_ = kCustom; - property_id_ = CustomPropertyId(input_str); + if (tag_ == kNonCustom) + return computed::TransitionProperty(std::get(property_id_)); + else if (tag_ == kCustom) + return computed::TransitionProperty(std::get(property_id_)); + return computed::TransitionProperty(); // Unsupported case. } - return true; - } + }; - public: - computed::TransitionProperty toComputedValue(computed::Context &context) const override + class TransitionPropertySet : std::vector, + public Parse, + public ToCss, + public ToComputedValue> { - if (tag_ == kNonCustom) - return computed::TransitionProperty(std::get(property_id_)); - else if (tag_ == kCustom) - return computed::TransitionProperty(std::get(property_id_)); - return computed::TransitionProperty(); // Unsupported case. - } - }; + friend class Parse; - class TransitionPropertySet : std::vector, - public Parse, - public ToCss, - public ToComputedValue> - { - friend class Parse; + public: + TransitionPropertySet() = default; + TransitionPropertySet(const std::vector &properties) + : std::vector(properties) + { + } - public: - TransitionPropertySet() = default; - TransitionPropertySet(const std::vector &properties) - : std::vector(properties) - { - } + private: + bool parse(const std::string &input_str) override + { + // Clear the existing properties. + clear(); - private: - bool parse(const std::string &input_str) override - { - // Clear the existing properties. - clear(); + size_t start = 0; + size_t end = input_str.find(','); - size_t start = 0; - size_t end = input_str.find(','); + while (true) + { + std::string prop_str = input_str.substr(start, end - start); - while (true) - { - std::string prop_str = input_str.substr(start, end - start); + // Trim leading and trailing whitespace of the `prop` + prop_str.erase(prop_str.begin(), std::find_if(prop_str.begin(), prop_str.end(), [](int ch) + { return !std::isspace(ch); })); + prop_str.erase(std::find_if(prop_str.rbegin(), prop_str.rend(), [](int ch) + { return !std::isspace(ch); }) + .base(), + prop_str.end()); - // Trim leading and trailing whitespace of the `prop` - prop_str.erase(prop_str.begin(), std::find_if(prop_str.begin(), prop_str.end(), [](int ch) - { return !std::isspace(ch); })); - prop_str.erase(std::find_if(prop_str.rbegin(), prop_str.rend(), [](int ch) - { return !std::isspace(ch); }) - .base(), - prop_str.end()); + // Try inserting the property. + if (!prop_str.empty()) + push_back(Parse::ParseSingleValue(prop_str)); - // Try inserting the property. - if (!prop_str.empty()) - push_back(Parse::ParseSingleValue(prop_str)); + // Break if we reach the end of the string + if (end == std::string::npos) + { + break; + } - // Break if we reach the end of the string - if (end == std::string::npos) - { - break; + // Go next + start = end + 1; + end = input_str.find(',', start); } - - // Go next - start = end + 1; - end = input_str.find(',', start); + return true; } - return true; - } - public: - std::string toCss() const override - { - std::string result; - for (const auto &property : *this) + public: + std::string toCss() const override { - if (!result.empty()) - result += ", "; - result += property.toCss(); + std::string result; + for (const auto &property : *this) + { + if (!result.empty()) + result += ", "; + result += property.toCss(); + } + return result; } - return result; - } - std::vector toComputedValue(computed::Context &context) const override - { - std::vector computed_properties; - for (const auto &property : *this) - computed_properties.push_back(property.toComputedValue(context)); - return computed_properties; - } - }; -} + std::vector toComputedValue(computed::Context &context) const override + { + std::vector computed_properties; + for (const auto &property : *this) + computed_properties.push_back(property.toComputedValue(context)); + return computed_properties; + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/background.hpp b/src/client/cssom/values/specified/background.hpp index ca7900a0c..8a3651358 100644 --- a/src/client/cssom/values/specified/background.hpp +++ b/src/client/cssom/values/specified/background.hpp @@ -4,552 +4,555 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class BackgroundBlendMode : public generics::GenericBackgroundBlendMode, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - - public: - using generics::GenericBackgroundBlendMode::GenericBackgroundBlendMode; - - bool parse(const std::string &input) override + class BackgroundBlendMode : public generics::GenericBackgroundBlendMode, + public Parse, + public ToComputedValue { - if (input == "normal") - tag_ = kNormal; - else if (input == "multiply") - tag_ = kMultiply; - else if (input == "screen") - tag_ = kScreen; - else if (input == "overlay") - tag_ = kOverlay; - else if (input == "darken") - tag_ = kDarken; - else if (input == "lighten") - tag_ = kLighten; - else if (input == "color-dodge") - tag_ = kColorDodge; - else if (input == "color-burn") - tag_ = kColorBurn; - else if (input == "hard-light") - tag_ = kHardLight; - else if (input == "soft-light") - tag_ = kSoftLight; - else if (input == "difference") - tag_ = kDifference; - else if (input == "exclusion") - tag_ = kExclusion; - else if (input == "hue") - tag_ = kHue; - else if (input == "saturation") - tag_ = kSaturation; - else if (input == "color") - tag_ = kColor; - else if (input == "luminosity") - tag_ = kLuminosity; - else - return false; - return true; - } + friend class Parse; - computed::BackgroundBlendMode toComputedValue(computed::Context &) const override - { - if (isNormal()) - return computed::BackgroundBlendMode::Normal(); - else if (isMultiply()) - return computed::BackgroundBlendMode::Multiply(); - else if (isScreen()) - return computed::BackgroundBlendMode::Screen(); - else if (isOverlay()) - return computed::BackgroundBlendMode::Overlay(); - else if (isDarken()) - return computed::BackgroundBlendMode::Darken(); - else if (isLighten()) - return computed::BackgroundBlendMode::Lighten(); - else if (isColorDodge()) - return computed::BackgroundBlendMode::ColorDodge(); - else if (isColorBurn()) - return computed::BackgroundBlendMode::ColorBurn(); - else if (isHardLight()) - return computed::BackgroundBlendMode::HardLight(); - else if (isSoftLight()) - return computed::BackgroundBlendMode::SoftLight(); - else if (isDifference()) - return computed::BackgroundBlendMode::Difference(); - else if (isExclusion()) - return computed::BackgroundBlendMode::Exclusion(); - else if (isHue()) - return computed::BackgroundBlendMode::Hue(); - else if (isSaturation()) - return computed::BackgroundBlendMode::Saturation(); - else if (isColor()) - return computed::BackgroundBlendMode::Color(); - else if (isLuminosity()) - return computed::BackgroundBlendMode::Luminosity(); - - // Default to Normal if none match - return computed::BackgroundBlendMode::Normal(); - } - }; - - class BackgroundClip : public generics::GenericBackgroundClip, - public Parse, - public ToComputedValue - { - friend class Parse; + public: + using generics::GenericBackgroundBlendMode::GenericBackgroundBlendMode; - public: - using generics::GenericBackgroundClip::GenericBackgroundClip; + bool parse(const std::string &input) override + { + if (input == "normal") + tag_ = kNormal; + else if (input == "multiply") + tag_ = kMultiply; + else if (input == "screen") + tag_ = kScreen; + else if (input == "overlay") + tag_ = kOverlay; + else if (input == "darken") + tag_ = kDarken; + else if (input == "lighten") + tag_ = kLighten; + else if (input == "color-dodge") + tag_ = kColorDodge; + else if (input == "color-burn") + tag_ = kColorBurn; + else if (input == "hard-light") + tag_ = kHardLight; + else if (input == "soft-light") + tag_ = kSoftLight; + else if (input == "difference") + tag_ = kDifference; + else if (input == "exclusion") + tag_ = kExclusion; + else if (input == "hue") + tag_ = kHue; + else if (input == "saturation") + tag_ = kSaturation; + else if (input == "color") + tag_ = kColor; + else if (input == "luminosity") + tag_ = kLuminosity; + else + return false; + return true; + } - bool parse(const std::string &input) override - { - if (input == "border-box") - tag_ = kBorderBox; - else if (input == "padding-box") - tag_ = kPaddingBox; - else if (input == "content-box") - tag_ = kContentBox; - else if (input == "text") - tag_ = kText; - else - return false; - return true; - } + computed::BackgroundBlendMode toComputedValue(computed::Context &) const override + { + if (isNormal()) + return computed::BackgroundBlendMode::Normal(); + else if (isMultiply()) + return computed::BackgroundBlendMode::Multiply(); + else if (isScreen()) + return computed::BackgroundBlendMode::Screen(); + else if (isOverlay()) + return computed::BackgroundBlendMode::Overlay(); + else if (isDarken()) + return computed::BackgroundBlendMode::Darken(); + else if (isLighten()) + return computed::BackgroundBlendMode::Lighten(); + else if (isColorDodge()) + return computed::BackgroundBlendMode::ColorDodge(); + else if (isColorBurn()) + return computed::BackgroundBlendMode::ColorBurn(); + else if (isHardLight()) + return computed::BackgroundBlendMode::HardLight(); + else if (isSoftLight()) + return computed::BackgroundBlendMode::SoftLight(); + else if (isDifference()) + return computed::BackgroundBlendMode::Difference(); + else if (isExclusion()) + return computed::BackgroundBlendMode::Exclusion(); + else if (isHue()) + return computed::BackgroundBlendMode::Hue(); + else if (isSaturation()) + return computed::BackgroundBlendMode::Saturation(); + else if (isColor()) + return computed::BackgroundBlendMode::Color(); + else if (isLuminosity()) + return computed::BackgroundBlendMode::Luminosity(); + + // Default to Normal if none match + return computed::BackgroundBlendMode::Normal(); + } + }; - computed::BackgroundClip toComputedValue(computed::Context &) const override - { - if (isBorderBox()) - return computed::BackgroundClip::BorderBox(); - else if (isPaddingBox()) - return computed::BackgroundClip::PaddingBox(); - else if (isContentBox()) - return computed::BackgroundClip::ContentBox(); - else if (isText()) - return computed::BackgroundClip::Text(); - - // Default to BorderBox if none match - return computed::BackgroundClip::BorderBox(); - } - }; - - class BackgroundOrigin : public generics::GenericBackgroundOrigin, + class BackgroundClip : public generics::GenericBackgroundClip, public Parse, - public ToComputedValue - { - friend class Parse; - - public: - using generics::GenericBackgroundOrigin::GenericBackgroundOrigin; - - bool parse(const std::string &input) override + public ToComputedValue { - if (input == "padding-box") - tag_ = kPaddingBox; - else if (input == "border-box") - tag_ = kBorderBox; - else if (input == "content-box") - tag_ = kContentBox; - else - return false; - return true; - } - - computed::BackgroundOrigin toComputedValue(computed::Context &) const override - { - if (isPaddingBox()) - return computed::BackgroundOrigin::PaddingBox(); - else if (isBorderBox()) - return computed::BackgroundOrigin::BorderBox(); - else if (isContentBox()) - return computed::BackgroundOrigin::ContentBox(); - - // Default to PaddingBox if none match - return computed::BackgroundOrigin::PaddingBox(); - } - }; + friend class Parse; - class BackgroundRepeat : public generics::GenericBackgroundRepeat, - public Parse, - public ToComputedValue - { - friend class Parse; + public: + using generics::GenericBackgroundClip::GenericBackgroundClip; - public: - using generics::GenericBackgroundRepeat::GenericBackgroundRepeat; + bool parse(const std::string &input) override + { + if (input == "border-box") + tag_ = kBorderBox; + else if (input == "padding-box") + tag_ = kPaddingBox; + else if (input == "content-box") + tag_ = kContentBox; + else if (input == "text") + tag_ = kText; + else + return false; + return true; + } - bool parse(const std::string &input) override - { - if (input == "repeat") - tag_ = kRepeat; - else if (input == "repeat-x") - tag_ = kRepeatX; - else if (input == "repeat-y") - tag_ = kRepeatY; - else if (input == "no-repeat") - tag_ = kNoRepeat; - else if (input == "space") - tag_ = kSpace; - else if (input == "round") - tag_ = kRound; - else - return false; - return true; - } + computed::BackgroundClip toComputedValue(computed::Context &) const override + { + if (isBorderBox()) + return computed::BackgroundClip::BorderBox(); + else if (isPaddingBox()) + return computed::BackgroundClip::PaddingBox(); + else if (isContentBox()) + return computed::BackgroundClip::ContentBox(); + else if (isText()) + return computed::BackgroundClip::Text(); + + // Default to BorderBox if none match + return computed::BackgroundClip::BorderBox(); + } + }; - computed::BackgroundRepeat toComputedValue(computed::Context &) const override + class BackgroundOrigin : public generics::GenericBackgroundOrigin, + public Parse, + public ToComputedValue { - if (isRepeat()) - return computed::BackgroundRepeat::Repeat(); - else if (isRepeatX()) - return computed::BackgroundRepeat::RepeatX(); - else if (isRepeatY()) - return computed::BackgroundRepeat::RepeatY(); - else if (isNoRepeat()) - return computed::BackgroundRepeat::NoRepeat(); - else if (isSpace()) - return computed::BackgroundRepeat::Space(); - else if (isRound()) - return computed::BackgroundRepeat::Round(); - - // Default to Repeat if none match - return computed::BackgroundRepeat::Repeat(); - } - }; - - class BackgroundSize : public generics::GenericBackgroundSize, - public Parse, - public ToComputedValue - { - friend class Parse; + friend class Parse; - public: - using generics::GenericBackgroundSize::GenericBackgroundSize; + public: + using generics::GenericBackgroundOrigin::GenericBackgroundOrigin; - bool parse(const std::string &input) override - { - if (input == "auto") - tag_ = kAuto; - else if (input == "cover") - tag_ = kCover; - else if (input == "contain") - tag_ = kContain; - else if (input.find('%') != std::string::npos) + bool parse(const std::string &input) override { - tag_ = kPercentage; - width_ = std::stof(input.substr(0, input.find('%'))); + if (input == "padding-box") + tag_ = kPaddingBox; + else if (input == "border-box") + tag_ = kBorderBox; + else if (input == "content-box") + tag_ = kContentBox; + else + return false; + return true; } - else if (input.find("px") != std::string::npos) + + computed::BackgroundOrigin toComputedValue(computed::Context &) const override { - tag_ = kLength; - width_ = std::stof(input.substr(0, input.find("px"))); + if (isPaddingBox()) + return computed::BackgroundOrigin::PaddingBox(); + else if (isBorderBox()) + return computed::BackgroundOrigin::BorderBox(); + else if (isContentBox()) + return computed::BackgroundOrigin::ContentBox(); + + // Default to PaddingBox if none match + return computed::BackgroundOrigin::PaddingBox(); } - else - return false; - return true; - } + }; - computed::BackgroundSize toComputedValue(computed::Context &) const override - { - if (isAuto()) - return computed::BackgroundSize::Auto(); - else if (isCover()) - return computed::BackgroundSize::Cover(); - else if (isContain()) - return computed::BackgroundSize::Contain(); - else if (isLength()) - return computed::BackgroundSize::Length(width_); - else if (isPercentage()) - return computed::BackgroundSize::Percentage(width_); - else if (isTwoValues()) - return computed::BackgroundSize::TwoValues(width_, height_); - - // Default to Auto if none match - return computed::BackgroundSize::Auto(); - } - }; - - class BackgroundPosition : public generics::GenericBackgroundPosition, + class BackgroundRepeat : public generics::GenericBackgroundRepeat, public Parse, - public ToComputedValue - { - friend class Parse; - - public: - using generics::GenericBackgroundPosition::GenericBackgroundPosition; - - bool parse(const std::string &input) override + public ToComputedValue { - // Tokenize the input by splitting on spaces - std::vector tokens = tokenize(input); + friend class Parse; - if (tokens.empty()) - return false; + public: + using generics::GenericBackgroundRepeat::GenericBackgroundRepeat; - // Handle different syntax variations - if (tokens.size() == 1) - return parseOneValue(tokens[0]); - else if (tokens.size() == 2) - return parseTwoValues(tokens[0], tokens[1]); - else if (tokens.size() == 3) - return parseThreeValues(tokens[0], tokens[1], tokens[2]); - else if (tokens.size() == 4) - return parseFourValues(tokens[0], tokens[1], tokens[2], tokens[3]); - else - return false; - } + bool parse(const std::string &input) override + { + if (input == "repeat") + tag_ = kRepeat; + else if (input == "repeat-x") + tag_ = kRepeatX; + else if (input == "repeat-y") + tag_ = kRepeatY; + else if (input == "no-repeat") + tag_ = kNoRepeat; + else if (input == "space") + tag_ = kSpace; + else if (input == "round") + tag_ = kRound; + else + return false; + return true; + } + + computed::BackgroundRepeat toComputedValue(computed::Context &) const override + { + if (isRepeat()) + return computed::BackgroundRepeat::Repeat(); + else if (isRepeatX()) + return computed::BackgroundRepeat::RepeatX(); + else if (isRepeatY()) + return computed::BackgroundRepeat::RepeatY(); + else if (isNoRepeat()) + return computed::BackgroundRepeat::NoRepeat(); + else if (isSpace()) + return computed::BackgroundRepeat::Space(); + else if (isRound()) + return computed::BackgroundRepeat::Round(); + + // Default to Repeat if none match + return computed::BackgroundRepeat::Repeat(); + } + }; - private: - std::vector tokenize(const std::string &input) + class BackgroundSize : public generics::GenericBackgroundSize, + public Parse, + public ToComputedValue { - std::vector tokens; - std::string current_token; + friend class Parse; + + public: + using generics::GenericBackgroundSize::GenericBackgroundSize; - for (char c : input) + bool parse(const std::string &input) override { - if (c == ' ' || c == '\t') + if (input == "auto") + tag_ = kAuto; + else if (input == "cover") + tag_ = kCover; + else if (input == "contain") + tag_ = kContain; + else if (input.find('%') != std::string::npos) { - if (!current_token.empty()) - { - tokens.push_back(current_token); - current_token.clear(); - } + tag_ = kPercentage; + width_ = std::stof(input.substr(0, input.find('%'))); } - else + else if (input.find("px") != std::string::npos) { - current_token += c; + tag_ = kLength; + width_ = std::stof(input.substr(0, input.find("px"))); } + else + return false; + return true; } - if (!current_token.empty()) - tokens.push_back(current_token); - - return tokens; - } - - bool isKeyword(const std::string &token) - { - return token == "center" || token == "left" || token == "right" || - token == "top" || token == "bottom"; - } + computed::BackgroundSize toComputedValue(computed::Context &) const override + { + if (isAuto()) + return computed::BackgroundSize::Auto(); + else if (isCover()) + return computed::BackgroundSize::Cover(); + else if (isContain()) + return computed::BackgroundSize::Contain(); + else if (isLength()) + return computed::BackgroundSize::Length(width_); + else if (isPercentage()) + return computed::BackgroundSize::Percentage(width_); + else if (isTwoValues()) + return computed::BackgroundSize::TwoValues(width_, height_); + + // Default to Auto if none match + return computed::BackgroundSize::Auto(); + } + }; - bool isHorizontalKeyword(const std::string &token) + class BackgroundPosition : public generics::GenericBackgroundPosition, + public Parse, + public ToComputedValue { - return token == "left" || token == "right" || token == "center"; - } + friend class Parse; - bool isVerticalKeyword(const std::string &token) - { - return token == "top" || token == "bottom" || token == "center"; - } + public: + using generics::GenericBackgroundPosition::GenericBackgroundPosition; - generics::BackgroundPositionKeyword stringToKeyword(const std::string &token) - { - if (token == "center") - return generics::BackgroundPositionKeyword::kCenterKeyword; - if (token == "left") - return generics::BackgroundPositionKeyword::kLeftKeyword; - if (token == "right") - return generics::BackgroundPositionKeyword::kRightKeyword; - if (token == "top") - return generics::BackgroundPositionKeyword::kTopKeyword; - if (token == "bottom") - return generics::BackgroundPositionKeyword::kBottomKeyword; - return generics::BackgroundPositionKeyword::kNone; - } - - bool isLengthOrPercentage(const std::string &token) - { - return token.find("px") != std::string::npos || - token.find("%") != std::string::npos || - (std::isdigit(token[0]) || token[0] == '-' || token[0] == '+'); - } - - float parseValue(const std::string &token) - { - if (token.find("px") != std::string::npos) - return std::stof(token.substr(0, token.find("px"))); - else if (token.find("%") != std::string::npos) - return std::stof(token.substr(0, token.find("%"))); - else - return std::stof(token); - } - - bool parseOneValue(const std::string &token) - { - if (token == "center") + bool parse(const std::string &input) override { - tag_ = kCenter; - return true; + // Tokenize the input by splitting on spaces + std::vector tokens = tokenize(input); + + if (tokens.empty()) + return false; + + // Handle different syntax variations + if (tokens.size() == 1) + return parseOneValue(tokens[0]); + else if (tokens.size() == 2) + return parseTwoValues(tokens[0], tokens[1]); + else if (tokens.size() == 3) + return parseThreeValues(tokens[0], tokens[1], tokens[2]); + else if (tokens.size() == 4) + return parseFourValues(tokens[0], tokens[1], tokens[2], tokens[3]); + else + return false; } - else if (token == "left") + + private: + std::vector tokenize(const std::string &input) { - tag_ = kLeft; - return true; + std::vector tokens; + std::string current_token; + + for (char c : input) + { + if (c == ' ' || c == '\t') + { + if (!current_token.empty()) + { + tokens.push_back(current_token); + current_token.clear(); + } + } + else + { + current_token += c; + } + } + + if (!current_token.empty()) + tokens.push_back(current_token); + + return tokens; } - else if (token == "right") + + bool isKeyword(const std::string &token) { - tag_ = kRight; - return true; + return token == "center" || token == "left" || token == "right" || + token == "top" || token == "bottom"; } - else if (token == "top") + + bool isHorizontalKeyword(const std::string &token) { - tag_ = kTop; - return true; + return token == "left" || token == "right" || token == "center"; } - else if (token == "bottom") + + bool isVerticalKeyword(const std::string &token) { - tag_ = kBottom; - return true; + return token == "top" || token == "bottom" || token == "center"; } - else if (token.find('%') != std::string::npos) + + generics::BackgroundPositionKeyword stringToKeyword(const std::string &token) { - tag_ = kPercentage; - x_ = parseValue(token); - return true; + if (token == "center") + return generics::BackgroundPositionKeyword::kCenterKeyword; + if (token == "left") + return generics::BackgroundPositionKeyword::kLeftKeyword; + if (token == "right") + return generics::BackgroundPositionKeyword::kRightKeyword; + if (token == "top") + return generics::BackgroundPositionKeyword::kTopKeyword; + if (token == "bottom") + return generics::BackgroundPositionKeyword::kBottomKeyword; + return generics::BackgroundPositionKeyword::kNone; } - else if (isLengthOrPercentage(token)) + + bool isLengthOrPercentage(const std::string &token) { - tag_ = kLength; - x_ = parseValue(token); - return true; + return token.find("px") != std::string::npos || + token.find("%") != std::string::npos || + (std::isdigit(token[0]) || token[0] == '-' || token[0] == '+'); } - return false; - } - - bool parseTwoValues(const std::string &token1, const std::string &token2) - { - // Two values: horizontal vertical - float x_val = 0, y_val = 0; - - // Parse first value (horizontal) - if (token1 == "left") - x_val = 0; - else if (token1 == "center") - x_val = 50; // 50% equivalent - else if (token1 == "right") - x_val = 100; // 100% equivalent - else if (isLengthOrPercentage(token1)) - x_val = parseValue(token1); - else - return false; - - // Parse second value (vertical) - if (token2 == "top") - y_val = 0; - else if (token2 == "center") - y_val = 50; // 50% equivalent - else if (token2 == "bottom") - y_val = 100; // 100% equivalent - else if (isLengthOrPercentage(token2)) - y_val = parseValue(token2); - else - return false; - - tag_ = kTwoValues; - x_ = x_val; - y_ = y_val; - return true; - } - - bool parseThreeValues(const std::string &token1, const std::string &token2, const std::string &token3) - { - // Three values: keyword offset keyword - if (!isKeyword(token1) || !isLengthOrPercentage(token2) || !isKeyword(token3)) - return false; - // Validate that we have one horizontal and one vertical keyword - bool first_horizontal = isHorizontalKeyword(token1); - bool third_vertical = isVerticalKeyword(token3); - bool first_vertical = isVerticalKeyword(token1); - bool third_horizontal = isHorizontalKeyword(token3); + float parseValue(const std::string &token) + { + if (token.find("px") != std::string::npos) + return std::stof(token.substr(0, token.find("px"))); + else if (token.find("%") != std::string::npos) + return std::stof(token.substr(0, token.find("%"))); + else + return std::stof(token); + } - if ((first_horizontal && third_vertical) || (first_vertical && third_horizontal)) + bool parseOneValue(const std::string &token) { - tag_ = kThreeValues; - if (first_horizontal) + if (token == "center") { - h_keyword_ = stringToKeyword(token1); - h_offset_ = parseValue(token2); - v_keyword_ = stringToKeyword(token3); - v_offset_ = 0.0f; + tag_ = kCenter; + return true; } - else + else if (token == "left") { - v_keyword_ = stringToKeyword(token1); - v_offset_ = parseValue(token2); - h_keyword_ = stringToKeyword(token3); - h_offset_ = 0.0f; + tag_ = kLeft; + return true; } - return true; + else if (token == "right") + { + tag_ = kRight; + return true; + } + else if (token == "top") + { + tag_ = kTop; + return true; + } + else if (token == "bottom") + { + tag_ = kBottom; + return true; + } + else if (token.find('%') != std::string::npos) + { + tag_ = kPercentage; + x_ = parseValue(token); + return true; + } + else if (isLengthOrPercentage(token)) + { + tag_ = kLength; + x_ = parseValue(token); + return true; + } + return false; } - return false; - } - - bool parseFourValues(const std::string &token1, const std::string &token2, const std::string &token3, const std::string &token4) - { - // Four values: keyword offset keyword offset - if (!isKeyword(token1) || !isLengthOrPercentage(token2) || - !isKeyword(token3) || !isLengthOrPercentage(token4)) - return false; + bool parseTwoValues(const std::string &token1, const std::string &token2) + { + // Two values: horizontal vertical + float x_val = 0, y_val = 0; + + // Parse first value (horizontal) + if (token1 == "left") + x_val = 0; + else if (token1 == "center") + x_val = 50; // 50% equivalent + else if (token1 == "right") + x_val = 100; // 100% equivalent + else if (isLengthOrPercentage(token1)) + x_val = parseValue(token1); + else + return false; + + // Parse second value (vertical) + if (token2 == "top") + y_val = 0; + else if (token2 == "center") + y_val = 50; // 50% equivalent + else if (token2 == "bottom") + y_val = 100; // 100% equivalent + else if (isLengthOrPercentage(token2)) + y_val = parseValue(token2); + else + return false; - // Validate that we have one horizontal and one vertical keyword - bool first_horizontal = isHorizontalKeyword(token1); - bool third_vertical = isVerticalKeyword(token3); - bool first_vertical = isVerticalKeyword(token1); - bool third_horizontal = isHorizontalKeyword(token3); + tag_ = kTwoValues; + x_ = x_val; + y_ = y_val; + return true; + } - if ((first_horizontal && third_vertical) || (first_vertical && third_horizontal)) + bool parseThreeValues(const std::string &token1, const std::string &token2, const std::string &token3) { - tag_ = kFourValues; - if (first_horizontal) + // Three values: keyword offset keyword + if (!isKeyword(token1) || !isLengthOrPercentage(token2) || !isKeyword(token3)) + return false; + + // Validate that we have one horizontal and one vertical keyword + bool first_horizontal = isHorizontalKeyword(token1); + bool third_vertical = isVerticalKeyword(token3); + bool first_vertical = isVerticalKeyword(token1); + bool third_horizontal = isHorizontalKeyword(token3); + + if ((first_horizontal && third_vertical) || (first_vertical && third_horizontal)) { - h_keyword_ = stringToKeyword(token1); - h_offset_ = parseValue(token2); - v_keyword_ = stringToKeyword(token3); - v_offset_ = parseValue(token4); + tag_ = kThreeValues; + if (first_horizontal) + { + h_keyword_ = stringToKeyword(token1); + h_offset_ = parseValue(token2); + v_keyword_ = stringToKeyword(token3); + v_offset_ = 0.0f; + } + else + { + v_keyword_ = stringToKeyword(token1); + v_offset_ = parseValue(token2); + h_keyword_ = stringToKeyword(token3); + h_offset_ = 0.0f; + } + return true; } - else + + return false; + } + + bool parseFourValues(const std::string &token1, const std::string &token2, const std::string &token3, const std::string &token4) + { + // Four values: keyword offset keyword offset + if (!isKeyword(token1) || !isLengthOrPercentage(token2) || + !isKeyword(token3) || !isLengthOrPercentage(token4)) + return false; + + // Validate that we have one horizontal and one vertical keyword + bool first_horizontal = isHorizontalKeyword(token1); + bool third_vertical = isVerticalKeyword(token3); + bool first_vertical = isVerticalKeyword(token1); + bool third_horizontal = isHorizontalKeyword(token3); + + if ((first_horizontal && third_vertical) || (first_vertical && third_horizontal)) { - v_keyword_ = stringToKeyword(token1); - v_offset_ = parseValue(token2); - h_keyword_ = stringToKeyword(token3); - h_offset_ = parseValue(token4); + tag_ = kFourValues; + if (first_horizontal) + { + h_keyword_ = stringToKeyword(token1); + h_offset_ = parseValue(token2); + v_keyword_ = stringToKeyword(token3); + v_offset_ = parseValue(token4); + } + else + { + v_keyword_ = stringToKeyword(token1); + v_offset_ = parseValue(token2); + h_keyword_ = stringToKeyword(token3); + h_offset_ = parseValue(token4); + } + return true; } - return true; - } - return false; - } + return false; + } - public: - computed::BackgroundPosition toComputedValue(computed::Context &) const override - { - if (isCenter()) + public: + computed::BackgroundPosition toComputedValue(computed::Context &) const override + { + if (isCenter()) + return computed::BackgroundPosition::Center(); + else if (isLeft()) + return computed::BackgroundPosition::Left(); + else if (isRight()) + return computed::BackgroundPosition::Right(); + else if (isTop()) + return computed::BackgroundPosition::Top(); + else if (isBottom()) + return computed::BackgroundPosition::Bottom(); + else if (isLength()) + return computed::BackgroundPosition::Length(x_); + else if (isPercentage()) + return computed::BackgroundPosition::Percentage(x_); + else if (isTwoValues()) + return computed::BackgroundPosition::TwoValues(x_, y_); + else if (isThreeValues()) + return computed::BackgroundPosition::ThreeValues(h_keyword_, h_offset_, v_keyword_); + else if (isFourValues()) + return computed::BackgroundPosition::FourValues(h_keyword_, h_offset_, v_keyword_, v_offset_); + + // Default to Center if none match return computed::BackgroundPosition::Center(); - else if (isLeft()) - return computed::BackgroundPosition::Left(); - else if (isRight()) - return computed::BackgroundPosition::Right(); - else if (isTop()) - return computed::BackgroundPosition::Top(); - else if (isBottom()) - return computed::BackgroundPosition::Bottom(); - else if (isLength()) - return computed::BackgroundPosition::Length(x_); - else if (isPercentage()) - return computed::BackgroundPosition::Percentage(x_); - else if (isTwoValues()) - return computed::BackgroundPosition::TwoValues(x_, y_); - else if (isThreeValues()) - return computed::BackgroundPosition::ThreeValues(h_keyword_, h_offset_, v_keyword_); - else if (isFourValues()) - return computed::BackgroundPosition::FourValues(h_keyword_, h_offset_, v_keyword_, v_offset_); - - // Default to Center if none match - return computed::BackgroundPosition::Center(); - } - }; -} + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/border.hpp b/src/client/cssom/values/specified/border.hpp index 7561a8d6c..3a1c8a737 100644 --- a/src/client/cssom/values/specified/border.hpp +++ b/src/client/cssom/values/specified/border.hpp @@ -5,243 +5,246 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class BorderStyle : public generics::GenericBorderStyle, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - using generics::GenericBorderStyle::GenericBorderStyle; - - private: - bool parse(const std::string &input) override - { - if (input == "hidden") - tag_ = kHidden; - else if (input == "none") - tag_ = kNone; - else if (input == "inset") - tag_ = kInset; - else if (input == "groove") - tag_ = kGroove; - else if (input == "outset") - tag_ = kOutset; - else if (input == "ridge") - tag_ = kRidge; - else if (input == "dotted") - tag_ = kDotted; - else if (input == "dashed") - tag_ = kDashed; - else if (input == "solid") - tag_ = kSolid; - else if (input == "double") - tag_ = kDouble; - return true; - } - - public: - BorderStyle toComputedValue(computed::Context &context) const override + class BorderStyle : public generics::GenericBorderStyle, + public Parse, + public ToComputedValue { - return *this; - } - }; + friend class Parse; + using generics::GenericBorderStyle::GenericBorderStyle; - class BorderSideWidth : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; + private: + bool parse(const std::string &input) override + { + if (input == "hidden") + tag_ = kHidden; + else if (input == "none") + tag_ = kNone; + else if (input == "inset") + tag_ = kInset; + else if (input == "groove") + tag_ = kGroove; + else if (input == "outset") + tag_ = kOutset; + else if (input == "ridge") + tag_ = kRidge; + else if (input == "dotted") + tag_ = kDotted; + else if (input == "dashed") + tag_ = kDashed; + else if (input == "solid") + tag_ = kSolid; + else if (input == "double") + tag_ = kDouble; + return true; + } - private: - enum Tag - { - kLength, - kThin, - kMedium, - kThick, + public: + BorderStyle toComputedValue(computed::Context &context) const override + { + return *this; + } }; - static constexpr float THIN_IN_PX = 1.0f; - static constexpr float MEDIUM_IN_PX = 3.0f; - static constexpr float THICK_IN_PX = 5.0f; - - public: - static BorderSideWidth Thin() + class BorderSideWidth : public Parse, + public ToCss, + public ToComputedValue { - return BorderSideWidth(kThin, THIN_IN_PX); - } - static BorderSideWidth Medium() - { - return BorderSideWidth(kMedium, MEDIUM_IN_PX); - } - static BorderSideWidth Thick() - { - return BorderSideWidth(kThick, THICK_IN_PX); - } + friend class Parse; - public: - BorderSideWidth() - : tag_(kMedium) - , length_(NoCalcLength::FromPx(MEDIUM_IN_PX)) - { - } - - private: - BorderSideWidth(Tag tag, float px) - : tag_(tag) - , length_(NoCalcLength::FromPx(px)) - { - } - - bool parse(const std::string &input) override - { - if (input == "thin") + private: + enum Tag + { + kLength, + kThin, + kMedium, + kThick, + }; + + static constexpr float THIN_IN_PX = 1.0f; + static constexpr float MEDIUM_IN_PX = 3.0f; + static constexpr float THICK_IN_PX = 5.0f; + + public: + static BorderSideWidth Thin() { - tag_ = kThin; - length_ = NoCalcLength::FromPx(THIN_IN_PX); + return BorderSideWidth(kThin, THIN_IN_PX); } - else if (input == "medium") + static BorderSideWidth Medium() { - tag_ = kMedium; - length_ = NoCalcLength::FromPx(MEDIUM_IN_PX); + return BorderSideWidth(kMedium, MEDIUM_IN_PX); } - else if (input == "thick") + static BorderSideWidth Thick() { - tag_ = kThick; - length_ = NoCalcLength::FromPx(THICK_IN_PX); + return BorderSideWidth(kThick, THICK_IN_PX); } - else + + public: + BorderSideWidth() + : tag_(kMedium) + , length_(NoCalcLength::FromPx(MEDIUM_IN_PX)) { - length_ = Parse::ParseSingleValue(input); - tag_ = kLength; } - return true; - } - public: - std::string toCss() const override - { - switch (tag_) - { - case kThin: - return "thin"; - case kMedium: - return "medium"; - case kThick: - return "thick"; - default: - return length_.toCss(); + private: + BorderSideWidth(Tag tag, float px) + : tag_(tag) + , length_(NoCalcLength::FromPx(px)) + { } - } - computed::BorderSideWidth toComputedValue(computed::Context &context) const override - { - switch (tag_) - { - case kThin: - return computed::BorderSideWidth(THIN_IN_PX); - case kMedium: - return computed::BorderSideWidth(MEDIUM_IN_PX); - case kThick: - return computed::BorderSideWidth(THICK_IN_PX); - default: - return computed::BorderSideWidth(length_.toComputedValue(context)); + + bool parse(const std::string &input) override + { + if (input == "thin") + { + tag_ = kThin; + length_ = NoCalcLength::FromPx(THIN_IN_PX); + } + else if (input == "medium") + { + tag_ = kMedium; + length_ = NoCalcLength::FromPx(MEDIUM_IN_PX); + } + else if (input == "thick") + { + tag_ = kThick; + length_ = NoCalcLength::FromPx(THICK_IN_PX); + } + else + { + length_ = Parse::ParseSingleValue(input); + tag_ = kLength; + } + return true; } - } - private: - Tag tag_; - // TODO(yorkie): support calc length. - NoCalcLength length_; - }; + public: + std::string toCss() const override + { + switch (tag_) + { + case kThin: + return "thin"; + case kMedium: + return "medium"; + case kThick: + return "thick"; + default: + return length_.toCss(); + } + } + computed::BorderSideWidth toComputedValue(computed::Context &context) const override + { + switch (tag_) + { + case kThin: + return computed::BorderSideWidth(THIN_IN_PX); + case kMedium: + return computed::BorderSideWidth(MEDIUM_IN_PX); + case kThick: + return computed::BorderSideWidth(THICK_IN_PX); + default: + return computed::BorderSideWidth(length_.toComputedValue(context)); + } + } - class BorderSideStyle : public generics::GenericBorderStyle, - public Parse, - public ToComputedValue - { - friend class Parse; - using generics::GenericBorderStyle::GenericBorderStyle; + private: + Tag tag_; + // TODO(yorkie): support calc length. + NoCalcLength length_; + }; - private: - bool parse(const std::string &input) override + class BorderSideStyle : public generics::GenericBorderStyle, + public Parse, + public ToComputedValue { - if (input == "hidden") - tag_ = kHidden; - else if (input == "none") - tag_ = kNone; - else if (input == "inset") - tag_ = kInset; - else if (input == "groove") - tag_ = kGroove; - else if (input == "outset") - tag_ = kOutset; - else if (input == "ridge") - tag_ = kRidge; - else if (input == "dotted") - tag_ = kDotted; - else if (input == "dashed") - tag_ = kDashed; - else if (input == "solid") - tag_ = kSolid; - else if (input == "double") - tag_ = kDouble; - return true; - } - - public: - computed::BorderSideStyle toComputedValue(computed::Context &context) const override - { - switch (tag_) - { - case kHidden: - return computed::BorderSideStyle::Hidden(); - case kNone: - return computed::BorderSideStyle::None(); - case kInset: - return computed::BorderSideStyle::Inset(); - case kGroove: - return computed::BorderSideStyle::Groove(); - case kOutset: - return computed::BorderSideStyle::Outset(); - case kRidge: - return computed::BorderSideStyle::Ridge(); - case kDotted: - return computed::BorderSideStyle::Dotted(); - case kDashed: - return computed::BorderSideStyle::Dashed(); - case kSolid: - return computed::BorderSideStyle::Solid(); - case kDouble: - return computed::BorderSideStyle::Double(); - default: - return computed::BorderSideStyle::None(); + friend class Parse; + using generics::GenericBorderStyle::GenericBorderStyle; + + private: + bool parse(const std::string &input) override + { + if (input == "hidden") + tag_ = kHidden; + else if (input == "none") + tag_ = kNone; + else if (input == "inset") + tag_ = kInset; + else if (input == "groove") + tag_ = kGroove; + else if (input == "outset") + tag_ = kOutset; + else if (input == "ridge") + tag_ = kRidge; + else if (input == "dotted") + tag_ = kDotted; + else if (input == "dashed") + tag_ = kDashed; + else if (input == "solid") + tag_ = kSolid; + else if (input == "double") + tag_ = kDouble; + return true; } - } - }; - class BorderCornerRadius : public generics::GenericBorderCornerRadius, - public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; - using generics::GenericBorderCornerRadius::GenericBorderCornerRadius; + public: + computed::BorderSideStyle toComputedValue(computed::Context &context) const override + { + switch (tag_) + { + case kHidden: + return computed::BorderSideStyle::Hidden(); + case kNone: + return computed::BorderSideStyle::None(); + case kInset: + return computed::BorderSideStyle::Inset(); + case kGroove: + return computed::BorderSideStyle::Groove(); + case kOutset: + return computed::BorderSideStyle::Outset(); + case kRidge: + return computed::BorderSideStyle::Ridge(); + case kDotted: + return computed::BorderSideStyle::Dotted(); + case kDashed: + return computed::BorderSideStyle::Dashed(); + case kSolid: + return computed::BorderSideStyle::Solid(); + case kDouble: + return computed::BorderSideStyle::Double(); + default: + return computed::BorderSideStyle::None(); + } + } + }; - private: - bool parse(const std::string &input) override + class BorderCornerRadius : public generics::GenericBorderCornerRadius, + public Parse, + public ToCss, + public ToComputedValue { - value_ = Parse::ParseSingleValue(input); - return true; - } + friend class Parse; + using generics::GenericBorderCornerRadius::GenericBorderCornerRadius; - public: - std::string toCss() const override - { - return value_.toCss(); - } - computed::BorderCornerRadius toComputedValue(computed::Context &context) const override - { - return computed::BorderCornerRadius(value_.toComputedValue(context)); - } - }; -} + private: + bool parse(const std::string &input) override + { + value_ = Parse::ParseSingleValue(input); + return true; + } + + public: + std::string toCss() const override + { + return value_.toCss(); + } + computed::BorderCornerRadius toComputedValue(computed::Context &context) const override + { + return computed::BorderCornerRadius(value_.toComputedValue(context)); + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/box.hpp b/src/client/cssom/values/specified/box.hpp index 637840fac..fd9634ae3 100644 --- a/src/client/cssom/values/specified/box.hpp +++ b/src/client/cssom/values/specified/box.hpp @@ -4,523 +4,526 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class Display; - class DisplayOutside + namespace client_cssom::values::specified { - friend class Display; - - private: - enum Tag : uint8_t + class Display; + class DisplayOutside { - kNone = 0, - kInline, - kBlock, - kTableCaption, - kInternalTable, - }; + friend class Display; - public: - static DisplayOutside None() - { - return DisplayOutside(kNone); - } - static DisplayOutside Inline() - { - return DisplayOutside(kInline); - } - static DisplayOutside Block() - { - return DisplayOutside(kBlock); - } - static DisplayOutside TableCaption() - { - return DisplayOutside(kTableCaption); - } - static DisplayOutside InternalTable() - { - return DisplayOutside(kInternalTable); - } - static DisplayOutside InternalTableCaption() - { - return DisplayOutside(kTableCaption); - } + private: + enum Tag : uint8_t + { + kNone = 0, + kInline, + kBlock, + kTableCaption, + kInternalTable, + }; + + public: + static DisplayOutside None() + { + return DisplayOutside(kNone); + } + static DisplayOutside Inline() + { + return DisplayOutside(kInline); + } + static DisplayOutside Block() + { + return DisplayOutside(kBlock); + } + static DisplayOutside TableCaption() + { + return DisplayOutside(kTableCaption); + } + static DisplayOutside InternalTable() + { + return DisplayOutside(kInternalTable); + } + static DisplayOutside InternalTableCaption() + { + return DisplayOutside(kTableCaption); + } - public: - DisplayOutside() - : tag_(kBlock) - { - } + public: + DisplayOutside() + : tag_(kBlock) + { + } - private: - DisplayOutside(Tag tag) - : tag_(tag) - { - } + private: + DisplayOutside(Tag tag) + : tag_(tag) + { + } - public: - inline bool isNone() const - { - return tag_ == kNone; - } - inline bool isInline() const - { - return tag_ == kInline; - } - inline bool isBlock() const + public: + inline bool isNone() const + { + return tag_ == kNone; + } + inline bool isInline() const + { + return tag_ == kInline; + } + inline bool isBlock() const + { + return tag_ == kBlock; + } + + private: + Tag tag_; + }; + + class DisplayInside { - return tag_ == kBlock; - } + friend class Display; - private: - Tag tag_; - }; + private: + enum Tag : uint8_t + { + kNone = 0, + kContents, + kFlow, + kFlowRoot, + kFlex, + kGrid, + kTable, + kRuby, + }; + + public: + static DisplayInside None() + { + return DisplayInside(kNone); + } + static DisplayInside Contents() + { + return DisplayInside(kContents); + } + static DisplayInside Flow() + { + return DisplayInside(kFlow); + } + static DisplayInside FlowRoot() + { + return DisplayInside(kFlowRoot); + } + static DisplayInside Flex() + { + return DisplayInside(kFlex); + } + static DisplayInside Grid() + { + return DisplayInside(kGrid); + } + static DisplayInside Table() + { + return DisplayInside(kTable); + } + static DisplayInside Ruby() + { + return DisplayInside(kRuby); + } - class DisplayInside - { - friend class Display; + public: + DisplayInside() + : tag_(kFlow) + { + } - private: - enum Tag : uint8_t - { - kNone = 0, - kContents, - kFlow, - kFlowRoot, - kFlex, - kGrid, - kTable, - kRuby, + private: + DisplayInside(Tag tag) + : tag_(tag) + { + } + + public: + inline bool isNone() const + { + return tag_ == kNone; + } + inline bool isFlex() const + { + return tag_ == kFlex; + } + inline bool isGrid() const + { + return tag_ == kGrid; + } + inline bool isFlexOrGrid() const + { + return isFlex() || isGrid(); + } + + private: + Tag tag_; }; - public: - static DisplayInside None() - { - return DisplayInside(kNone); - } - static DisplayInside Contents() - { - return DisplayInside(kContents); - } - static DisplayInside Flow() - { - return DisplayInside(kFlow); - } - static DisplayInside FlowRoot() - { - return DisplayInside(kFlowRoot); - } - static DisplayInside Flex() - { - return DisplayInside(kFlex); - } - static DisplayInside Grid() - { - return DisplayInside(kGrid); - } - static DisplayInside Table() - { - return DisplayInside(kTable); - } - static DisplayInside Ruby() + class Display : public Parse, + public ToCss, + public ToComputedValue, + public ToLayoutValue { - return DisplayInside(kRuby); - } + friend class Parse; - public: - DisplayInside() - : tag_(kFlow) - { - } + public: + // FIXME(yorkie): not used + static constexpr uint16_t LIST_ITEM_MASK = 0b1000000000000000; + static constexpr uint16_t OUTSIDE_MASK = 0b0111111100000000; + static constexpr uint16_t INSIDE_MASK = 0b0000000011111111; + static constexpr uint16_t OUTSIDE_SHIFT = 8; - private: - DisplayInside(Tag tag) - : tag_(tag) - { - } + static Display None() + { + return Display(DisplayOutside::kNone, DisplayInside::kNone); + } + static Display Contents() + { + return Display(DisplayOutside::kNone, DisplayInside::kContents); + } + static Display Inline() + { + return Display(DisplayOutside::kInline, DisplayInside::kFlow); + } + static Display InlineBlock() + { + return Display(DisplayOutside::kInline, DisplayInside::kFlowRoot); + } + static Display Block() + { + return Display(DisplayOutside::kBlock, DisplayInside::kFlow); + } + static Display FlowRoot() + { + return Display(DisplayOutside::kBlock, DisplayInside::kFlowRoot); + } + static Display Flex() + { + return Display(DisplayOutside::kBlock, DisplayInside::kFlex); + } + static Display InlineFlex() + { + return Display(DisplayOutside::kInline, DisplayInside::kFlex); + } + static Display Grid() + { + return Display(DisplayOutside::kBlock, DisplayInside::kGrid); + } + static Display InlineGrid() + { + return Display(DisplayOutside::kInline, DisplayInside::kGrid); + } + static Display Table() + { + return Display(DisplayOutside::kBlock, DisplayInside::kTable); + } + static Display InlineTable() + { + return Display(DisplayOutside::kInline, DisplayInside::kTable); + } - public: - inline bool isNone() const - { - return tag_ == kNone; - } - inline bool isFlex() const - { - return tag_ == kFlex; - } - inline bool isGrid() const - { - return tag_ == kGrid; - } - inline bool isFlexOrGrid() const - { - return isFlex() || isGrid(); - } + public: + Display() + : Display(DisplayOutside::kNone, DisplayInside::kNone) + { + } - private: - Tag tag_; - }; + private: + Display(uint8_t outside_bits, uint8_t inside_bits) + : bits_(static_cast(outside_bits) << OUTSIDE_SHIFT | static_cast(inside_bits)) + { + } - class Display : public Parse, - public ToCss, - public ToComputedValue, - public ToLayoutValue - { - friend class Parse; + private: + bool parse(const std::string &input) override + { + if (input == "none") + bits_ = 0; + else if (input == "block") + bits_ = DisplayOutside::kBlock << OUTSIDE_SHIFT | DisplayInside::kFlow; + else if (input == "inline") + bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kFlow; + else if (input == "inline-block") + bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kFlowRoot; + else if (input == "flex") + bits_ = DisplayOutside::kBlock << OUTSIDE_SHIFT | DisplayInside::kFlex; + else if (input == "inline-flex") + bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kFlex; + else if (input == "grid") + bits_ = DisplayOutside::kBlock << OUTSIDE_SHIFT | DisplayInside::kGrid; + else if (input == "inline-grid") + bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kGrid; + return true; + } - public: - // FIXME(yorkie): not used - static constexpr uint16_t LIST_ITEM_MASK = 0b1000000000000000; - static constexpr uint16_t OUTSIDE_MASK = 0b0111111100000000; - static constexpr uint16_t INSIDE_MASK = 0b0000000011111111; - static constexpr uint16_t OUTSIDE_SHIFT = 8; + public: + std::string toCss() const override + { + return "display"; + } + Display toComputedValue(computed::Context &) const override + { + return *this; + } + crates::layout2::styles::Display toLayoutValue() const override + { + if (isNone()) + return crates::layout2::styles::Display::None(); + else if (isFlex()) + return crates::layout2::styles::Display::Flex(); + else if (isGrid()) + return crates::layout2::styles::Display::Grid(); + else + return crates::layout2::styles::Display::Block(); + } - static Display None() - { - return Display(DisplayOutside::kNone, DisplayInside::kNone); - } - static Display Contents() - { - return Display(DisplayOutside::kNone, DisplayInside::kContents); - } - static Display Inline() - { - return Display(DisplayOutside::kInline, DisplayInside::kFlow); - } - static Display InlineBlock() - { - return Display(DisplayOutside::kInline, DisplayInside::kFlowRoot); - } - static Display Block() - { - return Display(DisplayOutside::kBlock, DisplayInside::kFlow); - } - static Display FlowRoot() - { - return Display(DisplayOutside::kBlock, DisplayInside::kFlowRoot); - } - static Display Flex() - { - return Display(DisplayOutside::kBlock, DisplayInside::kFlex); - } - static Display InlineFlex() - { - return Display(DisplayOutside::kInline, DisplayInside::kFlex); - } - static Display Grid() - { - return Display(DisplayOutside::kBlock, DisplayInside::kGrid); - } - static Display InlineGrid() - { - return Display(DisplayOutside::kInline, DisplayInside::kGrid); - } - static Display Table() - { - return Display(DisplayOutside::kBlock, DisplayInside::kTable); - } - static Display InlineTable() - { - return Display(DisplayOutside::kInline, DisplayInside::kTable); - } + inline DisplayOutside outside() const + { + return DisplayOutside(static_cast((bits_ & OUTSIDE_MASK) >> OUTSIDE_SHIFT)); + } + inline DisplayInside inside() const + { + return DisplayInside(static_cast(bits_ & INSIDE_MASK)); + } - public: - Display() - : Display(DisplayOutside::kNone, DisplayInside::kNone) - { - } + inline bool isItemContainer() const + { + return inside().isFlexOrGrid(); + } + inline bool isNone() const + { + return outside().isNone() && inside().isNone(); + } + inline bool isFlex() const + { + return outside().isBlock() && inside().isFlex(); + } + inline bool isGrid() const + { + return outside().isBlock() && inside().isGrid(); + } - private: - Display(uint8_t outside_bits, uint8_t inside_bits) - : bits_(static_cast(outside_bits) << OUTSIDE_SHIFT | static_cast(inside_bits)) - { - } + private: + uint16_t bits_ = 0; + }; - private: - bool parse(const std::string &input) override - { - if (input == "none") - bits_ = 0; - else if (input == "block") - bits_ = DisplayOutside::kBlock << OUTSIDE_SHIFT | DisplayInside::kFlow; - else if (input == "inline") - bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kFlow; - else if (input == "inline-block") - bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kFlowRoot; - else if (input == "flex") - bits_ = DisplayOutside::kBlock << OUTSIDE_SHIFT | DisplayInside::kFlex; - else if (input == "inline-flex") - bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kFlex; - else if (input == "grid") - bits_ = DisplayOutside::kBlock << OUTSIDE_SHIFT | DisplayInside::kGrid; - else if (input == "inline-grid") - bits_ = DisplayOutside::kInline << OUTSIDE_SHIFT | DisplayInside::kGrid; - return true; - } - - public: - std::string toCss() const override - { - return "display"; - } - Display toComputedValue(computed::Context &) const override - { - return *this; - } - crates::layout2::styles::Display toLayoutValue() const override + class BoxSizing : public Parse, + public ToCss, + public ToComputedValue, + public ToLayoutValue { - if (isNone()) - return crates::layout2::styles::Display::None(); - else if (isFlex()) - return crates::layout2::styles::Display::Flex(); - else if (isGrid()) - return crates::layout2::styles::Display::Grid(); - else - return crates::layout2::styles::Display::Block(); - } - - inline DisplayOutside outside() const - { - return DisplayOutside(static_cast((bits_ & OUTSIDE_MASK) >> OUTSIDE_SHIFT)); - } - inline DisplayInside inside() const - { - return DisplayInside(static_cast(bits_ & INSIDE_MASK)); - } + friend class Parse; - inline bool isItemContainer() const - { - return inside().isFlexOrGrid(); - } - inline bool isNone() const - { - return outside().isNone() && inside().isNone(); - } - inline bool isFlex() const - { - return outside().isBlock() && inside().isFlex(); - } - inline bool isGrid() const - { - return outside().isBlock() && inside().isGrid(); - } + private: + enum Tag : uint8_t + { + kContentBox = 0, + kBorderBox, + }; - private: - uint16_t bits_ = 0; - }; + public: + static BoxSizing ContentBox() + { + return BoxSizing(kContentBox); + } + static BoxSizing BorderBox() + { + return BoxSizing(kBorderBox); + } - class BoxSizing : public Parse, - public ToCss, - public ToComputedValue, - public ToLayoutValue - { - friend class Parse; + public: + BoxSizing() + : BoxSizing(kContentBox) + { + } - private: - enum Tag : uint8_t - { - kContentBox = 0, - kBorderBox, - }; + private: + BoxSizing(Tag tag) + : tag_(tag) + { + } - public: - static BoxSizing ContentBox() - { - return BoxSizing(kContentBox); - } - static BoxSizing BorderBox() - { - return BoxSizing(kBorderBox); - } + private: + bool parse(const std::string &input) override + { + if (input == "content-box") + tag_ = kContentBox; + else if (input == "border-box") + tag_ = kBorderBox; + return true; + } - public: - BoxSizing() - : BoxSizing(kContentBox) - { - } + public: + std::string toCss() const override + { + if (tag_ == kBorderBox) + return "border-box"; + else + return "content-box"; + } + BoxSizing toComputedValue(computed::Context &) const override + { + return *this; + } + crates::layout2::styles::BoxSizing toLayoutValue() const override + { + if (tag_ == kContentBox) + return crates::layout2::styles::BoxSizing::ContentBox(); + else + return crates::layout2::styles::BoxSizing::BorderBox(); + } - private: - BoxSizing(Tag tag) - : tag_(tag) - { - } + private: + Tag tag_; + }; - private: - bool parse(const std::string &input) override - { - if (input == "content-box") - tag_ = kContentBox; - else if (input == "border-box") - tag_ = kBorderBox; - return true; - } - - public: - std::string toCss() const override - { - if (tag_ == kBorderBox) - return "border-box"; - else - return "content-box"; - } - BoxSizing toComputedValue(computed::Context &) const override + class Overflow : public Parse, + public ToCss, + public ToComputedValue, + public ToLayoutValue { - return *this; - } - crates::layout2::styles::BoxSizing toLayoutValue() const override - { - if (tag_ == kContentBox) - return crates::layout2::styles::BoxSizing::ContentBox(); - else - return crates::layout2::styles::BoxSizing::BorderBox(); - } - - private: - Tag tag_; - }; - - class Overflow : public Parse, - public ToCss, - public ToComputedValue, - public ToLayoutValue - { - friend class Parse; + friend class Parse; - private: - enum Tag : uint8_t - { - kVisible = 0, - kHidden, - kScroll, - kAuto, - kClip, - }; + private: + enum Tag : uint8_t + { + kVisible = 0, + kHidden, + kScroll, + kAuto, + kClip, + }; + + public: + static Overflow Visible() + { + return Overflow(kVisible); + } + static Overflow Hidden() + { + return Overflow(kHidden); + } + static Overflow Scroll() + { + return Overflow(kScroll); + } + static Overflow Auto() + { + return Overflow(kAuto); + } + static Overflow Clip() + { + return Overflow(kClip); + } - public: - static Overflow Visible() - { - return Overflow(kVisible); - } - static Overflow Hidden() - { - return Overflow(kHidden); - } - static Overflow Scroll() - { - return Overflow(kScroll); - } - static Overflow Auto() - { - return Overflow(kAuto); - } - static Overflow Clip() - { - return Overflow(kClip); - } + public: + Overflow() + : tag_(kVisible) + { + } - public: - Overflow() - : tag_(kVisible) - { - } + private: + Overflow(Tag tag) + : tag_(tag) + { + } - private: - Overflow(Tag tag) - : tag_(tag) - { - } + bool parse(const std::string &input) override + { + if (input == "visible") + tag_ = kVisible; + else if (input == "hidden") + tag_ = kHidden; + else if (input == "scroll") + tag_ = kScroll; + else if (input == "auto") + tag_ = kAuto; + else if (input == "clip") + tag_ = kClip; + return true; + } - bool parse(const std::string &input) override - { - if (input == "visible") - tag_ = kVisible; - else if (input == "hidden") - tag_ = kHidden; - else if (input == "scroll") - tag_ = kScroll; - else if (input == "auto") - tag_ = kAuto; - else if (input == "clip") - tag_ = kClip; - return true; - } - - public: - std::string toCss() const override - { - switch (tag_) - { - case kVisible: - return "visible"; - case kHidden: - return "hidden"; - case kScroll: - return "scroll"; - case kAuto: - return "auto"; - case kClip: - return "clip"; - } - return ""; - } - Overflow toComputedValue(computed::Context &) const override - { - return *this; - } - crates::layout2::styles::Overflow toLayoutValue() const override - { - switch (tag_) - { - case kVisible: - return crates::layout2::styles::Overflow::Visible(); - case kHidden: - return crates::layout2::styles::Overflow::Hidden(); - case kScroll: - return crates::layout2::styles::Overflow::Scroll(); - case kClip: - return crates::layout2::styles::Overflow::Clip(); - case kAuto: - default: - // TODO(yorkie): support the `auto` value. - return crates::layout2::styles::Overflow::Visible(); - } - } - - inline bool isVisible() const - { - return tag_ == kVisible; - } - inline bool isHidden() const - { - return tag_ == kHidden; - } - inline bool isScroll() const - { - return tag_ == kScroll; - } - inline bool isAuto() const - { - return tag_ == kAuto; - } - inline bool isClip() const - { - return tag_ == kClip; - } + public: + std::string toCss() const override + { + switch (tag_) + { + case kVisible: + return "visible"; + case kHidden: + return "hidden"; + case kScroll: + return "scroll"; + case kAuto: + return "auto"; + case kClip: + return "clip"; + } + return ""; + } + Overflow toComputedValue(computed::Context &) const override + { + return *this; + } + crates::layout2::styles::Overflow toLayoutValue() const override + { + switch (tag_) + { + case kVisible: + return crates::layout2::styles::Overflow::Visible(); + case kHidden: + return crates::layout2::styles::Overflow::Hidden(); + case kScroll: + return crates::layout2::styles::Overflow::Scroll(); + case kClip: + return crates::layout2::styles::Overflow::Clip(); + case kAuto: + default: + // TODO(yorkie): support the `auto` value. + return crates::layout2::styles::Overflow::Visible(); + } + } - // Compbined checks - inline bool isAutoOrScroll() const - { - return isAuto() || isScroll(); - } - // Returns if the overflow is scrollable, namely `auto`, `scroll`, or `hidden`. - // NOTE(yorkie): `hidden` is considered scrollable because it can be scrolled by JavaScript. - inline bool isScrollable() const - { - return !isVisible() && !isClip(); - } + inline bool isVisible() const + { + return tag_ == kVisible; + } + inline bool isHidden() const + { + return tag_ == kHidden; + } + inline bool isScroll() const + { + return tag_ == kScroll; + } + inline bool isAuto() const + { + return tag_ == kAuto; + } + inline bool isClip() const + { + return tag_ == kClip; + } - private: - Tag tag_; - }; -} + // Compbined checks + inline bool isAutoOrScroll() const + { + return isAuto() || isScroll(); + } + // Returns if the overflow is scrollable, namely `auto`, `scroll`, or `hidden`. + // NOTE(yorkie): `hidden` is considered scrollable because it can be scrolled by JavaScript. + inline bool isScrollable() const + { + return !isVisible() && !isClip(); + } + + private: + Tag tag_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/calc.hpp b/src/client/cssom/values/specified/calc.hpp index 26bfbfc1f..8da5d2016 100644 --- a/src/client/cssom/values/specified/calc.hpp +++ b/src/client/cssom/values/specified/calc.hpp @@ -7,49 +7,52 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class Leaf + namespace client_cssom::values::specified { - private: - enum Tag + class Leaf { - kLength, - kAngle, - kTime, - kResolution, - kColorComponent, - kPercentage, - kNumber, - }; + private: + enum Tag + { + kLength, + kAngle, + kTime, + kResolution, + kColorComponent, + kPercentage, + kNumber, + }; - private: - Tag tag_; - }; + private: + Tag tag_; + }; - using CalcNode = generics::GenericCalcNode; + using CalcNode = generics::GenericCalcNode; - class CalcLengthPercentage - { - public: - CalcLengthPercentage() - : clamping_mode_(AllowedNumbericType::Default()) - , calc_node_(nullptr) + class CalcLengthPercentage { - } - CalcLengthPercentage(const CalcLengthPercentage &other) - : clamping_mode_(other.clamping_mode_) - , calc_node_(nullptr) - { - } + public: + CalcLengthPercentage() + : clamping_mode_(AllowedNumbericType::Default()) + , calc_node_(nullptr) + { + } + CalcLengthPercentage(const CalcLengthPercentage &other) + : clamping_mode_(other.clamping_mode_) + , calc_node_(nullptr) + { + } - CalcLengthPercentage operator=(const CalcLengthPercentage &other) - { - return *this; - } + CalcLengthPercentage operator=(const CalcLengthPercentage &other) + { + return *this; + } - private: - AllowedNumbericType clamping_mode_; - std::unique_ptr calc_node_ = nullptr; - }; -} + private: + AllowedNumbericType clamping_mode_; + std::unique_ptr calc_node_ = nullptr; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/color.hpp b/src/client/cssom/values/specified/color.hpp index b2bcee321..314fe05d0 100644 --- a/src/client/cssom/values/specified/color.hpp +++ b/src/client/cssom/values/specified/color.hpp @@ -7,102 +7,105 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class Color : public Parse, - public ToCss, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - - private: - enum Tag : uint8_t + class Color : public Parse, + public ToCss, + public ToComputedValue { - kCurrentColor, - kAbsolute, - kColorFunction, - kColorMix, - kLightDark, - }; + friend class Parse; - struct AbsoluteColorVariant - { - uint32_t r; - uint32_t g; - uint32_t b; - uint32_t a; - - operator glm::vec4() const + private: + enum Tag : uint8_t { - return glm::vec4(r, g, b, a); - } - }; - using ValueVariant = std::variant; + kCurrentColor, + kAbsolute, + kColorFunction, + kColorMix, + kLightDark, + }; - private: - bool parse(const std::string &input) override - { - if (input == "currentcolor") + struct AbsoluteColorVariant { - tag_ = Tag::kCurrentColor; - value_ = std::monostate(); - } - else + uint32_t r; + uint32_t g; + uint32_t b; + uint32_t a; + + operator glm::vec4() const + { + return glm::vec4(r, g, b, a); + } + }; + using ValueVariant = std::variant; + + private: + bool parse(const std::string &input) override { - using namespace crates::css2; + if (input == "currentcolor") + { + tag_ = Tag::kCurrentColor; + value_ = std::monostate(); + } + else + { + using namespace crates::css2; - // TODO(yorkie): support color functions, mix, light/dark. - auto color = parsing::parseColor(input); - tag_ = Tag::kAbsolute; - value_ = AbsoluteColorVariant{ - static_cast(color.r()), - static_cast(color.g()), - static_cast(color.b()), - static_cast(color.a())}; + // TODO(yorkie): support color functions, mix, light/dark. + auto color = parsing::parseColor(input); + tag_ = Tag::kAbsolute; + value_ = AbsoluteColorVariant{ + static_cast(color.r()), + static_cast(color.g()), + static_cast(color.b()), + static_cast(color.a())}; + } + return true; } - return true; - } - public: - inline bool isCurrentColor() const - { - return tag_ == Tag::kCurrentColor; - } - inline bool isAbsoluteColor() const - { - return tag_ == Tag::kAbsolute; - } - - std::string toCss() const override - { - if (isCurrentColor()) - return "currentcolor"; - else if (isAbsoluteColor()) + public: + inline bool isCurrentColor() const { - AbsoluteColorVariant absolute_color = std::get(value_); - return "rgba(" + std::to_string(absolute_color.r) + ", " + - std::to_string(absolute_color.g) + ", " + - std::to_string(absolute_color.b) + ", " + - std::to_string(absolute_color.a) + ")"; + return tag_ == Tag::kCurrentColor; } - return ""; - } - computed::Color toComputedValue(computed::Context &context) const override - { - if (isCurrentColor()) + inline bool isAbsoluteColor() const { - return computed::Color::CurrentColor(); + return tag_ == Tag::kAbsolute; } - else if (isAbsoluteColor()) + + std::string toCss() const override + { + if (isCurrentColor()) + return "currentcolor"; + else if (isAbsoluteColor()) + { + AbsoluteColorVariant absolute_color = std::get(value_); + return "rgba(" + std::to_string(absolute_color.r) + ", " + + std::to_string(absolute_color.g) + ", " + + std::to_string(absolute_color.b) + ", " + + std::to_string(absolute_color.a) + ")"; + } + return ""; + } + computed::Color toComputedValue(computed::Context &context) const override { - AbsoluteColorVariant absolute_color = std::get(value_); - return computed::Color::Absolute(absolute_color); + if (isCurrentColor()) + { + return computed::Color::CurrentColor(); + } + else if (isAbsoluteColor()) + { + AbsoluteColorVariant absolute_color = std::get(value_); + return computed::Color::Absolute(absolute_color); + } + return computed::Color::Transparent(); } - return computed::Color::Transparent(); - } - private: - Tag tag_; - ValueVariant value_; - }; -} + private: + Tag tag_; + ValueVariant value_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/common.hpp b/src/client/cssom/values/specified/common.hpp index 4bc006a83..a11091640 100644 --- a/src/client/cssom/values/specified/common.hpp +++ b/src/client/cssom/values/specified/common.hpp @@ -7,117 +7,120 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class AllowedNumbericType + namespace client_cssom::values::specified { - private: - enum Tag + class AllowedNumbericType { - kAll, - kNonNegative, - kAtLeastOne, - kZeroToOne - }; - - public: - static AllowedNumbericType Default() - { - return AllowedNumbericType(); - } - static AllowedNumbericType All() - { - return AllowedNumbericType(kAll); - } - static AllowedNumbericType NonNegative() - { - return AllowedNumbericType(kNonNegative); - } - static AllowedNumbericType AtLeastOne() - { - return AllowedNumbericType(kAtLeastOne); - } - static AllowedNumbericType ZeroToOne() - { - return AllowedNumbericType(kZeroToOne); - } + private: + enum Tag + { + kAll, + kNonNegative, + kAtLeastOne, + kZeroToOne + }; - public: - AllowedNumbericType() - : tag_(kAll) - { - } + public: + static AllowedNumbericType Default() + { + return AllowedNumbericType(); + } + static AllowedNumbericType All() + { + return AllowedNumbericType(kAll); + } + static AllowedNumbericType NonNegative() + { + return AllowedNumbericType(kNonNegative); + } + static AllowedNumbericType AtLeastOne() + { + return AllowedNumbericType(kAtLeastOne); + } + static AllowedNumbericType ZeroToOne() + { + return AllowedNumbericType(kZeroToOne); + } - private: - AllowedNumbericType(Tag tag) - : tag_(tag) - { - } + public: + AllowedNumbericType() + : tag_(kAll) + { + } - public: - // Clamp the value following the rules of this numeric type. - float clamp(float v) const - { - switch (tag_) + private: + AllowedNumbericType(Tag tag) + : tag_(tag) { - case kAll: - return v; - case kNonNegative: - return std::max(0.0f, v); - case kAtLeastOne: - return std::max(1.0f, v); - case kZeroToOne: - return std::clamp(v, 0.0f, 1.0f); } - assert(false && "Invalid tag."); - } - private: - Tag tag_; - }; + public: + // Clamp the value following the rules of this numeric type. + float clamp(float v) const + { + switch (tag_) + { + case kAll: + return v; + case kNonNegative: + return std::max(0.0f, v); + case kAtLeastOne: + return std::max(1.0f, v); + case kZeroToOne: + return std::clamp(v, 0.0f, 1.0f); + } + assert(false && "Invalid tag."); + } - // `` - class Number : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; + private: + Tag tag_; + }; - public: - Number(float value = 0.0f, std::optional clamping_mode = std::nullopt) - : value_(value) - , calc_clamping_mode_(clamping_mode) + // `` + class Number : public Parse, + public ToCss, + public ToComputedValue { - } + friend class Parse; - private: - bool parse(const std::string &input) override - { - value_ = std::stof(input); - // TODO(yorkie): support calc function. - return true; - } + public: + Number(float value = 0.0f, std::optional clamping_mode = std::nullopt) + : value_(value) + , calc_clamping_mode_(clamping_mode) + { + } - public: - std::string toCss() const override - { - return std::to_string(value_); - } - CSSFloat toComputedValue(computed::Context &) const override - { - if (calc_clamping_mode_.has_value()) - return calc_clamping_mode_->clamp(value_); - return value_; - } - inline bool wasCalc() const - { - return calc_clamping_mode_.has_value(); - } + private: + bool parse(const std::string &input) override + { + value_ = std::stof(input); + // TODO(yorkie): support calc function. + return true; + } + + public: + std::string toCss() const override + { + return std::to_string(value_); + } + CSSFloat toComputedValue(computed::Context &) const override + { + if (calc_clamping_mode_.has_value()) + return calc_clamping_mode_->clamp(value_); + return value_; + } + inline bool wasCalc() const + { + return calc_clamping_mode_.has_value(); + } - private: - float value_; - std::optional calc_clamping_mode_; - }; + private: + float value_; + std::optional calc_clamping_mode_; + }; - using NonNegativeNumber = generics::NonNegative; -} + using NonNegativeNumber = generics::NonNegative; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/easing.hpp b/src/client/cssom/values/specified/easing.hpp index 1d36bf1f8..3fd423388 100644 --- a/src/client/cssom/values/specified/easing.hpp +++ b/src/client/cssom/values/specified/easing.hpp @@ -4,77 +4,80 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class TimingFunction : public generics::GenericTimingFunction, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - using generics::GenericTimingFunction::GenericTimingFunction; - - private: - bool parse(const std::string &input) override + class TimingFunction : public generics::GenericTimingFunction, + public Parse, + public ToComputedValue { - if (input == "linear") - { - tag_ = kKeyword; - timing_function_ = kLinear; - return true; - } - else if (input == "ease") - { - tag_ = kKeyword; - timing_function_ = kEase; - return true; - } - else if (input == "ease-in") - { - tag_ = kKeyword; - timing_function_ = kEaseIn; - return true; - } - else if (input == "ease-out") - { - tag_ = kKeyword; - timing_function_ = kEaseOut; - return true; - } - else if (input == "ease-in-out") + friend class Parse; + using generics::GenericTimingFunction::GenericTimingFunction; + + private: + bool parse(const std::string &input) override { - tag_ = kKeyword; - timing_function_ = kEaseInOut; - return true; + if (input == "linear") + { + tag_ = kKeyword; + timing_function_ = kLinear; + return true; + } + else if (input == "ease") + { + tag_ = kKeyword; + timing_function_ = kEase; + return true; + } + else if (input == "ease-in") + { + tag_ = kKeyword; + timing_function_ = kEaseIn; + return true; + } + else if (input == "ease-out") + { + tag_ = kKeyword; + timing_function_ = kEaseOut; + return true; + } + else if (input == "ease-in-out") + { + tag_ = kKeyword; + timing_function_ = kEaseInOut; + return true; + } + return false; // Invalid timing function format. } - return false; // Invalid timing function format. - } - public: - computed::TimingFunction toComputedValue(computed::Context &context) const override - { - switch (tag_) + public: + computed::TimingFunction toComputedValue(computed::Context &context) const override { - case kKeyword: - switch (std::get(timing_function_)) + switch (tag_) + { + case kKeyword: + switch (std::get(timing_function_)) + { + case kLinear: + return computed::TimingFunction::Linear(); + case kEase: + return computed::TimingFunction::Ease(); + case kEaseIn: + return computed::TimingFunction::EaseIn(); + case kEaseOut: + return computed::TimingFunction::EaseOut(); + case kEaseInOut: + return computed::TimingFunction::EaseInOut(); + } + case kCubicBezier: { - case kLinear: - return computed::TimingFunction::Linear(); - case kEase: - return computed::TimingFunction::Ease(); - case kEaseIn: - return computed::TimingFunction::EaseIn(); - case kEaseOut: - return computed::TimingFunction::EaseOut(); - case kEaseInOut: - return computed::TimingFunction::EaseInOut(); + const auto &cubic = std::get(timing_function_); + return computed::TimingFunction::CubicBezier(cubic.x1, cubic.y1, cubic.x2, cubic.y2); } - case kCubicBezier: - { - const auto &cubic = std::get(timing_function_); - return computed::TimingFunction::CubicBezier(cubic.x1, cubic.y1, cubic.x2, cubic.y2); - } + } + return computed::TimingFunction(); // Should not reach here. } - return computed::TimingFunction(); // Should not reach here. - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/filter.hpp b/src/client/cssom/values/specified/filter.hpp index 0d70ce278..5f498203d 100644 --- a/src/client/cssom/values/specified/filter.hpp +++ b/src/client/cssom/values/specified/filter.hpp @@ -4,316 +4,319 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class FilterFunction : public generics::GenericFilterFunction, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - friend class Filter; - - using generics::GenericFilterFunction::GenericFilterFunction; - - public: - FilterFunction() - : generics::GenericFilterFunction(kNone) - { - } - - bool parse(const std::string &input) override + class FilterFunction : public generics::GenericFilterFunction, + public Parse, + public ToComputedValue { - css_filter_parser::CSSFilterParser parser(input); - auto functions = parser.parse(); - - if (!parser.isValid() || functions.empty()) - return false; + friend class Parse; + friend class Filter; - // Parse single filter function - const auto &func = functions[0]; + using generics::GenericFilterFunction::GenericFilterFunction; - switch (func.type) + public: + FilterFunction() + : generics::GenericFilterFunction(kNone) { - case css_filter_parser::FilterFunctionType::kNone: - tag_ = kNone; - break; - case css_filter_parser::FilterFunctionType::kBlur: - tag_ = kBlur; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "px" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kBrightness: - tag_ = kBrightness; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kContrast: - tag_ = kContrast; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kDropShadow: - tag_ = kDropShadow; - raw_value_ = func.raw_value; - break; - case css_filter_parser::FilterFunctionType::kGrayscale: - tag_ = kGrayscale; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kHueRotate: - tag_ = kHueRotate; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "deg" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kInvert: - tag_ = kInvert; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kOpacity: - tag_ = kOpacity; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kSaturate: - tag_ = kSaturate; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - case css_filter_parser::FilterFunctionType::kSepia: - tag_ = kSepia; - if (!func.values.empty()) - { - parameters_.clear(); - parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); - } - break; - default: - return false; } - return true; - } - - computed::FilterFunction toComputedValue(computed::Context &) const override - { - computed::FilterFunction result; - result.tag_ = static_cast(tag_); - result.parameters_ = parameters_; - result.raw_value_ = raw_value_; - return result; - } - }; - - class Filter : public generics::GenericFilter, - public Parse, - public ToComputedValue - { - friend class Parse; - using generics::GenericFilter::GenericFilter; - - public: - bool parse(const std::string &input) override - { - if (input.empty()) - return false; - - // Handle "none" case - if (input == "none") + bool parse(const std::string &input) override { - is_none_ = true; - filter_functions_.clear(); - return true; - } + css_filter_parser::CSSFilterParser parser(input); + auto functions = parser.parse(); - // Use the CSS filter parser - css_filter_parser::CSSFilterParser parser(input); - auto parsed_functions = parser.parse(); - - if (!parser.isValid()) - { - return false; - } + if (!parser.isValid() || functions.empty()) + return false; - // Convert parsed functions to FilterFunction objects - filter_functions_.clear(); - for (const auto &parsed_func : parsed_functions) - { - FilterFunction filter_func; + // Parse single filter function + const auto &func = functions[0]; - // Map parser function type to our internal type - switch (parsed_func.type) + switch (func.type) { + case css_filter_parser::FilterFunctionType::kNone: + tag_ = kNone; + break; case css_filter_parser::FilterFunctionType::kBlur: - if (!parsed_func.values.empty()) + tag_ = kBlur; + if (!func.values.empty()) { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "px" : parsed_func.units[0]); - filter_func = FilterFunction::Blur(param); - } - else - { - filter_func = FilterFunction::Blur(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "px" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kBrightness: - if (!parsed_func.values.empty()) + tag_ = kBrightness; + if (!func.values.empty()) { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Brightness(param); - } - else - { - filter_func = FilterFunction::Brightness(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kContrast: - if (!parsed_func.values.empty()) + tag_ = kContrast; + if (!func.values.empty()) { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Contrast(param); - } - else - { - filter_func = FilterFunction::Contrast(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kDropShadow: - filter_func = FilterFunction::DropShadow(parsed_func.raw_value); + tag_ = kDropShadow; + raw_value_ = func.raw_value; break; case css_filter_parser::FilterFunctionType::kGrayscale: - if (!parsed_func.values.empty()) - { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Grayscale(param); - } - else + tag_ = kGrayscale; + if (!func.values.empty()) { - filter_func = FilterFunction::Grayscale(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kHueRotate: - if (!parsed_func.values.empty()) + tag_ = kHueRotate; + if (!func.values.empty()) { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "deg" : parsed_func.units[0]); - filter_func = FilterFunction::HueRotate(param); - } - else - { - filter_func = FilterFunction::HueRotate(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "deg" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kInvert: - if (!parsed_func.values.empty()) + tag_ = kInvert; + if (!func.values.empty()) { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Invert(param); - } - else - { - filter_func = FilterFunction::Invert(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kOpacity: - if (!parsed_func.values.empty()) - { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Opacity(param); - } - else + tag_ = kOpacity; + if (!func.values.empty()) { - filter_func = FilterFunction::Opacity(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kSaturate: - if (!parsed_func.values.empty()) - { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Saturate(param); - } - else + tag_ = kSaturate; + if (!func.values.empty()) { - filter_func = FilterFunction::Saturate(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; case css_filter_parser::FilterFunctionType::kSepia: - if (!parsed_func.values.empty()) + tag_ = kSepia; + if (!func.values.empty()) { - generics::FilterFunctionValue param(parsed_func.values[0], - parsed_func.units.empty() ? "" : parsed_func.units[0]); - filter_func = FilterFunction::Sepia(param); - } - else - { - filter_func = FilterFunction::Sepia(); + parameters_.clear(); + parameters_.emplace_back(func.values[0], func.units.empty() ? "" : func.units[0]); } break; default: return false; } - filter_functions_.push_back(filter_func); + return true; } - is_none_ = filter_functions_.empty(); - return true; - } + computed::FilterFunction toComputedValue(computed::Context &) const override + { + computed::FilterFunction result; + result.tag_ = static_cast(tag_); + result.parameters_ = parameters_; + result.raw_value_ = raw_value_; + return result; + } + }; - computed::Filter toComputedValue(computed::Context &context) const override + class Filter : public generics::GenericFilter, + public Parse, + public ToComputedValue { - computed::Filter result; + friend class Parse; + using generics::GenericFilter::GenericFilter; - if (is_none_) + public: + bool parse(const std::string &input) override { - result = computed::Filter::None(); + if (input.empty()) + return false; + + // Handle "none" case + if (input == "none") + { + is_none_ = true; + filter_functions_.clear(); + return true; + } + + // Use the CSS filter parser + css_filter_parser::CSSFilterParser parser(input); + auto parsed_functions = parser.parse(); + + if (!parser.isValid()) + { + return false; + } + + // Convert parsed functions to FilterFunction objects + filter_functions_.clear(); + for (const auto &parsed_func : parsed_functions) + { + FilterFunction filter_func; + + // Map parser function type to our internal type + switch (parsed_func.type) + { + case css_filter_parser::FilterFunctionType::kBlur: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "px" : parsed_func.units[0]); + filter_func = FilterFunction::Blur(param); + } + else + { + filter_func = FilterFunction::Blur(); + } + break; + case css_filter_parser::FilterFunctionType::kBrightness: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Brightness(param); + } + else + { + filter_func = FilterFunction::Brightness(); + } + break; + case css_filter_parser::FilterFunctionType::kContrast: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Contrast(param); + } + else + { + filter_func = FilterFunction::Contrast(); + } + break; + case css_filter_parser::FilterFunctionType::kDropShadow: + filter_func = FilterFunction::DropShadow(parsed_func.raw_value); + break; + case css_filter_parser::FilterFunctionType::kGrayscale: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Grayscale(param); + } + else + { + filter_func = FilterFunction::Grayscale(); + } + break; + case css_filter_parser::FilterFunctionType::kHueRotate: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "deg" : parsed_func.units[0]); + filter_func = FilterFunction::HueRotate(param); + } + else + { + filter_func = FilterFunction::HueRotate(); + } + break; + case css_filter_parser::FilterFunctionType::kInvert: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Invert(param); + } + else + { + filter_func = FilterFunction::Invert(); + } + break; + case css_filter_parser::FilterFunctionType::kOpacity: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Opacity(param); + } + else + { + filter_func = FilterFunction::Opacity(); + } + break; + case css_filter_parser::FilterFunctionType::kSaturate: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Saturate(param); + } + else + { + filter_func = FilterFunction::Saturate(); + } + break; + case css_filter_parser::FilterFunctionType::kSepia: + if (!parsed_func.values.empty()) + { + generics::FilterFunctionValue param(parsed_func.values[0], + parsed_func.units.empty() ? "" : parsed_func.units[0]); + filter_func = FilterFunction::Sepia(param); + } + else + { + filter_func = FilterFunction::Sepia(); + } + break; + default: + return false; + } + + filter_functions_.push_back(filter_func); + } + + is_none_ = filter_functions_.empty(); + return true; } - else + + computed::Filter toComputedValue(computed::Context &context) const override { - std::vector computed_functions; - for (const auto &func : filter_functions_) + computed::Filter result; + + if (is_none_) { - computed::FilterFunction computed_func; - computed_func.tag_ = static_cast(func.tag_); - computed_func.parameters_ = func.parameters_; - computed_func.raw_value_ = func.raw_value_; - computed_functions.push_back(computed_func); + result = computed::Filter::None(); + } + else + { + std::vector computed_functions; + for (const auto &func : filter_functions_) + { + computed::FilterFunction computed_func; + computed_func.tag_ = static_cast(func.tag_); + computed_func.parameters_ = func.parameters_; + computed_func.raw_value_ = func.raw_value_; + computed_functions.push_back(computed_func); + } + result.setFunctions(computed_functions); } - result.setFunctions(computed_functions); - } - return result; - } - }; -} + return result; + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/flex.hpp b/src/client/cssom/values/specified/flex.hpp index 568ef7928..be7d8fc35 100644 --- a/src/client/cssom/values/specified/flex.hpp +++ b/src/client/cssom/values/specified/flex.hpp @@ -4,46 +4,49 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class FlexDirection : public generics::GenericFlexDirection, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - - private: - bool parse(const std::string &input) override + class FlexDirection : public generics::GenericFlexDirection, + public Parse, + public ToComputedValue { - if (input == "row") - tag_ = kRow; - else if (input == "row-reverse") - tag_ = kRowReverse; - else if (input == "column") - tag_ = kColumn; - else if (input == "column-reverse") - tag_ = kColumnReverse; - else - tag_ = kRow; - return true; - } + friend class Parse; - public: - computed::FlexDirection toComputedValue(computed::Context &context) const override - { - switch (tag_) + private: + bool parse(const std::string &input) override + { + if (input == "row") + tag_ = kRow; + else if (input == "row-reverse") + tag_ = kRowReverse; + else if (input == "column") + tag_ = kColumn; + else if (input == "column-reverse") + tag_ = kColumnReverse; + else + tag_ = kRow; + return true; + } + + public: + computed::FlexDirection toComputedValue(computed::Context &context) const override { - case kRow: - return computed::FlexDirection::Row(); - case kRowReverse: - return computed::FlexDirection::RowReverse(); - case kColumn: - return computed::FlexDirection::Column(); - case kColumnReverse: - return computed::FlexDirection::ColumnReverse(); - default: - return computed::FlexDirection::Row(); + switch (tag_) + { + case kRow: + return computed::FlexDirection::Row(); + case kRowReverse: + return computed::FlexDirection::RowReverse(); + case kColumn: + return computed::FlexDirection::Column(); + case kColumnReverse: + return computed::FlexDirection::ColumnReverse(); + default: + return computed::FlexDirection::Row(); + } } - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/font.hpp b/src/client/cssom/values/specified/font.hpp index b603f4d7a..0930f6d76 100644 --- a/src/client/cssom/values/specified/font.hpp +++ b/src/client/cssom/values/specified/font.hpp @@ -8,120 +8,122 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - enum class SystemFont + namespace client_cssom::values::specified { - kCaption, - kIcon, - kMenu, - kMessageBox, - kSmallCaption, - kStatusBar, - kEnd, - }; - - enum class FontSizeKeyword - { - kXXSmall, - kXSmall, - kSmall, - kMedium, - kLarge, - kXLarge, - kXXLarge, - kMath, - kNone, - }; - - class KeywordInfo : public ToCss, - public ToComputedValue - { - public: - KeywordInfo(FontSizeKeyword keyword) - : keyword(keyword) - , factor(0.0f) - , offset(0.0f) + enum class SystemFont + { + kCaption, + kIcon, + kMenu, + kMessageBox, + kSmallCaption, + kStatusBar, + kEnd, + }; + + enum class FontSizeKeyword { - } + kXXSmall, + kXSmall, + kSmall, + kMedium, + kLarge, + kXLarge, + kXXLarge, + kMath, + kNone, + }; - std::string toCss() const override + class KeywordInfo : public ToCss, + public ToComputedValue { - switch (keyword) + public: + KeywordInfo(FontSizeKeyword keyword) + : keyword(keyword) + , factor(0.0f) + , offset(0.0f) { - case FontSizeKeyword::kXXSmall: - return "xx-small"; - case FontSizeKeyword::kXSmall: - return "x-small"; - case FontSizeKeyword::kSmall: - return "small"; - case FontSizeKeyword::kMedium: - return "medium"; - case FontSizeKeyword::kLarge: - return "large"; - case FontSizeKeyword::kXLarge: - return "x-large"; - case FontSizeKeyword::kXXLarge: - return "xx-large"; - case FontSizeKeyword::kMath: - return "math"; - case FontSizeKeyword::kNone: - return "none"; } - assert(false && "Invalid keyword."); - } - float toComputedValue(computed::Context &) const override - { - switch (keyword) + + std::string toCss() const override { - case FontSizeKeyword::kXXSmall: - return 0.5f; - case FontSizeKeyword::kXSmall: - return 0.75f; - case FontSizeKeyword::kSmall: - return 0.875f; - case FontSizeKeyword::kMedium: - return 1.0f; - case FontSizeKeyword::kLarge: - return 1.25f; - case FontSizeKeyword::kXLarge: - return 1.5f; - case FontSizeKeyword::kXXLarge: - return 2.0f; - case FontSizeKeyword::kMath: - return 1.2f; - case FontSizeKeyword::kNone: - return 0.0f; + switch (keyword) + { + case FontSizeKeyword::kXXSmall: + return "xx-small"; + case FontSizeKeyword::kXSmall: + return "x-small"; + case FontSizeKeyword::kSmall: + return "small"; + case FontSizeKeyword::kMedium: + return "medium"; + case FontSizeKeyword::kLarge: + return "large"; + case FontSizeKeyword::kXLarge: + return "x-large"; + case FontSizeKeyword::kXXLarge: + return "xx-large"; + case FontSizeKeyword::kMath: + return "math"; + case FontSizeKeyword::kNone: + return "none"; + } + assert(false && "Invalid keyword."); + } + float toComputedValue(computed::Context &) const override + { + switch (keyword) + { + case FontSizeKeyword::kXXSmall: + return 0.5f; + case FontSizeKeyword::kXSmall: + return 0.75f; + case FontSizeKeyword::kSmall: + return 0.875f; + case FontSizeKeyword::kMedium: + return 1.0f; + case FontSizeKeyword::kLarge: + return 1.25f; + case FontSizeKeyword::kXLarge: + return 1.5f; + case FontSizeKeyword::kXXLarge: + return 2.0f; + case FontSizeKeyword::kMath: + return 1.2f; + case FontSizeKeyword::kNone: + return 0.0f; + } + assert(false && "Invalid keyword."); } - assert(false && "Invalid keyword."); - } - - public: - FontSizeKeyword keyword; - float factor; - float offset; - }; - - class FontSize : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; - private: - enum Tag - { - kLength, - kKeyword, - kSmaller, - kLarger, - kSystem, + public: + FontSizeKeyword keyword; + float factor; + float offset; }; - using ValueVariant = std::variant; - private: - bool parse(const std::string &input) override + class FontSize : public Parse, + public ToCss, + public ToComputedValue { + friend class Parse; + + private: + enum Tag + { + kLength, + kKeyword, + kSmaller, + kLarger, + kSystem, + }; + using ValueVariant = std::variant; + + private: + bool parse(const std::string &input) override + { #define CHECK_AND_SET(NAME, TAG, VALUE) \ if (input == NAME) \ { \ @@ -137,348 +139,349 @@ namespace client_cssom::values::specified return true; \ } - // System font keywords. - CHECK_AND_SET("caption", kSystem, SystemFont::kCaption) - CHECK_AND_SET("icon", kSystem, SystemFont::kIcon) - CHECK_AND_SET("menu", kSystem, SystemFont::kMenu) - CHECK_AND_SET("message-box", kSystem, SystemFont::kMessageBox) - CHECK_AND_SET("small-caption", kSystem, SystemFont::kSmallCaption) - CHECK_AND_SET("status-bar", kSystem, SystemFont::kStatusBar) - - // Font size keywords. - CHECK_AND_SET("xx-small", kKeyword, KeywordInfo(FontSizeKeyword::kXXSmall)) - CHECK_AND_SET("x-small", kKeyword, KeywordInfo(FontSizeKeyword::kXSmall)) - CHECK_AND_SET("small", kKeyword, KeywordInfo(FontSizeKeyword::kSmall)) - CHECK_AND_SET("medium", kKeyword, KeywordInfo(FontSizeKeyword::kMedium)) - CHECK_AND_SET("large", kKeyword, KeywordInfo(FontSizeKeyword::kLarge)) - CHECK_AND_SET("x-large", kKeyword, KeywordInfo(FontSizeKeyword::kXLarge)) - CHECK_AND_SET("xx-large", kKeyword, KeywordInfo(FontSizeKeyword::kXXLarge)) - CHECK_AND_SET("math", kKeyword, KeywordInfo(FontSizeKeyword::kMath)) - CHECK_AND_SET("none", kKeyword, KeywordInfo(FontSizeKeyword::kNone)) - - // Font size relative keywords. - CHECK_AND_SET_TAG_ONLY("smaller", kSmaller) - CHECK_AND_SET_TAG_ONLY("larger", kLarger) + // System font keywords. + CHECK_AND_SET("caption", kSystem, SystemFont::kCaption) + CHECK_AND_SET("icon", kSystem, SystemFont::kIcon) + CHECK_AND_SET("menu", kSystem, SystemFont::kMenu) + CHECK_AND_SET("message-box", kSystem, SystemFont::kMessageBox) + CHECK_AND_SET("small-caption", kSystem, SystemFont::kSmallCaption) + CHECK_AND_SET("status-bar", kSystem, SystemFont::kStatusBar) + + // Font size keywords. + CHECK_AND_SET("xx-small", kKeyword, KeywordInfo(FontSizeKeyword::kXXSmall)) + CHECK_AND_SET("x-small", kKeyword, KeywordInfo(FontSizeKeyword::kXSmall)) + CHECK_AND_SET("small", kKeyword, KeywordInfo(FontSizeKeyword::kSmall)) + CHECK_AND_SET("medium", kKeyword, KeywordInfo(FontSizeKeyword::kMedium)) + CHECK_AND_SET("large", kKeyword, KeywordInfo(FontSizeKeyword::kLarge)) + CHECK_AND_SET("x-large", kKeyword, KeywordInfo(FontSizeKeyword::kXLarge)) + CHECK_AND_SET("xx-large", kKeyword, KeywordInfo(FontSizeKeyword::kXXLarge)) + CHECK_AND_SET("math", kKeyword, KeywordInfo(FontSizeKeyword::kMath)) + CHECK_AND_SET("none", kKeyword, KeywordInfo(FontSizeKeyword::kNone)) + + // Font size relative keywords. + CHECK_AND_SET_TAG_ONLY("smaller", kSmaller) + CHECK_AND_SET_TAG_ONLY("larger", kLarger) #undef CHECK_AND_SET #undef CHECK_AND_SET_TAG_ONLY - // Length - tag_ = kLength; - value_ = Parse::ParseSingleValue(input); - return true; - } - - public: - std::string toCss() const override - { - switch (tag_) - { - case kLength: - return std::get(value_).toCss(); - case kKeyword: - return std::get(value_).toCss(); - case kSmaller: - return "smaller"; - case kLarger: - return "larger"; - case kSystem: - return "system"; + // Length + tag_ = kLength; + value_ = Parse::ParseSingleValue(input); + return true; } - assert(false && "Invalid tag."); - } - computed::FontSize toComputedValue(computed::Context &context) const override - { - if (tag_ == kLength) + public: + std::string toCss() const override { - const auto &length = std::get(value_); - return computed::FontSize(length.toComputedValue(context).getLength().px()); + switch (tag_) + { + case kLength: + return std::get(value_).toCss(); + case kKeyword: + return std::get(value_).toCss(); + case kSmaller: + return "smaller"; + case kLarger: + return "larger"; + case kSystem: + return "system"; + } + assert(false && "Invalid tag."); } - if (tag_ == kKeyword) + + computed::FontSize toComputedValue(computed::Context &context) const override { - const auto &keyword = std::get(value_); - return computed::FontSize(keyword.toComputedValue(context)); + if (tag_ == kLength) + { + const auto &length = std::get(value_); + return computed::FontSize(length.toComputedValue(context).getLength().px()); + } + if (tag_ == kKeyword) + { + const auto &keyword = std::get(value_); + return computed::FontSize(keyword.toComputedValue(context)); + } + return computed::FontSize(); } - return computed::FontSize(); - } - - private: - Tag tag_; - ValueVariant value_ = KeywordInfo(FontSizeKeyword::kMedium); - }; - class AbsoluteFontWeight : public ToCss, - public ToComputedValue - { - private: - enum Tag - { - kWeight, - kNormal, - kBold, + private: + Tag tag_; + ValueVariant value_ = KeywordInfo(FontSizeKeyword::kMedium); }; - public: - static AbsoluteFontWeight Normal() - { - return AbsoluteFontWeight(kNormal, 400); - } - static AbsoluteFontWeight Bold() + class AbsoluteFontWeight : public ToCss, + public ToComputedValue { - return AbsoluteFontWeight(kBold, 700); - } - static AbsoluteFontWeight Weight(int weight_number) - { - if (weight_number < 1 || weight_number > 1000) - throw std::out_of_range("Weight number must be between 1 and 1000"); - return AbsoluteFontWeight(kWeight, weight_number); - } - - private: - AbsoluteFontWeight(Tag tag, float weight_number) - : tag_(tag) - , weight_number_(weight_number) - { - } + private: + enum Tag + { + kWeight, + kNormal, + kBold, + }; - public: - std::string toCss() const override - { - switch (tag_) + public: + static AbsoluteFontWeight Normal() { - case kWeight: - return std::to_string(weight_number_); - case kNormal: - return "normal"; - case kBold: - return "bold"; + return AbsoluteFontWeight(kNormal, 400); } - assert(false && "Invalid tag."); - } - int toComputedValue(computed::Context &) const override - { - return weight_number_; - } - - private: - Tag tag_; - int weight_number_; - }; - - class FontWeight : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; - - private: - enum Tag : uint8_t - { - kAbsolute, - kBolder, - kLighter, - kSystem, - }; - using ValueVariant = std::variant; - - private: - bool parse(const std::string &input) override - { - if (isdigit(input[0])) + static AbsoluteFontWeight Bold() { - tag_ = kAbsolute; - value_ = AbsoluteFontWeight::Weight(std::stoi(input)); - return true; + return AbsoluteFontWeight(kBold, 700); } - if (input == "normal") + static AbsoluteFontWeight Weight(int weight_number) { - tag_ = kAbsolute; - value_ = AbsoluteFontWeight::Normal(); - return true; + if (weight_number < 1 || weight_number > 1000) + throw std::out_of_range("Weight number must be between 1 and 1000"); + return AbsoluteFontWeight(kWeight, weight_number); } - if (input == "bold") + + private: + AbsoluteFontWeight(Tag tag, float weight_number) + : tag_(tag) + , weight_number_(weight_number) { - tag_ = kAbsolute; - value_ = AbsoluteFontWeight::Bold(); - return true; } - if (input == "bolder") + + public: + std::string toCss() const override { - tag_ = kBolder; - return true; + switch (tag_) + { + case kWeight: + return std::to_string(weight_number_); + case kNormal: + return "normal"; + case kBold: + return "bold"; + } + assert(false && "Invalid tag."); } - if (input == "lighter") + int toComputedValue(computed::Context &) const override { - tag_ = kLighter; - return true; + return weight_number_; } - // TODO(yorkie): support system font. - return false; - } + private: + Tag tag_; + int weight_number_; + }; - public: - std::string toCss() const override - { - switch (tag_) - { - case kAbsolute: - return std::get(value_).toCss(); - case kBolder: - return "bolder"; - case kLighter: - return "lighter"; - case kSystem: - // TODO(yorkie): support system font. - return "system"; - } - assert(false && "Invalid tag."); - } - computed::FontWeight toComputedValue(computed::Context &context) const override + class FontWeight : public Parse, + public ToCss, + public ToComputedValue { - if (tag_ == kAbsolute) - return computed::FontWeight(std::get(value_).toComputedValue(context)); - if (tag_ == kBolder) - return computed::FontWeight(context.baseFontWeight() + 100); - if (tag_ == kLighter) - return computed::FontWeight(context.baseFontWeight() - 100); - assert(false && "Invalid tag."); - } - - private: - Tag tag_; - ValueVariant value_ = AbsoluteFontWeight::Normal(); - }; - - class FontStyle : public generics::FontStyle, - public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; + friend class Parse; - private: - bool parse(const std::string &input) override - { - if (input == "normal") - { - tag_ = kNormal; - return true; - } - if (input == "italic") + private: + enum Tag : uint8_t { - tag_ = kItalic; - return true; - } - if (input == "oblique") - { - tag_ = kOblique; - return true; - } - if (input.starts_with("oblique ")) + kAbsolute, + kBolder, + kLighter, + kSystem, + }; + using ValueVariant = std::variant; + + private: + bool parse(const std::string &input) override { - tag_ = kOblique; - auto angle_str = input.substr(8); - if (!angle_str.empty()) - oblique_angle_ = Parse::ParseSingleValue(angle_str); - return true; + if (isdigit(input[0])) + { + tag_ = kAbsolute; + value_ = AbsoluteFontWeight::Weight(std::stoi(input)); + return true; + } + if (input == "normal") + { + tag_ = kAbsolute; + value_ = AbsoluteFontWeight::Normal(); + return true; + } + if (input == "bold") + { + tag_ = kAbsolute; + value_ = AbsoluteFontWeight::Bold(); + return true; + } + if (input == "bolder") + { + tag_ = kBolder; + return true; + } + if (input == "lighter") + { + tag_ = kLighter; + return true; + } + + // TODO(yorkie): support system font. + return false; } - return false; - } - public: - std::string toCss() const override - { - switch (tag_) + public: + std::string toCss() const override { - case kNormal: - return "normal"; - case kItalic: - return "italic"; - case kOblique: - if (oblique_angle_.has_value()) - return "oblique " + oblique_angle_->toCss(); - return "oblique"; + switch (tag_) + { + case kAbsolute: + return std::get(value_).toCss(); + case kBolder: + return "bolder"; + case kLighter: + return "lighter"; + case kSystem: + // TODO(yorkie): support system font. + return "system"; + } + assert(false && "Invalid tag."); } - assert(false && "Invalid tag."); - } - computed::FontStyle toComputedValue(computed::Context &context) const override - { - switch (tag_) + computed::FontWeight toComputedValue(computed::Context &context) const override { - case kNormal: - return computed::FontStyle::Normal(); - case kItalic: - return computed::FontStyle::Italic(); - case kOblique: - return computed::FontStyle::Oblique(oblique_angle_->toComputedValue(context)); + if (tag_ == kAbsolute) + return computed::FontWeight(std::get(value_).toComputedValue(context)); + if (tag_ == kBolder) + return computed::FontWeight(context.baseFontWeight() + 100); + if (tag_ == kLighter) + return computed::FontWeight(context.baseFontWeight() - 100); + assert(false && "Invalid tag."); } - assert(false && "Invalid tag."); - } - }; - class LineHeight : public generics::GenericLineHeight, - public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; - using generics::GenericLineHeight::GenericLineHeight; + private: + Tag tag_; + ValueVariant value_ = AbsoluteFontWeight::Normal(); + }; - private: - bool parse(const std::string &input) override + class FontStyle : public generics::FontStyle, + public Parse, + public ToCss, + public ToComputedValue { - if (input == "normal") + friend class Parse; + + private: + bool parse(const std::string &input) override { - setNormal(); - return true; + if (input == "normal") + { + tag_ = kNormal; + return true; + } + if (input == "italic") + { + tag_ = kItalic; + return true; + } + if (input == "oblique") + { + tag_ = kOblique; + return true; + } + if (input.starts_with("oblique ")) + { + tag_ = kOblique; + auto angle_str = input.substr(8); + if (!angle_str.empty()) + oblique_angle_ = Parse::ParseSingleValue(angle_str); + return true; + } + return false; } - if (LengthPercentage::IsLengthOrPercentage(input)) + + public: + std::string toCss() const override { - setLength(Parse::ParseSingleValue(input)); - return true; + switch (tag_) + { + case kNormal: + return "normal"; + case kItalic: + return "italic"; + case kOblique: + if (oblique_angle_.has_value()) + return "oblique " + oblique_angle_->toCss(); + return "oblique"; + } + assert(false && "Invalid tag."); } - else + computed::FontStyle toComputedValue(computed::Context &context) const override { - setNumber(Parse::ParseSingleValue(input)); - return true; + switch (tag_) + { + case kNormal: + return computed::FontStyle::Normal(); + case kItalic: + return computed::FontStyle::Italic(); + case kOblique: + return computed::FontStyle::Oblique(oblique_angle_->toComputedValue(context)); + } + assert(false && "Invalid tag."); } - return false; - } + }; - public: - std::string toCss() const override - { - if (isNormal()) - return "normal"; - if (isLength()) - return getLength().toCss(); - if (isNumber()) - return getNumber().toCss(); - - // unreachable - assert(false && "Invalid tag."); - } - computed::LineHeight toComputedValue(computed::Context &context) const override + class LineHeight : public generics::GenericLineHeight, + public Parse, + public ToCss, + public ToComputedValue { - if (isNormal()) + friend class Parse; + using generics::GenericLineHeight::GenericLineHeight; + + private: + bool parse(const std::string &input) override { - return computed::LineHeight::Normal(); + if (input == "normal") + { + setNormal(); + return true; + } + if (LengthPercentage::IsLengthOrPercentage(input)) + { + setLength(Parse::ParseSingleValue(input)); + return true; + } + else + { + setNumber(Parse::ParseSingleValue(input)); + return true; + } + return false; } - if (isLength()) + + public: + std::string toCss() const override { - auto computed_length = getLength().toComputedValue(context).getLength(); - return computed::LineHeight::Length(computed_length.px()); + if (isNormal()) + return "normal"; + if (isLength()) + return getLength().toCss(); + if (isNumber()) + return getNumber().toCss(); + + // unreachable + assert(false && "Invalid tag."); } - if (isNumber()) + computed::LineHeight toComputedValue(computed::Context &context) const override { - CSSFloat computed_number = getNumber().toComputedValue(context); - return computed::LineHeight::Number(computed_number); + if (isNormal()) + { + return computed::LineHeight::Normal(); + } + if (isLength()) + { + auto computed_length = getLength().toComputedValue(context).getLength(); + return computed::LineHeight::Length(computed_length.px()); + } + if (isNumber()) + { + CSSFloat computed_number = getNumber().toComputedValue(context); + return computed::LineHeight::Number(computed_number); + } + + // unreachable + assert(false && "Invalid tag."); } - - // unreachable - assert(false && "Invalid tag."); - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/image.cpp b/src/client/cssom/values/specified/image.cpp index 9a1a8bcde..095e922bf 100644 --- a/src/client/cssom/values/specified/image.cpp +++ b/src/client/cssom/values/specified/image.cpp @@ -3,261 +3,264 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - bool Image::parse(const std::string &input) + namespace client_cssom::values::specified { - // Use the new CSS image parser with proper tokenization - *this = css_parser::CSSImageParser::parseImage(input); - return !isNone() || input == "none"; - } - - std::string Image::toCss() const - { - if (isGradient()) - { - const auto &gradient = std::get(*this); - return gradientToCss(gradient); - } - else + bool Image::parse(const std::string &input) { - // Delegate to base class for monostate and URL cases - return generics::GenericImage::toCss(); + // Use the new CSS image parser with proper tokenization + *this = css_parser::CSSImageParser::parseImage(input); + return !isNone() || input == "none"; } - } - - computed::Image Image::toComputedValue(computed::Context &context) const - { - computed::Image computed_img; - if (isNone()) - { - computed_img.emplace(); - } - else if (isUrl()) + std::string Image::toCss() const { - const auto &url_or_none = std::get(*this); - - // Resolve relative URLs if the URL is not absolute - if (url_or_none.url.has_value()) + if (isGradient()) { - std::string original_url = url_or_none.url.value(); - std::string resolved_url = original_url; - - // Check if URL is relative (doesn't start with protocol or is a data URL) - if (!original_url.empty() && - original_url.find("://") == std::string::npos && - original_url.find("data:") != 0) - { - // Get base URI from context - std::string base_uri = context.getBaseURI(); - if (!base_uri.empty()) - { - // Use UrlHelper to resolve relative URL - resolved_url = crates::UrlHelper::CreateUrlStringWithPath(base_uri, original_url); - } - } - - // Create new UrlOrNone with resolved URL - UrlOrNone resolved_url_or_none = UrlOrNone::Url(resolved_url); - computed_img.emplace(resolved_url_or_none); + const auto &gradient = std::get(*this); + return gradientToCss(gradient); } else { - computed_img.emplace(url_or_none); + // Delegate to base class for monostate and URL cases + return generics::GenericImage::toCss(); } } - else if (isGradient()) - { - const auto &gradient = std::get(*this); - computed::Gradient computed_gradient = gradient.toComputedValue(context); - computed_img.emplace(computed_gradient); - } - return computed_img; - } - - std::string Image::gradientToCss(const Gradient &gradient) const - { - // Enhanced gradient serialization with proper CSS output - if (holds_alternative(gradient.gradient_type)) + computed::Image Image::toComputedValue(computed::Context &context) const { - const auto &linearGrad = std::get(gradient.gradient_type); - std::string functionName = gradient.repeating ? "repeating-linear-gradient" : "linear-gradient"; + computed::Image computed_img; - std::string direction; - switch (linearGrad.direction) + if (isNone()) { - case generics::LineDirection::kToRight: - direction = "to right"; - break; - case generics::LineDirection::kToLeft: - direction = "to left"; - break; - case generics::LineDirection::kToTop: - direction = "to top"; - break; - case generics::LineDirection::kToBottom: - direction = "to bottom"; - break; - case generics::LineDirection::kToTopLeft: - direction = "to top left"; - break; - case generics::LineDirection::kToTopRight: - direction = "to top right"; - break; - case generics::LineDirection::kToBottomLeft: - direction = "to bottom left"; - break; - case generics::LineDirection::kToBottomRight: - direction = "to bottom right"; - break; + computed_img.emplace(); } - - // TODO: Add proper color stop serialization - std::string colorStops = "red, blue"; // Placeholder - if (!linearGrad.items.empty()) + else if (isUrl()) { - // Serialize actual color stops when color parsing is implemented - } + const auto &url_or_none = std::get(*this); - return functionName + "(" + direction + ", " + colorStops + ")"; - } - else if (holds_alternative(gradient.gradient_type)) - { - const auto &radialGrad = std::get(gradient.gradient_type); - std::string functionName = gradient.repeating ? "repeating-radial-gradient" : "radial-gradient"; - std::string shape = (radialGrad.shape == generics::RadialGradientShape::kCircle) ? "circle" : "ellipse"; + // Resolve relative URLs if the URL is not absolute + if (url_or_none.url.has_value()) + { + std::string original_url = url_or_none.url.value(); + std::string resolved_url = original_url; - std::string size; - switch (radialGrad.size) - { - case generics::RadialGradientSize::kClosestSide: - size = "closest-side"; - break; - case generics::RadialGradientSize::kClosestCorner: - size = "closest-corner"; - break; - case generics::RadialGradientSize::kFarthestSide: - size = "farthest-side"; - break; - case generics::RadialGradientSize::kFarthestCorner: - size = "farthest-corner"; - break; - } + // Check if URL is relative (doesn't start with protocol or is a data URL) + if (!original_url.empty() && + original_url.find("://") == std::string::npos && + original_url.find("data:") != 0) + { + // Get base URI from context + std::string base_uri = context.getBaseURI(); + if (!base_uri.empty()) + { + // Use UrlHelper to resolve relative URL + resolved_url = crates::UrlHelper::CreateUrlStringWithPath(base_uri, original_url); + } + } - // TODO: Add proper color stop serialization - std::string colorStops = "red, blue"; // Placeholder - if (!radialGrad.items.empty()) - { - // Serialize actual color stops when color parsing is implemented + // Create new UrlOrNone with resolved URL + UrlOrNone resolved_url_or_none = UrlOrNone::Url(resolved_url); + computed_img.emplace(resolved_url_or_none); + } + else + { + computed_img.emplace(url_or_none); + } } - - std::string shapeSize = shape; - if (!size.empty()) + else if (isGradient()) { - shapeSize += " " + size; + const auto &gradient = std::get(*this); + computed::Gradient computed_gradient = gradient.toComputedValue(context); + computed_img.emplace(computed_gradient); } - return functionName + "(" + shapeSize + ", " + colorStops + ")"; + return computed_img; } - return "none"; - } - - // Gradient conversion to computed value - computed::Gradient Gradient::toComputedValue(computed::Context &context) const - { - computed::Gradient computed_gradient; - computed_gradient.repeating = this->repeating; - - // Convert gradient type from specified to computed - if (std::holds_alternative(this->gradient_type)) + std::string Image::gradientToCss(const Gradient &gradient) const { - const auto &specified_linear = std::get(this->gradient_type); - computed::Gradient::LinearGradient computed_linear; + // Enhanced gradient serialization with proper CSS output + if (holds_alternative(gradient.gradient_type)) + { + const auto &linearGrad = std::get(gradient.gradient_type); + std::string functionName = gradient.repeating ? "repeating-linear-gradient" : "linear-gradient"; + + std::string direction; + switch (linearGrad.direction) + { + case generics::LineDirection::kToRight: + direction = "to right"; + break; + case generics::LineDirection::kToLeft: + direction = "to left"; + break; + case generics::LineDirection::kToTop: + direction = "to top"; + break; + case generics::LineDirection::kToBottom: + direction = "to bottom"; + break; + case generics::LineDirection::kToTopLeft: + direction = "to top left"; + break; + case generics::LineDirection::kToTopRight: + direction = "to top right"; + break; + case generics::LineDirection::kToBottomLeft: + direction = "to bottom left"; + break; + case generics::LineDirection::kToBottomRight: + direction = "to bottom right"; + break; + } - // Copy direction (no conversion needed for enum) - computed_linear.direction = specified_linear.direction; + // TODO: Add proper color stop serialization + std::string colorStops = "red, blue"; // Placeholder + if (!linearGrad.items.empty()) + { + // Serialize actual color stops when color parsing is implemented + } - // Convert gradient items - for (const auto &item : specified_linear.items) + return functionName + "(" + direction + ", " + colorStops + ")"; + } + else if (holds_alternative(gradient.gradient_type)) { - computed::Gradient::LinearGradient::GradientItem computed_item; - computed_item.type = item.type; + const auto &radialGrad = std::get(gradient.gradient_type); + std::string functionName = gradient.repeating ? "repeating-radial-gradient" : "radial-gradient"; + std::string shape = (radialGrad.shape == generics::RadialGradientShape::kCircle) ? "circle" : "ellipse"; - if (item.type == generics::GenericGradientItemBase::kSimpleColorStop) + std::string size; + switch (radialGrad.size) { - const auto &color_stop = std::get::SimpleColorStop>(item.value); - typename computed::Gradient::LinearGradient::GradientItem::SimpleColorStop computed_stop; - computed_stop.color = color_stop.color.toComputedValue(context); - computed_item.value = computed_stop; + case generics::RadialGradientSize::kClosestSide: + size = "closest-side"; + break; + case generics::RadialGradientSize::kClosestCorner: + size = "closest-corner"; + break; + case generics::RadialGradientSize::kFarthestSide: + size = "farthest-side"; + break; + case generics::RadialGradientSize::kFarthestCorner: + size = "farthest-corner"; + break; } - else if (item.type == generics::GenericGradientItemBase::kComplexColorStop) + + // TODO: Add proper color stop serialization + std::string colorStops = "red, blue"; // Placeholder + if (!radialGrad.items.empty()) { - const auto &color_stop = std::get::ComplexColorStop>(item.value); - typename computed::Gradient::LinearGradient::GradientItem::ComplexColorStop computed_stop; - computed_stop.color = color_stop.color.toComputedValue(context); - computed_stop.length_percentage = color_stop.length_percentage.toComputedValue(context); - computed_item.value = computed_stop; + // Serialize actual color stops when color parsing is implemented } - else if (item.type == generics::GenericGradientItemBase::kInterpolationHint) + + std::string shapeSize = shape; + if (!size.empty()) { - const auto &hint = std::get::InterpolationHint>(item.value); - typename computed::Gradient::LinearGradient::GradientItem::InterpolationHint computed_hint; - computed_hint.length_percentage = hint.length_percentage.toComputedValue(context); - computed_item.value = computed_hint; + shapeSize += " " + size; } - computed_linear.items.push_back(computed_item); + return functionName + "(" + shapeSize + ", " + colorStops + ")"; } - computed_gradient.gradient_type = computed_linear; + return "none"; } - else if (std::holds_alternative(this->gradient_type)) - { - const auto &specified_radial = std::get(this->gradient_type); - computed::Gradient::RadialGradient computed_radial; - // Copy shape and size (no conversion needed for enums) - computed_radial.shape = specified_radial.shape; - computed_radial.size = specified_radial.size; + // Gradient conversion to computed value + computed::Gradient Gradient::toComputedValue(computed::Context &context) const + { + computed::Gradient computed_gradient; + computed_gradient.repeating = this->repeating; - // Convert gradient items - for (const auto &item : specified_radial.items) + // Convert gradient type from specified to computed + if (std::holds_alternative(this->gradient_type)) { - computed::Gradient::RadialGradient::GradientItem computed_item; - computed_item.type = item.type; + const auto &specified_linear = std::get(this->gradient_type); + computed::Gradient::LinearGradient computed_linear; - if (item.type == generics::GenericGradientItem::kSimpleColorStop) - { - const auto &color_stop = std::get::SimpleColorStop>(item.value); - typename computed::Gradient::RadialGradient::GradientItem::SimpleColorStop computed_stop; - computed_stop.color = color_stop.color.toComputedValue(context); - computed_item.value = computed_stop; - } - else if (item.type == generics::GenericGradientItem::kComplexColorStop) + // Copy direction (no conversion needed for enum) + computed_linear.direction = specified_linear.direction; + + // Convert gradient items + for (const auto &item : specified_linear.items) { - const auto &color_stop = std::get::ComplexColorStop>(item.value); - typename computed::Gradient::RadialGradient::GradientItem::ComplexColorStop computed_stop; - computed_stop.color = color_stop.color.toComputedValue(context); - computed_stop.length_percentage = color_stop.length_percentage.toComputedValue(context); - computed_item.value = computed_stop; + computed::Gradient::LinearGradient::GradientItem computed_item; + computed_item.type = item.type; + + if (item.type == generics::GenericGradientItemBase::kSimpleColorStop) + { + const auto &color_stop = std::get::SimpleColorStop>(item.value); + typename computed::Gradient::LinearGradient::GradientItem::SimpleColorStop computed_stop; + computed_stop.color = color_stop.color.toComputedValue(context); + computed_item.value = computed_stop; + } + else if (item.type == generics::GenericGradientItemBase::kComplexColorStop) + { + const auto &color_stop = std::get::ComplexColorStop>(item.value); + typename computed::Gradient::LinearGradient::GradientItem::ComplexColorStop computed_stop; + computed_stop.color = color_stop.color.toComputedValue(context); + computed_stop.length_percentage = color_stop.length_percentage.toComputedValue(context); + computed_item.value = computed_stop; + } + else if (item.type == generics::GenericGradientItemBase::kInterpolationHint) + { + const auto &hint = std::get::InterpolationHint>(item.value); + typename computed::Gradient::LinearGradient::GradientItem::InterpolationHint computed_hint; + computed_hint.length_percentage = hint.length_percentage.toComputedValue(context); + computed_item.value = computed_hint; + } + + computed_linear.items.push_back(computed_item); } - else if (item.type == generics::GenericGradientItem::kInterpolationHint) + + computed_gradient.gradient_type = computed_linear; + } + else if (std::holds_alternative(this->gradient_type)) + { + const auto &specified_radial = std::get(this->gradient_type); + computed::Gradient::RadialGradient computed_radial; + + // Copy shape and size (no conversion needed for enums) + computed_radial.shape = specified_radial.shape; + computed_radial.size = specified_radial.size; + + // Convert gradient items + for (const auto &item : specified_radial.items) { - const auto &hint = std::get::InterpolationHint>(item.value); - typename computed::Gradient::RadialGradient::GradientItem::InterpolationHint computed_hint; - computed_hint.length_percentage = hint.length_percentage.toComputedValue(context); - computed_item.value = computed_hint; + computed::Gradient::RadialGradient::GradientItem computed_item; + computed_item.type = item.type; + + if (item.type == generics::GenericGradientItem::kSimpleColorStop) + { + const auto &color_stop = std::get::SimpleColorStop>(item.value); + typename computed::Gradient::RadialGradient::GradientItem::SimpleColorStop computed_stop; + computed_stop.color = color_stop.color.toComputedValue(context); + computed_item.value = computed_stop; + } + else if (item.type == generics::GenericGradientItem::kComplexColorStop) + { + const auto &color_stop = std::get::ComplexColorStop>(item.value); + typename computed::Gradient::RadialGradient::GradientItem::ComplexColorStop computed_stop; + computed_stop.color = color_stop.color.toComputedValue(context); + computed_stop.length_percentage = color_stop.length_percentage.toComputedValue(context); + computed_item.value = computed_stop; + } + else if (item.type == generics::GenericGradientItem::kInterpolationHint) + { + const auto &hint = std::get::InterpolationHint>(item.value); + typename computed::Gradient::RadialGradient::GradientItem::InterpolationHint computed_hint; + computed_hint.length_percentage = hint.length_percentage.toComputedValue(context); + computed_item.value = computed_hint; + } + + computed_radial.items.push_back(computed_item); } - computed_radial.items.push_back(computed_item); + computed_gradient.gradient_type = computed_radial; } - computed_gradient.gradient_type = computed_radial; + return computed_gradient; } - - return computed_gradient; } -} +} // namespace endor diff --git a/src/client/cssom/values/specified/image.hpp b/src/client/cssom/values/specified/image.hpp index ee8f1795f..80415a9c8 100644 --- a/src/client/cssom/values/specified/image.hpp +++ b/src/client/cssom/values/specified/image.hpp @@ -10,85 +10,88 @@ #include "./color.hpp" #include "./url.hpp" -namespace client_cssom::values::specified +namespace endor { - using GradientItem = generics::GenericGradientItem; - using GradientBase = generics::GenericGradient; - - class Gradient : public GradientBase, - public ToComputedValue - { - using GradientBase::GenericGradient; - - public: - // Convert to computed value - computed::Gradient toComputedValue(computed::Context &context) const; - }; - - class Image : public generics::GenericImage, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - - public: - // Default constructor creates a 'none' image - Image() = default; - - // Copy constructor - Image(const Image &) = default; - Image &operator=(const Image &) = default; - - // Move constructor - Image(Image &&) = default; - Image &operator=(Image &&) = default; - - // Static factory methods - static Image None() - { - Image img; - img.emplace(); - return img; - } - - static Image Url(const std::string &url_str) - { - Image img; - img.emplace(UrlOrNone::Url(url_str)); - return img; - } + using GradientItem = generics::GenericGradientItem; + using GradientBase = generics::GenericGradient; - // Check if image is none/empty - bool isNone() const + class Gradient : public GradientBase, + public ToComputedValue { - return std::holds_alternative(*this); - } + using GradientBase::GenericGradient; - // Check if image is a URL - bool isUrl() const - { - return std::holds_alternative(*this); - } + public: + // Convert to computed value + computed::Gradient toComputedValue(computed::Context &context) const; + }; - // Check if image is a gradient - bool isGradient() const + class Image : public generics::GenericImage, + public Parse, + public ToComputedValue { - return std::holds_alternative(*this); - } - - // Parse implementation using proper CSS tokenizer and parser - bool parse(const std::string &input) override; - - // CSS serialization (override to provide gradient-specific behavior) - std::string toCss() const override; - - // Convert to computed value - computed::Image toComputedValue(computed::Context &context) const override; - - private: - // Helper method for gradient CSS serialization - std::string gradientToCss(const Gradient &gradient) const; - }; -} + friend class Parse; + + public: + // Default constructor creates a 'none' image + Image() = default; + + // Copy constructor + Image(const Image &) = default; + Image &operator=(const Image &) = default; + + // Move constructor + Image(Image &&) = default; + Image &operator=(Image &&) = default; + + // Static factory methods + static Image None() + { + Image img; + img.emplace(); + return img; + } + + static Image Url(const std::string &url_str) + { + Image img; + img.emplace(UrlOrNone::Url(url_str)); + return img; + } + + // Check if image is none/empty + bool isNone() const + { + return std::holds_alternative(*this); + } + + // Check if image is a URL + bool isUrl() const + { + return std::holds_alternative(*this); + } + + // Check if image is a gradient + bool isGradient() const + { + return std::holds_alternative(*this); + } + + // Parse implementation using proper CSS tokenizer and parser + bool parse(const std::string &input) override; + + // CSS serialization (override to provide gradient-specific behavior) + std::string toCss() const override; + + // Convert to computed value + computed::Image toComputedValue(computed::Context &context) const override; + + private: + // Helper method for gradient CSS serialization + std::string gradientToCss(const Gradient &gradient) const; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/length.hpp b/src/client/cssom/values/specified/length.hpp index a8c98cb3f..0537062d8 100644 --- a/src/client/cssom/values/specified/length.hpp +++ b/src/client/cssom/values/specified/length.hpp @@ -10,1020 +10,1023 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - // Number of pixels per inch - constexpr float PX_PER_IN = 96.0f; - // Number of pixels per centimeter - constexpr float PX_PER_CM = PX_PER_IN / 2.54f; - // Number of pixels per millimeter - constexpr float PX_PER_MM = PX_PER_IN / 25.4f; - // Number of pixels per quarter - constexpr float PX_PER_Q = PX_PER_MM / 4.0f; - // Number of pixels per point - constexpr float PX_PER_PT = PX_PER_IN / 72.0f; - // Number of pixels per pica - constexpr float PX_PER_PC = PX_PER_PT * 12.0f; - - class NoCalcLength; - class AbsoluteLength + namespace client_cssom::values::specified { - friend class NoCalcLength; - - private: - enum Tag - { - kPx, - kIn, - kCm, - kMm, - kQ, - kPt, - kPc, - }; - - static constexpr const char *UNIT_PX = "px"; - static constexpr const char *UNIT_IN = "in"; - static constexpr const char *UNIT_CM = "cm"; - static constexpr const char *UNIT_MM = "mm"; - static constexpr const char *UNIT_Q = "q"; - static constexpr const char *UNIT_PT = "pt"; - static constexpr const char *UNIT_PC = "pc"; - - public: - static AbsoluteLength Px(float value) - { - return AbsoluteLength(kPx, value); - } - static AbsoluteLength In(float value) - { - return AbsoluteLength(kIn, value); - } - static AbsoluteLength Cm(float value) - { - return AbsoluteLength(kCm, value); - } - static AbsoluteLength Mm(float value) - { - return AbsoluteLength(kMm, value); - } - static AbsoluteLength Q(float value) - { - return AbsoluteLength(kQ, value); - } - static AbsoluteLength Pt(float value) - { - return AbsoluteLength(kPt, value); - } - static AbsoluteLength Pc(float value) - { - return AbsoluteLength(kPc, value); - } + // Number of pixels per inch + constexpr float PX_PER_IN = 96.0f; + // Number of pixels per centimeter + constexpr float PX_PER_CM = PX_PER_IN / 2.54f; + // Number of pixels per millimeter + constexpr float PX_PER_MM = PX_PER_IN / 25.4f; + // Number of pixels per quarter + constexpr float PX_PER_Q = PX_PER_MM / 4.0f; + // Number of pixels per point + constexpr float PX_PER_PT = PX_PER_IN / 72.0f; + // Number of pixels per pica + constexpr float PX_PER_PC = PX_PER_PT * 12.0f; + + class NoCalcLength; + class AbsoluteLength + { + friend class NoCalcLength; + + private: + enum Tag + { + kPx, + kIn, + kCm, + kMm, + kQ, + kPt, + kPc, + }; + + static constexpr const char *UNIT_PX = "px"; + static constexpr const char *UNIT_IN = "in"; + static constexpr const char *UNIT_CM = "cm"; + static constexpr const char *UNIT_MM = "mm"; + static constexpr const char *UNIT_Q = "q"; + static constexpr const char *UNIT_PT = "pt"; + static constexpr const char *UNIT_PC = "pc"; + + public: + static AbsoluteLength Px(float value) + { + return AbsoluteLength(kPx, value); + } + static AbsoluteLength In(float value) + { + return AbsoluteLength(kIn, value); + } + static AbsoluteLength Cm(float value) + { + return AbsoluteLength(kCm, value); + } + static AbsoluteLength Mm(float value) + { + return AbsoluteLength(kMm, value); + } + static AbsoluteLength Q(float value) + { + return AbsoluteLength(kQ, value); + } + static AbsoluteLength Pt(float value) + { + return AbsoluteLength(kPt, value); + } + static AbsoluteLength Pc(float value) + { + return AbsoluteLength(kPc, value); + } - private: - AbsoluteLength(Tag tag, float value) - : tag_(tag) - , unitless_value_(value) - { - } + private: + AbsoluteLength(Tag tag, float value) + : tag_(tag) + , unitless_value_(value) + { + } - public: - inline float unitlessValue() const - { - return unitless_value_; - } - inline const char *unit() const - { - switch (tag_) - { - case kPx: - return UNIT_PX; - case kIn: - return UNIT_IN; - case kCm: - return UNIT_CM; - case kMm: - return UNIT_MM; - case kQ: - return UNIT_Q; - case kPt: - return UNIT_PT; - case kPc: - return UNIT_PC; - } - assert(false && "Invalid tag."); - } - - inline float toPx() const - { - switch (tag_) + public: + inline float unitlessValue() const { - case kPx: return unitless_value_; - case kIn: - return unitless_value_ * PX_PER_IN; - case kCm: - return unitless_value_ * PX_PER_CM; - case kMm: - return unitless_value_ * PX_PER_MM; - case kQ: - return unitless_value_ * PX_PER_Q; - case kPt: - return unitless_value_ * PX_PER_PT; - case kPc: - return unitless_value_ * PX_PER_PC; - } - assert(false && "Invalid tag."); - } - - private: - Tag tag_; - float unitless_value_; - }; - - class FontRelativeLength - { - friend class NoCalcLength; - - private: - enum Tag - { - kEm, - kEx, - kCh, - kCap, - kIc, - kRem, - kLh, - kRlh, - }; - - static constexpr const char *UNIT_EM = "em"; - static constexpr const char *UNIT_EX = "ex"; - static constexpr const char *UNIT_CH = "ch"; - static constexpr const char *UNIT_CAP = "cap"; - static constexpr const char *UNIT_IC = "ic"; - static constexpr const char *UNIT_REM = "rem"; - static constexpr const char *UNIT_LH = "lh"; - static constexpr const char *UNIT_RLH = "rlh"; - - public: - static FontRelativeLength Em(float value) - { - return FontRelativeLength(kEm, value); - } - static FontRelativeLength Ex(float value) - { - return FontRelativeLength(kEx, value); - } - static FontRelativeLength Ch(float value) - { - return FontRelativeLength(kCh, value); - } - static FontRelativeLength Cap(float value) - { - return FontRelativeLength(kCap, value); - } - static FontRelativeLength Ic(float value) - { - return FontRelativeLength(kIc, value); - } - static FontRelativeLength Rem(float value) - { - return FontRelativeLength(kRem, value); - } - static FontRelativeLength Lh(float value) - { - return FontRelativeLength(kLh, value); - } - static FontRelativeLength Rlh(float value) - { - return FontRelativeLength(kRlh, value); - } - - private: - FontRelativeLength(Tag tag, float value) - : tag_(tag) - , unitless_value_(value) - { - } + } + inline const char *unit() const + { + switch (tag_) + { + case kPx: + return UNIT_PX; + case kIn: + return UNIT_IN; + case kCm: + return UNIT_CM; + case kMm: + return UNIT_MM; + case kQ: + return UNIT_Q; + case kPt: + return UNIT_PT; + case kPc: + return UNIT_PC; + } + assert(false && "Invalid tag."); + } - public: - inline float unitlessValue() const - { - return unitless_value_; - } - const char *unit() const - { - switch (tag_) - { - case kEm: - return UNIT_EM; - case kEx: - return UNIT_EX; - case kCh: - return UNIT_CH; - case kCap: - return UNIT_CAP; - case kIc: - return UNIT_IC; - case kRem: - return UNIT_REM; - case kLh: - return UNIT_LH; - case kRlh: - return UNIT_RLH; - } - assert(false && "Invalid tag."); - } - - float toComputedValue(computed::Context &context) const - { - switch (tag_) - { - case kEm: - case kCap: - return unitless_value_ * context.baseFontSize(); - case kRem: - return unitless_value_ * context.rootFontSize(); - case kEx: - // Assuming 'ex' is half of the font size. - return unitless_value_ * context.baseFontSize() * 0.5f; - case kLh: - return unitless_value_ * context.baseLineHeight(); - case kRlh: - return unitless_value_ * context.rootLineHeight(); - default: - // Returns the root font-size if not supported - return context.rootFontSize(); - } - } - - private: - Tag tag_; - float unitless_value_; - }; - - class ViewportPercentageLength - { - friend class NoCalcLength; + inline float toPx() const + { + switch (tag_) + { + case kPx: + return unitless_value_; + case kIn: + return unitless_value_ * PX_PER_IN; + case kCm: + return unitless_value_ * PX_PER_CM; + case kMm: + return unitless_value_ * PX_PER_MM; + case kQ: + return unitless_value_ * PX_PER_Q; + case kPt: + return unitless_value_ * PX_PER_PT; + case kPc: + return unitless_value_ * PX_PER_PC; + } + assert(false && "Invalid tag."); + } - private: - enum Tag - { - kVw, - kVh, - kVmin, - kVmax, - kVb, - kVi, + private: + Tag tag_; + float unitless_value_; }; - static constexpr const char *UNIT_VW = "vw"; - static constexpr const char *UNIT_VH = "vh"; - static constexpr const char *UNIT_VMIN = "vmin"; - static constexpr const char *UNIT_VMAX = "vmax"; - static constexpr const char *UNIT_VB = "vb"; - static constexpr const char *UNIT_VI = "vi"; - - public: - static ViewportPercentageLength Vw(float value) - { - return ViewportPercentageLength(kVw, value); - } - static ViewportPercentageLength Vh(float value) - { - return ViewportPercentageLength(kVh, value); - } - static ViewportPercentageLength Vmin(float value) - { - return ViewportPercentageLength(kVmin, value); - } - static ViewportPercentageLength Vmax(float value) - { - return ViewportPercentageLength(kVmax, value); - } - static ViewportPercentageLength Vb(float value) - { - return ViewportPercentageLength(kVb, value); - } - static ViewportPercentageLength Vi(float value) - { - return ViewportPercentageLength(kVi, value); - } - - private: - ViewportPercentageLength(Tag tag, float value) - : tag_(tag) - , unitless_value_(value) - { - } - - public: - inline float unitlessValue() const + class FontRelativeLength { - return unitless_value_; - } - const char *unit() const - { - switch (tag_) - { - case kVw: - return UNIT_VW; - case kVh: - return UNIT_VH; - case kVmin: - return UNIT_VMIN; - case kVmax: - return UNIT_VMAX; - case kVb: - return UNIT_VB; - case kVi: - return UNIT_VI; - } - assert(false && "Invalid tag."); - } - float toComputedValue(computed::Context &context) const - { - glm::uvec4 base_viewport = context.baseViewport(); - switch (tag_) - { - case kVw: - return unitless_value_ * base_viewport.x / 100.0f; - case kVh: - return unitless_value_ * base_viewport.y / 100.0f; - case kVmin: - return unitless_value_ * std::min(base_viewport.x, base_viewport.y) / 100.0f; - case kVmax: - return unitless_value_ * std::max(base_viewport.x, base_viewport.y) / 100.0f; - default: - return 0.0f; - } - } - - private: - Tag tag_; - float unitless_value_; - }; - - class ContainerRelativeLength - { - friend class NoCalcLength; - - private: - enum Tag - { - kCqw, - kCqh, - kCqi, - kCqb, - kCqmin, - kCqmax, - }; + friend class NoCalcLength; - static constexpr const char *UNIT_CQW = "cqw"; - static constexpr const char *UNIT_CQH = "cqh"; - static constexpr const char *UNIT_CQI = "cqi"; - static constexpr const char *UNIT_CQB = "cqb"; - static constexpr const char *UNIT_CQMIN = "cqmin"; - static constexpr const char *UNIT_CQMAX = "cqmax"; + private: + enum Tag + { + kEm, + kEx, + kCh, + kCap, + kIc, + kRem, + kLh, + kRlh, + }; + + static constexpr const char *UNIT_EM = "em"; + static constexpr const char *UNIT_EX = "ex"; + static constexpr const char *UNIT_CH = "ch"; + static constexpr const char *UNIT_CAP = "cap"; + static constexpr const char *UNIT_IC = "ic"; + static constexpr const char *UNIT_REM = "rem"; + static constexpr const char *UNIT_LH = "lh"; + static constexpr const char *UNIT_RLH = "rlh"; + + public: + static FontRelativeLength Em(float value) + { + return FontRelativeLength(kEm, value); + } + static FontRelativeLength Ex(float value) + { + return FontRelativeLength(kEx, value); + } + static FontRelativeLength Ch(float value) + { + return FontRelativeLength(kCh, value); + } + static FontRelativeLength Cap(float value) + { + return FontRelativeLength(kCap, value); + } + static FontRelativeLength Ic(float value) + { + return FontRelativeLength(kIc, value); + } + static FontRelativeLength Rem(float value) + { + return FontRelativeLength(kRem, value); + } + static FontRelativeLength Lh(float value) + { + return FontRelativeLength(kLh, value); + } + static FontRelativeLength Rlh(float value) + { + return FontRelativeLength(kRlh, value); + } - public: - static ContainerRelativeLength Cqw(float value) - { - return ContainerRelativeLength(kCqw, value); - } - static ContainerRelativeLength Cqh(float value) - { - return ContainerRelativeLength(kCqh, value); - } - static ContainerRelativeLength Cqi(float value) - { - return ContainerRelativeLength(kCqi, value); - } - static ContainerRelativeLength Cqb(float value) - { - return ContainerRelativeLength(kCqb, value); - } - static ContainerRelativeLength Cqmin(float value) - { - return ContainerRelativeLength(kCqmin, value); - } - static ContainerRelativeLength Cqmax(float value) - { - return ContainerRelativeLength(kCqmax, value); - } + private: + FontRelativeLength(Tag tag, float value) + : tag_(tag) + , unitless_value_(value) + { + } - private: - ContainerRelativeLength(Tag tag, float value) - : tag_(tag) - , unitless_value_(value) - { - } + public: + inline float unitlessValue() const + { + return unitless_value_; + } + const char *unit() const + { + switch (tag_) + { + case kEm: + return UNIT_EM; + case kEx: + return UNIT_EX; + case kCh: + return UNIT_CH; + case kCap: + return UNIT_CAP; + case kIc: + return UNIT_IC; + case kRem: + return UNIT_REM; + case kLh: + return UNIT_LH; + case kRlh: + return UNIT_RLH; + } + assert(false && "Invalid tag."); + } - public: - inline float unitlessValue() const - { - return unitless_value_; - } - const char *unit() const - { - switch (tag_) - { - case kCqw: - return UNIT_CQW; - case kCqh: - return UNIT_CQH; - case kCqi: - return UNIT_CQI; - case kCqb: - return UNIT_CQB; - case kCqmin: - return UNIT_CQMIN; - case kCqmax: - return UNIT_CQMAX; - } - assert(false && "Invalid tag."); - } - - private: - Tag tag_; - float unitless_value_; - }; - - class NoCalcLength : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; + float toComputedValue(computed::Context &context) const + { + switch (tag_) + { + case kEm: + case kCap: + return unitless_value_ * context.baseFontSize(); + case kRem: + return unitless_value_ * context.rootFontSize(); + case kEx: + // Assuming 'ex' is half of the font size. + return unitless_value_ * context.baseFontSize() * 0.5f; + case kLh: + return unitless_value_ * context.baseLineHeight(); + case kRlh: + return unitless_value_ * context.rootLineHeight(); + default: + // Returns the root font-size if not supported + return context.rootFontSize(); + } + } - private: - enum Tag - { - kAbsolute, - kFontRelative, - kViewportPercentage, - kContainerRelative, + private: + Tag tag_; + float unitless_value_; }; - using LengthVariant = std::variant; - - public: - static NoCalcLength FromPx(float value) - { - return NoCalcLength(AbsoluteLength::Px(value)); - } - static bool MaybeDimension(const std::string &input) - { - if (input.empty()) - return false; - - // Check if the first character is a digit or a dot. - if (!isdigit(input[0]) && input[0] != '.') - return false; - - return input.ends_with(AbsoluteLength::UNIT_PX) || - input.ends_with(AbsoluteLength::UNIT_IN) || - input.ends_with(AbsoluteLength::UNIT_CM) || - input.ends_with(AbsoluteLength::UNIT_MM) || - input.ends_with(AbsoluteLength::UNIT_Q) || - input.ends_with(AbsoluteLength::UNIT_PT) || - input.ends_with(AbsoluteLength::UNIT_PC) || - input.ends_with(FontRelativeLength::UNIT_EM) || - input.ends_with(FontRelativeLength::UNIT_EX) || - input.ends_with(FontRelativeLength::UNIT_CH) || - input.ends_with(FontRelativeLength::UNIT_CAP) || - input.ends_with(FontRelativeLength::UNIT_IC) || - input.ends_with(FontRelativeLength::UNIT_REM) || - input.ends_with(FontRelativeLength::UNIT_LH) || - input.ends_with(FontRelativeLength::UNIT_RLH) || - input.ends_with(ViewportPercentageLength::UNIT_VW) || - input.ends_with(ViewportPercentageLength::UNIT_VH) || - input.ends_with(ViewportPercentageLength::UNIT_VMIN) || - input.ends_with(ViewportPercentageLength::UNIT_VMAX) || - input.ends_with(ViewportPercentageLength::UNIT_VB) || - input.ends_with(ViewportPercentageLength::UNIT_VI); - } - - public: - NoCalcLength() - : tag_(kAbsolute) - , length_(AbsoluteLength::Px(0)) - { - } - NoCalcLength(AbsoluteLength absolute_length) - : tag_(kAbsolute) - , length_(absolute_length) + class ViewportPercentageLength { - } - NoCalcLength(FontRelativeLength font_relative_length) - : tag_(kFontRelative) - , length_(font_relative_length) - { - } - NoCalcLength(ViewportPercentageLength viewport_percentage_length) - : tag_(kViewportPercentage) - , length_(viewport_percentage_length) - { - } - NoCalcLength(ContainerRelativeLength container_relative_length) - : tag_(kContainerRelative) - , length_(container_relative_length) - { - } + friend class NoCalcLength; - private: - bool parse(const std::string &input) override - { - // Check if the first character is a digit or a dot. - if (!isdigit(input[0]) && input[0] != '.') - return false; + private: + enum Tag + { + kVw, + kVh, + kVmin, + kVmax, + kVb, + kVi, + }; + + static constexpr const char *UNIT_VW = "vw"; + static constexpr const char *UNIT_VH = "vh"; + static constexpr const char *UNIT_VMIN = "vmin"; + static constexpr const char *UNIT_VMAX = "vmax"; + static constexpr const char *UNIT_VB = "vb"; + static constexpr const char *UNIT_VI = "vi"; + + public: + static ViewportPercentageLength Vw(float value) + { + return ViewportPercentageLength(kVw, value); + } + static ViewportPercentageLength Vh(float value) + { + return ViewportPercentageLength(kVh, value); + } + static ViewportPercentageLength Vmin(float value) + { + return ViewportPercentageLength(kVmin, value); + } + static ViewportPercentageLength Vmax(float value) + { + return ViewportPercentageLength(kVmax, value); + } + static ViewportPercentageLength Vb(float value) + { + return ViewportPercentageLength(kVb, value); + } + static ViewportPercentageLength Vi(float value) + { + return ViewportPercentageLength(kVi, value); + } - size_t num_end = 0; - bool has_decimal = false; + private: + ViewportPercentageLength(Tag tag, float value) + : tag_(tag) + , unitless_value_(value) + { + } - while (num_end < input.size()) + public: + inline float unitlessValue() const + { + return unitless_value_; + } + const char *unit() const { - char c = input[num_end]; - if (isdigit(static_cast(c))) + switch (tag_) { - num_end++; + case kVw: + return UNIT_VW; + case kVh: + return UNIT_VH; + case kVmin: + return UNIT_VMIN; + case kVmax: + return UNIT_VMAX; + case kVb: + return UNIT_VB; + case kVi: + return UNIT_VI; } - else if (c == '.' && !has_decimal) + assert(false && "Invalid tag."); + } + float toComputedValue(computed::Context &context) const + { + glm::uvec4 base_viewport = context.baseViewport(); + switch (tag_) { - has_decimal = true; - num_end++; + case kVw: + return unitless_value_ * base_viewport.x / 100.0f; + case kVh: + return unitless_value_ * base_viewport.y / 100.0f; + case kVmin: + return unitless_value_ * std::min(base_viewport.x, base_viewport.y) / 100.0f; + case kVmax: + return unitless_value_ * std::max(base_viewport.x, base_viewport.y) / 100.0f; + default: + return 0.0f; } - else - break; } - // Failure case: no digits or only a dot. - if (num_end == 0 || (num_end == 1 && input[0] == '.')) - return false; - - // Parse the number. - double num; - std::istringstream iss(input.substr(0, num_end)); - iss >> num; - - std::string unit = input.substr(num_end); - updateFrom(num, unit.c_str()); - return true; - } + private: + Tag tag_; + float unitless_value_; + }; - void updateFrom(float value, const char *unit) + class ContainerRelativeLength { - assert(unit != nullptr); + friend class NoCalcLength; - // Absolute lengths - if (strcmp(unit, AbsoluteLength::UNIT_PX) == 0) + private: + enum Tag { - tag_ = kAbsolute; - length_ = AbsoluteLength::Px(value); - return; - } - if (strcmp(unit, AbsoluteLength::UNIT_IN) == 0) + kCqw, + kCqh, + kCqi, + kCqb, + kCqmin, + kCqmax, + }; + + static constexpr const char *UNIT_CQW = "cqw"; + static constexpr const char *UNIT_CQH = "cqh"; + static constexpr const char *UNIT_CQI = "cqi"; + static constexpr const char *UNIT_CQB = "cqb"; + static constexpr const char *UNIT_CQMIN = "cqmin"; + static constexpr const char *UNIT_CQMAX = "cqmax"; + + public: + static ContainerRelativeLength Cqw(float value) { - tag_ = kAbsolute; - length_ = AbsoluteLength::In(value); - return; + return ContainerRelativeLength(kCqw, value); } - if (strcmp(unit, AbsoluteLength::UNIT_CM) == 0) + static ContainerRelativeLength Cqh(float value) { - tag_ = kAbsolute; - length_ = AbsoluteLength::Cm(value); - return; + return ContainerRelativeLength(kCqh, value); } - if (strcmp(unit, AbsoluteLength::UNIT_MM) == 0) + static ContainerRelativeLength Cqi(float value) { - tag_ = kAbsolute; - length_ = AbsoluteLength::Mm(value); - return; + return ContainerRelativeLength(kCqi, value); } - if (strcmp(unit, AbsoluteLength::UNIT_Q) == 0) + static ContainerRelativeLength Cqb(float value) { - tag_ = kAbsolute; - length_ = AbsoluteLength::Q(value); - return; + return ContainerRelativeLength(kCqb, value); } - if (strcmp(unit, AbsoluteLength::UNIT_PT) == 0) + static ContainerRelativeLength Cqmin(float value) { - tag_ = kAbsolute; - length_ = AbsoluteLength::Pt(value); - return; + return ContainerRelativeLength(kCqmin, value); } - if (strcmp(unit, AbsoluteLength::UNIT_PC) == 0) + static ContainerRelativeLength Cqmax(float value) { - tag_ = kAbsolute; - length_ = AbsoluteLength::Pc(value); - return; + return ContainerRelativeLength(kCqmax, value); } - // Font-relative lengths - if (strcmp(unit, FontRelativeLength::UNIT_EM) == 0) + private: + ContainerRelativeLength(Tag tag, float value) + : tag_(tag) + , unitless_value_(value) { - tag_ = kFontRelative; - length_ = FontRelativeLength::Em(value); - return; } - if (strcmp(unit, FontRelativeLength::UNIT_EX) == 0) + + public: + inline float unitlessValue() const { - tag_ = kFontRelative; - length_ = FontRelativeLength::Ex(value); - return; + return unitless_value_; } - if (strcmp(unit, FontRelativeLength::UNIT_CH) == 0) + const char *unit() const { - tag_ = kFontRelative; - length_ = FontRelativeLength::Ch(value); - return; + switch (tag_) + { + case kCqw: + return UNIT_CQW; + case kCqh: + return UNIT_CQH; + case kCqi: + return UNIT_CQI; + case kCqb: + return UNIT_CQB; + case kCqmin: + return UNIT_CQMIN; + case kCqmax: + return UNIT_CQMAX; + } + assert(false && "Invalid tag."); } - if (strcmp(unit, FontRelativeLength::UNIT_CAP) == 0) + + private: + Tag tag_; + float unitless_value_; + }; + + class NoCalcLength : public Parse, + public ToCss, + public ToComputedValue + { + friend class Parse; + + private: + enum Tag { - tag_ = kFontRelative; - length_ = FontRelativeLength::Cap(value); - return; + kAbsolute, + kFontRelative, + kViewportPercentage, + kContainerRelative, + }; + + using LengthVariant = std::variant; + + public: + static NoCalcLength FromPx(float value) + { + return NoCalcLength(AbsoluteLength::Px(value)); } - if (strcmp(unit, FontRelativeLength::UNIT_IC) == 0) + static bool MaybeDimension(const std::string &input) { - tag_ = kFontRelative; - length_ = FontRelativeLength::Ic(value); - return; + if (input.empty()) + return false; + + // Check if the first character is a digit or a dot. + if (!isdigit(input[0]) && input[0] != '.') + return false; + + return input.ends_with(AbsoluteLength::UNIT_PX) || + input.ends_with(AbsoluteLength::UNIT_IN) || + input.ends_with(AbsoluteLength::UNIT_CM) || + input.ends_with(AbsoluteLength::UNIT_MM) || + input.ends_with(AbsoluteLength::UNIT_Q) || + input.ends_with(AbsoluteLength::UNIT_PT) || + input.ends_with(AbsoluteLength::UNIT_PC) || + input.ends_with(FontRelativeLength::UNIT_EM) || + input.ends_with(FontRelativeLength::UNIT_EX) || + input.ends_with(FontRelativeLength::UNIT_CH) || + input.ends_with(FontRelativeLength::UNIT_CAP) || + input.ends_with(FontRelativeLength::UNIT_IC) || + input.ends_with(FontRelativeLength::UNIT_REM) || + input.ends_with(FontRelativeLength::UNIT_LH) || + input.ends_with(FontRelativeLength::UNIT_RLH) || + input.ends_with(ViewportPercentageLength::UNIT_VW) || + input.ends_with(ViewportPercentageLength::UNIT_VH) || + input.ends_with(ViewportPercentageLength::UNIT_VMIN) || + input.ends_with(ViewportPercentageLength::UNIT_VMAX) || + input.ends_with(ViewportPercentageLength::UNIT_VB) || + input.ends_with(ViewportPercentageLength::UNIT_VI); } - if (strcmp(unit, FontRelativeLength::UNIT_REM) == 0) + + public: + NoCalcLength() + : tag_(kAbsolute) + , length_(AbsoluteLength::Px(0)) { - tag_ = kFontRelative; - length_ = FontRelativeLength::Rem(value); - return; } - if (strcmp(unit, FontRelativeLength::UNIT_LH) == 0) + NoCalcLength(AbsoluteLength absolute_length) + : tag_(kAbsolute) + , length_(absolute_length) { - tag_ = kFontRelative; - length_ = FontRelativeLength::Lh(value); - return; } - if (strcmp(unit, FontRelativeLength::UNIT_RLH) == 0) + NoCalcLength(FontRelativeLength font_relative_length) + : tag_(kFontRelative) + , length_(font_relative_length) { - tag_ = kFontRelative; - length_ = FontRelativeLength::Rlh(value); - return; } - - // Viewport percentage lengths - if (strcmp(unit, ViewportPercentageLength::UNIT_VW) == 0) + NoCalcLength(ViewportPercentageLength viewport_percentage_length) + : tag_(kViewportPercentage) + , length_(viewport_percentage_length) { - tag_ = kViewportPercentage; - length_ = ViewportPercentageLength::Vw(value); - return; } - if (strcmp(unit, ViewportPercentageLength::UNIT_VH) == 0) + NoCalcLength(ContainerRelativeLength container_relative_length) + : tag_(kContainerRelative) + , length_(container_relative_length) { - tag_ = kViewportPercentage; - length_ = ViewportPercentageLength::Vh(value); - return; } - if (strcmp(unit, ViewportPercentageLength::UNIT_VMIN) == 0) + + private: + bool parse(const std::string &input) override { - tag_ = kViewportPercentage; - length_ = ViewportPercentageLength::Vmin(value); - return; + // Check if the first character is a digit or a dot. + if (!isdigit(input[0]) && input[0] != '.') + return false; + + size_t num_end = 0; + bool has_decimal = false; + + while (num_end < input.size()) + { + char c = input[num_end]; + if (isdigit(static_cast(c))) + { + num_end++; + } + else if (c == '.' && !has_decimal) + { + has_decimal = true; + num_end++; + } + else + break; + } + + // Failure case: no digits or only a dot. + if (num_end == 0 || (num_end == 1 && input[0] == '.')) + return false; + + // Parse the number. + double num; + std::istringstream iss(input.substr(0, num_end)); + iss >> num; + + std::string unit = input.substr(num_end); + updateFrom(num, unit.c_str()); + return true; } - if (strcmp(unit, ViewportPercentageLength::UNIT_VMAX) == 0) + + void updateFrom(float value, const char *unit) { - tag_ = kViewportPercentage; - length_ = ViewportPercentageLength::Vmax(value); - return; + assert(unit != nullptr); + + // Absolute lengths + if (strcmp(unit, AbsoluteLength::UNIT_PX) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::Px(value); + return; + } + if (strcmp(unit, AbsoluteLength::UNIT_IN) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::In(value); + return; + } + if (strcmp(unit, AbsoluteLength::UNIT_CM) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::Cm(value); + return; + } + if (strcmp(unit, AbsoluteLength::UNIT_MM) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::Mm(value); + return; + } + if (strcmp(unit, AbsoluteLength::UNIT_Q) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::Q(value); + return; + } + if (strcmp(unit, AbsoluteLength::UNIT_PT) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::Pt(value); + return; + } + if (strcmp(unit, AbsoluteLength::UNIT_PC) == 0) + { + tag_ = kAbsolute; + length_ = AbsoluteLength::Pc(value); + return; + } + + // Font-relative lengths + if (strcmp(unit, FontRelativeLength::UNIT_EM) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Em(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_EX) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Ex(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_CH) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Ch(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_CAP) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Cap(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_IC) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Ic(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_REM) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Rem(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_LH) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Lh(value); + return; + } + if (strcmp(unit, FontRelativeLength::UNIT_RLH) == 0) + { + tag_ = kFontRelative; + length_ = FontRelativeLength::Rlh(value); + return; + } + + // Viewport percentage lengths + if (strcmp(unit, ViewportPercentageLength::UNIT_VW) == 0) + { + tag_ = kViewportPercentage; + length_ = ViewportPercentageLength::Vw(value); + return; + } + if (strcmp(unit, ViewportPercentageLength::UNIT_VH) == 0) + { + tag_ = kViewportPercentage; + length_ = ViewportPercentageLength::Vh(value); + return; + } + if (strcmp(unit, ViewportPercentageLength::UNIT_VMIN) == 0) + { + tag_ = kViewportPercentage; + length_ = ViewportPercentageLength::Vmin(value); + return; + } + if (strcmp(unit, ViewportPercentageLength::UNIT_VMAX) == 0) + { + tag_ = kViewportPercentage; + length_ = ViewportPercentageLength::Vmax(value); + return; + } + if (strcmp(unit, ViewportPercentageLength::UNIT_VB) == 0) + { + tag_ = kViewportPercentage; + length_ = ViewportPercentageLength::Vb(value); + return; + } + if (strcmp(unit, ViewportPercentageLength::UNIT_VI) == 0) + { + tag_ = kViewportPercentage; + length_ = ViewportPercentageLength::Vi(value); + return; + } + + // Container relative lengths + if (strcmp(unit, ContainerRelativeLength::UNIT_CQW) == 0) + { + tag_ = kContainerRelative; + length_ = ContainerRelativeLength::Cqw(value); + return; + } + if (strcmp(unit, ContainerRelativeLength::UNIT_CQH) == 0) + { + tag_ = kContainerRelative; + length_ = ContainerRelativeLength::Cqh(value); + return; + } + if (strcmp(unit, ContainerRelativeLength::UNIT_CQI) == 0) + { + tag_ = kContainerRelative; + length_ = ContainerRelativeLength::Cqi(value); + return; + } + if (strcmp(unit, ContainerRelativeLength::UNIT_CQB) == 0) + { + tag_ = kContainerRelative; + length_ = ContainerRelativeLength::Cqb(value); + return; + } + if (strcmp(unit, ContainerRelativeLength::UNIT_CQMIN) == 0) + { + tag_ = kContainerRelative; + length_ = ContainerRelativeLength::Cqmin(value); + return; + } + if (strcmp(unit, ContainerRelativeLength::UNIT_CQMAX) == 0) + { + tag_ = kContainerRelative; + length_ = ContainerRelativeLength::Cqmax(value); + return; + } + + // Unreachable + cerr << "Failed to parse length: " << value << unit << endl; + assert(false && "Invalid unit."); } - if (strcmp(unit, ViewportPercentageLength::UNIT_VB) == 0) + + public: + std::string toCss() const override { - tag_ = kViewportPercentage; - length_ = ViewportPercentageLength::Vb(value); - return; + return std::to_string(unitlessValue()) + std::string(unit()); } - if (strcmp(unit, ViewportPercentageLength::UNIT_VI) == 0) + float toComputedValue(computed::Context &context) const override { - tag_ = kViewportPercentage; - length_ = ViewportPercentageLength::Vi(value); - return; + if (tag_ == kAbsolute) + { + const auto &absolute_length = std::get(length_); + return absolute_length.toPx(); + } + else if (tag_ == kFontRelative) + { + const auto &font_relative_length = std::get(length_); + return font_relative_length.toComputedValue(context); + } + else if (tag_ == kViewportPercentage) + { + const auto &viewport_percentage_length = std::get(length_); + return viewport_percentage_length.toComputedValue(context); + } + else if (tag_ == kContainerRelative) + { + assert(false && "Container relative length is not implemented yet."); + } + + // Unreachable + assert(false && "Invalid tag."); } - // Container relative lengths - if (strcmp(unit, ContainerRelativeLength::UNIT_CQW) == 0) + float unitlessValue() const { - tag_ = kContainerRelative; - length_ = ContainerRelativeLength::Cqw(value); - return; + switch (tag_) + { + case kAbsolute: + return std::get(length_).unitlessValue(); + case kFontRelative: + return std::get(length_).unitlessValue(); + case kViewportPercentage: + return std::get(length_).unitlessValue(); + case kContainerRelative: + return std::get(length_).unitlessValue(); + } + assert(false && "Invalid tag."); } - if (strcmp(unit, ContainerRelativeLength::UNIT_CQH) == 0) + // Returns the unit as a string. + const char *unit() const { - tag_ = kContainerRelative; - length_ = ContainerRelativeLength::Cqh(value); - return; + switch (tag_) + { + case kAbsolute: + return std::get(length_).unit(); + case kFontRelative: + return std::get(length_).unit(); + case kViewportPercentage: + return std::get(length_).unit(); + case kContainerRelative: + return std::get(length_).unit(); + } + assert(false && "Invalid tag."); } - if (strcmp(unit, ContainerRelativeLength::UNIT_CQI) == 0) + + inline bool isZero() const { - tag_ = kContainerRelative; - length_ = ContainerRelativeLength::Cqi(value); - return; + return unitlessValue() == 0.0f; } - if (strcmp(unit, ContainerRelativeLength::UNIT_CQB) == 0) + inline bool isInfinite() const { - tag_ = kContainerRelative; - length_ = ContainerRelativeLength::Cqb(value); - return; + return unitlessValue() == std::numeric_limits::infinity(); } - if (strcmp(unit, ContainerRelativeLength::UNIT_CQMIN) == 0) + inline bool isNan() const { - tag_ = kContainerRelative; - length_ = ContainerRelativeLength::Cqmin(value); - return; + return unitlessValue() != unitlessValue(); } - if (strcmp(unit, ContainerRelativeLength::UNIT_CQMAX) == 0) + inline bool isNegative() const { - tag_ = kContainerRelative; - length_ = ContainerRelativeLength::Cqmax(value); - return; + return unitlessValue() < 0.0f; } - // Unreachable - cerr << "Failed to parse length: " << value << unit << endl; - assert(false && "Invalid unit."); - } + private: + Tag tag_; + LengthVariant length_; + }; - public: - std::string toCss() const override - { - return std::to_string(unitlessValue()) + std::string(unit()); - } - float toComputedValue(computed::Context &context) const override + class LengthPercentage : public Parse, + public ToCss, + public ToComputedValue { - if (tag_ == kAbsolute) + friend class Parse; + + private: + enum Tag : uint8_t { - const auto &absolute_length = std::get(length_); - return absolute_length.toPx(); - } - else if (tag_ == kFontRelative) + kLength, + kPercentage, + kCalc, + }; + using ValueVariant = std::variant; + + public: + // Creates a LengthPercentage from a inner length percentage value. + static LengthPercentage From(crates::css2::values::specified::LengthPercentage inner_length_percentage) { - const auto &font_relative_length = std::get(length_); - return font_relative_length.toComputedValue(context); + if (inner_length_percentage.isNoCalcLength()) + { + const auto &no_calc_length = inner_length_percentage.getNoCalcLength(); + return Parse::ParseSingleValue(no_calc_length.toCss()); + } + else if (inner_length_percentage.isPercentage()) + { + const auto &percentage = inner_length_percentage.getPercentage(); + return LengthPercentage(computed::Percentage(percentage.value)); + } + else + { + // TODO(yorkie): support calc length. + return LengthPercentage(0.0f); + } } - else if (tag_ == kViewportPercentage) + // Returns if the input string is a valid length or percentage. + static bool IsLengthOrPercentage(const std::string &input) { - const auto &viewport_percentage_length = std::get(length_); - return viewport_percentage_length.toComputedValue(context); + return input.ends_with("%") || NoCalcLength::MaybeDimension(input); } - else if (tag_ == kContainerRelative) + + public: + LengthPercentage(float value = 0.0f) + : tag_(kLength) + , value_(NoCalcLength::FromPx(value)) { - assert(false && "Container relative length is not implemented yet."); } - // Unreachable - assert(false && "Invalid tag."); - } - - float unitlessValue() const - { - switch (tag_) - { - case kAbsolute: - return std::get(length_).unitlessValue(); - case kFontRelative: - return std::get(length_).unitlessValue(); - case kViewportPercentage: - return std::get(length_).unitlessValue(); - case kContainerRelative: - return std::get(length_).unitlessValue(); - } - assert(false && "Invalid tag."); - } - // Returns the unit as a string. - const char *unit() const - { - switch (tag_) + LengthPercentage(NoCalcLength length) + : tag_(kLength) + , value_(length) + { + } + LengthPercentage(computed::Percentage percentage) + : tag_(kPercentage) + , value_(percentage) { - case kAbsolute: - return std::get(length_).unit(); - case kFontRelative: - return std::get(length_).unit(); - case kViewportPercentage: - return std::get(length_).unit(); - case kContainerRelative: - return std::get(length_).unit(); } - assert(false && "Invalid tag."); - } - - inline bool isZero() const - { - return unitlessValue() == 0.0f; - } - inline bool isInfinite() const - { - return unitlessValue() == std::numeric_limits::infinity(); - } - inline bool isNan() const - { - return unitlessValue() != unitlessValue(); - } - inline bool isNegative() const - { - return unitlessValue() < 0.0f; - } - - private: - Tag tag_; - LengthVariant length_; - }; - - class LengthPercentage : public Parse, - public ToCss, - public ToComputedValue - { - friend class Parse; - - private: - enum Tag : uint8_t - { - kLength, - kPercentage, - kCalc, - }; - using ValueVariant = std::variant; - public: - // Creates a LengthPercentage from a inner length percentage value. - static LengthPercentage From(crates::css2::values::specified::LengthPercentage inner_length_percentage) - { - if (inner_length_percentage.isNoCalcLength()) + private: + bool parse(const std::string &input) override { - const auto &no_calc_length = inner_length_percentage.getNoCalcLength(); - return Parse::ParseSingleValue(no_calc_length.toCss()); + if (input.empty()) + return true; + + if (input.ends_with("%")) + { + float percent = std::stof(input.substr(0, input.length() - 1)); + tag_ = kPercentage; + value_ = computed::Percentage(percent / 100.0f); + return true; + } + else if (input.starts_with("calc(") && input.ends_with(")")) + { + tag_ = kCalc; + value_ = CalcLengthPercentage(); + return true; + } + else + { + tag_ = kLength; + value_ = Parse::ParseSingleValue(input); + return true; + } } - else if (inner_length_percentage.isPercentage()) + + public: + std::string toCss() const override { - const auto &percentage = inner_length_percentage.getPercentage(); - return LengthPercentage(computed::Percentage(percentage.value)); + switch (tag_) + { + case kLength: + return std::get(value_).toCss(); + case kPercentage: + return std::get(value_).toCss(); + case kCalc: + // TODO(yorkie): support calc length. + return "calc()"; + } } - else + computed::LengthPercentage toComputedValue(computed::Context &context) const override { - // TODO(yorkie): support calc length. - return LengthPercentage(0.0f); + if (tag_ == kLength) + { + auto length = std::get(value_).toComputedValue(context); + return computed::LengthPercentage::Length(length); + } + else if (tag_ == kPercentage) + { + auto percent = std::get(value_).value(); + return computed::LengthPercentage::Percentage(percent); + } + else + { + // TODO(yorkie): support calc length. + return computed::LengthPercentage::Length(0.0f); + } } - } - // Returns if the input string is a valid length or percentage. - static bool IsLengthOrPercentage(const std::string &input) - { - return input.ends_with("%") || NoCalcLength::MaybeDimension(input); - } - public: - LengthPercentage(float value = 0.0f) - : tag_(kLength) - , value_(NoCalcLength::FromPx(value)) - { - } + private: + Tag tag_; + ValueVariant value_; + }; - LengthPercentage(NoCalcLength length) - : tag_(kLength) - , value_(length) - { - } - LengthPercentage(computed::Percentage percentage) - : tag_(kPercentage) - , value_(percentage) - { - } + using NonNegativeLengthPercentage = generics::NonNegative; - private: - bool parse(const std::string &input) override + class Size final : public generics::GenericSize, + public Parse, + public ToComputedValue { - if (input.empty()) - return true; + friend class Parse; + using generics::GenericSize::GenericSize; - if (input.ends_with("%")) + public: + Size() + : generics::GenericSize() { - float percent = std::stof(input.substr(0, input.length() - 1)); - tag_ = kPercentage; - value_ = computed::Percentage(percent / 100.0f); - return true; } - else if (input.starts_with("calc(") && input.ends_with(")")) + + private: + bool parse(const std::string &input) override { - tag_ = kCalc; - value_ = CalcLengthPercentage(); + if (input == "auto") + setAuto(); + if (LengthPercentage::IsLengthOrPercentage(input)) + setLengthPercentage(Parse::ParseSingleValue(input)); return true; } - else + + public: + computed::Size toComputedValue(computed::Context &context) const override { - tag_ = kLength; - value_ = Parse::ParseSingleValue(input); - return true; + if (isAuto()) + return computed::Size::Auto(); + else if (isLengthPercentage()) + return computed::Size::LengthPercentage(lengthPercent().toComputedValue(context)); + assert(false && "Invalid tag."); } - } + }; - public: - std::string toCss() const override + class MarginSize : public generics::GenericMargin, + public Parse, + public ToComputedValue { - switch (tag_) - { - case kLength: - return std::get(value_).toCss(); - case kPercentage: - return std::get(value_).toCss(); - case kCalc: - // TODO(yorkie): support calc length. - return "calc()"; - } - } - computed::LengthPercentage toComputedValue(computed::Context &context) const override - { - if (tag_ == kLength) - { - auto length = std::get(value_).toComputedValue(context); - return computed::LengthPercentage::Length(length); - } - else if (tag_ == kPercentage) + friend class Parse; + using generics::GenericMargin::GenericMargin; + + private: + bool parse(const std::string &input) override { - auto percent = std::get(value_).value(); - return computed::LengthPercentage::Percentage(percent); + if (input == "auto") + setAuto(); + else if (LengthPercentage::IsLengthOrPercentage(input)) + setLengthPercentage(Parse::ParseSingleValue(input)); + return true; } - else + + public: + computed::MarginSize toComputedValue(computed::Context &context) const override { - // TODO(yorkie): support calc length. - return computed::LengthPercentage::Length(0.0f); + if (isAuto()) + return computed::MarginSize::Auto(); + else if (isLengthPercentage()) + return computed::MarginSize::LengthPercentage(lengthPercent().toComputedValue(context)); + assert(false && "Invalid tag."); } - } - - private: - Tag tag_; - ValueVariant value_; - }; - - using NonNegativeLengthPercentage = generics::NonNegative; - - class Size final : public generics::GenericSize, - public Parse, - public ToComputedValue - { - friend class Parse; - using generics::GenericSize::GenericSize; - - public: - Size() - : generics::GenericSize() - { - } - - private: - bool parse(const std::string &input) override - { - if (input == "auto") - setAuto(); - if (LengthPercentage::IsLengthOrPercentage(input)) - setLengthPercentage(Parse::ParseSingleValue(input)); - return true; - } - - public: - computed::Size toComputedValue(computed::Context &context) const override - { - if (isAuto()) - return computed::Size::Auto(); - else if (isLengthPercentage()) - return computed::Size::LengthPercentage(lengthPercent().toComputedValue(context)); - assert(false && "Invalid tag."); - } - }; - - class MarginSize : public generics::GenericMargin, - public Parse, - public ToComputedValue - { - friend class Parse; - using generics::GenericMargin::GenericMargin; - - private: - bool parse(const std::string &input) override - { - if (input == "auto") - setAuto(); - else if (LengthPercentage::IsLengthOrPercentage(input)) - setLengthPercentage(Parse::ParseSingleValue(input)); - return true; - } - - public: - computed::MarginSize toComputedValue(computed::Context &context) const override - { - if (isAuto()) - return computed::MarginSize::Auto(); - else if (isLengthPercentage()) - return computed::MarginSize::LengthPercentage(lengthPercent().toComputedValue(context)); - assert(false && "Invalid tag."); - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/position.hpp b/src/client/cssom/values/specified/position.hpp index 3949ab947..40b598932 100644 --- a/src/client/cssom/values/specified/position.hpp +++ b/src/client/cssom/values/specified/position.hpp @@ -6,80 +6,83 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class PositionType : public generics::PositionType, - public Parse, - public ToCss, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - - private: - bool parse(const std::string &input) override + class PositionType : public generics::PositionType, + public Parse, + public ToCss, + public ToComputedValue { - if (input == "static") - tag_ = kStatic; - else if (input == "relative") - tag_ = kRelative; - else if (input == "absolute") - tag_ = kAbsolute; - else if (input == "fixed") - tag_ = kFixed; - else if (input == "sticky") - tag_ = kSticky; - return true; - } + friend class Parse; - public: - std::string toCss() const override - { - switch (tag_) + private: + bool parse(const std::string &input) override { - case kStatic: - return "static"; - case kRelative: - return "relative"; - case kAbsolute: - return "absolute"; - case kFixed: - return "fixed"; - case kSticky: - return "sticky"; + if (input == "static") + tag_ = kStatic; + else if (input == "relative") + tag_ = kRelative; + else if (input == "absolute") + tag_ = kAbsolute; + else if (input == "fixed") + tag_ = kFixed; + else if (input == "sticky") + tag_ = kSticky; + return true; } - return ""; - } - generics::PositionType toComputedValue(computed::Context &) const override - { - return *this; - } - }; - class InsetSize : public generics::GenericMargin, - public Parse, - public ToComputedValue - { - friend class Parse; - using generics::GenericMargin::GenericMargin; + public: + std::string toCss() const override + { + switch (tag_) + { + case kStatic: + return "static"; + case kRelative: + return "relative"; + case kAbsolute: + return "absolute"; + case kFixed: + return "fixed"; + case kSticky: + return "sticky"; + } + return ""; + } + generics::PositionType toComputedValue(computed::Context &) const override + { + return *this; + } + }; - private: - bool parse(const std::string &input) override + class InsetSize : public generics::GenericMargin, + public Parse, + public ToComputedValue { - if (input == "auto") - setAuto(); - else if (LengthPercentage::IsLengthOrPercentage(input)) - setLengthPercentage(Parse::ParseSingleValue(input)); - return true; - } + friend class Parse; + using generics::GenericMargin::GenericMargin; - public: - computed::InsetSize toComputedValue(computed::Context &context) const override - { - if (isAuto()) - return computed::InsetSize::Auto(); - else if (isLengthPercentage()) - return computed::InsetSize::LengthPercentage(lengthPercent().toComputedValue(context)); - assert(false && "Invalid tag."); - } - }; -} + private: + bool parse(const std::string &input) override + { + if (input == "auto") + setAuto(); + else if (LengthPercentage::IsLengthOrPercentage(input)) + setLengthPercentage(Parse::ParseSingleValue(input)); + return true; + } + + public: + computed::InsetSize toComputedValue(computed::Context &context) const override + { + if (isAuto()) + return computed::InsetSize::Auto(); + else if (isLengthPercentage()) + return computed::InsetSize::LengthPercentage(lengthPercent().toComputedValue(context)); + assert(false && "Invalid tag."); + } + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/text.hpp b/src/client/cssom/values/specified/text.hpp index aaa08aba4..1bf10289b 100644 --- a/src/client/cssom/values/specified/text.hpp +++ b/src/client/cssom/values/specified/text.hpp @@ -6,334 +6,337 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class TextAlign : public Parse, - public ToCss + namespace client_cssom::values::specified { - friend class Parse; - - protected: - enum Tag : uint8_t - { - kStart, - kEnd, - kLeft, - kRight, - kCenter, - kJustify, - kMatchParent, - }; - - public: - static TextAlign Start() - { - return TextAlign(Tag::kStart); - } - static TextAlign End() - { - return TextAlign(Tag::kEnd); - } - static TextAlign Left() - { - return TextAlign(Tag::kLeft); - } - static TextAlign Right() - { - return TextAlign(Tag::kRight); - } - static TextAlign Center() + class TextAlign : public Parse, + public ToCss { - return TextAlign(Tag::kCenter); - } - static TextAlign Justify() - { - return TextAlign(Tag::kJustify); - } - static TextAlign MatchParent() - { - return TextAlign(Tag::kMatchParent); - } + friend class Parse; - public: - TextAlign() - : tag_(Tag::kStart) - { - } + protected: + enum Tag : uint8_t + { + kStart, + kEnd, + kLeft, + kRight, + kCenter, + kJustify, + kMatchParent, + }; - protected: - TextAlign(Tag tag) - : tag_(tag) - { - } + public: + static TextAlign Start() + { + return TextAlign(Tag::kStart); + } + static TextAlign End() + { + return TextAlign(Tag::kEnd); + } + static TextAlign Left() + { + return TextAlign(Tag::kLeft); + } + static TextAlign Right() + { + return TextAlign(Tag::kRight); + } + static TextAlign Center() + { + return TextAlign(Tag::kCenter); + } + static TextAlign Justify() + { + return TextAlign(Tag::kJustify); + } + static TextAlign MatchParent() + { + return TextAlign(Tag::kMatchParent); + } - private: - bool parse(const std::string &input) override - { - if (input == "start") - tag_ = Tag::kStart; - else if (input == "end") - tag_ = Tag::kEnd; - else if (input == "left") - tag_ = Tag::kLeft; - else if (input == "right") - tag_ = Tag::kRight; - else if (input == "center") - tag_ = Tag::kCenter; - else if (input == "justify") - tag_ = Tag::kJustify; - else if (input == "match-parent") - tag_ = Tag::kMatchParent; - return true; - } - std::string toCss() const override - { - switch (tag_) - { - case Tag::kStart: - return "start"; - case Tag::kEnd: - return "end"; - case Tag::kLeft: - return "left"; - case Tag::kRight: - return "right"; - case Tag::kCenter: - return "center"; - case Tag::kJustify: - return "justify"; - case Tag::kMatchParent: - return "match-parent"; - } - assert(false && "Invalid tag."); - } + public: + TextAlign() + : tag_(Tag::kStart) + { + } - protected: - Tag tag_ = Tag::kStart; - }; + protected: + TextAlign(Tag tag) + : tag_(tag) + { + } - class Direction : public Parse, - public ToCss - { - friend class Parse; + private: + bool parse(const std::string &input) override + { + if (input == "start") + tag_ = Tag::kStart; + else if (input == "end") + tag_ = Tag::kEnd; + else if (input == "left") + tag_ = Tag::kLeft; + else if (input == "right") + tag_ = Tag::kRight; + else if (input == "center") + tag_ = Tag::kCenter; + else if (input == "justify") + tag_ = Tag::kJustify; + else if (input == "match-parent") + tag_ = Tag::kMatchParent; + return true; + } + std::string toCss() const override + { + switch (tag_) + { + case Tag::kStart: + return "start"; + case Tag::kEnd: + return "end"; + case Tag::kLeft: + return "left"; + case Tag::kRight: + return "right"; + case Tag::kCenter: + return "center"; + case Tag::kJustify: + return "justify"; + case Tag::kMatchParent: + return "match-parent"; + } + assert(false && "Invalid tag."); + } - protected: - enum Tag - { - kLTR, - kRTL, + protected: + Tag tag_ = Tag::kStart; }; - public: - static Direction LTR() + class Direction : public Parse, + public ToCss { - return Direction(true); - } - static Direction RTL() - { - return Direction(false); - } + friend class Parse; - public: - Direction() - : tag_(Tag::kLTR) - { - } + protected: + enum Tag + { + kLTR, + kRTL, + }; - private: - Direction(bool isLTR) - : tag_(isLTR ? Tag::kLTR : Tag::kRTL) - { - } + public: + static Direction LTR() + { + return Direction(true); + } + static Direction RTL() + { + return Direction(false); + } - private: - bool parse(const std::string &input) override - { - tag_ = input == "ltr" ? Tag::kLTR : Tag::kRTL; - return true; - } + public: + Direction() + : tag_(Tag::kLTR) + { + } - public: - std::string toCss() const override - { - return tag_ == Tag::kLTR ? "ltr" : "rtl"; - } + private: + Direction(bool isLTR) + : tag_(isLTR ? Tag::kLTR : Tag::kRTL) + { + } - protected: - Tag tag_ = Tag::kLTR; - }; + private: + bool parse(const std::string &input) override + { + tag_ = input == "ltr" ? Tag::kLTR : Tag::kRTL; + return true; + } - class VerticalAlign : public Parse, - public ToCss - { - friend class Parse; + public: + std::string toCss() const override + { + return tag_ == Tag::kLTR ? "ltr" : "rtl"; + } - public: - enum Tag : uint8_t - { - kBaseline, - kSub, - kSuper, - kTop, - kTextTop, - kMiddle, - kBottom, - kTextBottom, - kLength, // Custom length value - kPercentage, // Percentage value + protected: + Tag tag_ = Tag::kLTR; }; - public: - static VerticalAlign Baseline() + class VerticalAlign : public Parse, + public ToCss { - return VerticalAlign(Tag::kBaseline); - } - static VerticalAlign Sub() - { - return VerticalAlign(Tag::kSub); - } - static VerticalAlign Super() - { - return VerticalAlign(Tag::kSuper); - } - static VerticalAlign Top() - { - return VerticalAlign(Tag::kTop); - } - static VerticalAlign TextTop() - { - return VerticalAlign(Tag::kTextTop); - } - static VerticalAlign Middle() - { - return VerticalAlign(Tag::kMiddle); - } - static VerticalAlign Bottom() - { - return VerticalAlign(Tag::kBottom); - } - static VerticalAlign TextBottom() - { - return VerticalAlign(Tag::kTextBottom); - } - static VerticalAlign Length(float value) - { - return VerticalAlign(Tag::kLength, value); - } - static VerticalAlign Percentage(float value) - { - return VerticalAlign(Tag::kPercentage, value); - } + friend class Parse; - public: - VerticalAlign() - : tag_(Tag::kBaseline) - , value_(0.0f) - { - } + public: + enum Tag : uint8_t + { + kBaseline, + kSub, + kSuper, + kTop, + kTextTop, + kMiddle, + kBottom, + kTextBottom, + kLength, // Custom length value + kPercentage, // Percentage value + }; - protected: - VerticalAlign(Tag tag, float value = 0.0f) - : tag_(tag) - , value_(value) - { - } + public: + static VerticalAlign Baseline() + { + return VerticalAlign(Tag::kBaseline); + } + static VerticalAlign Sub() + { + return VerticalAlign(Tag::kSub); + } + static VerticalAlign Super() + { + return VerticalAlign(Tag::kSuper); + } + static VerticalAlign Top() + { + return VerticalAlign(Tag::kTop); + } + static VerticalAlign TextTop() + { + return VerticalAlign(Tag::kTextTop); + } + static VerticalAlign Middle() + { + return VerticalAlign(Tag::kMiddle); + } + static VerticalAlign Bottom() + { + return VerticalAlign(Tag::kBottom); + } + static VerticalAlign TextBottom() + { + return VerticalAlign(Tag::kTextBottom); + } + static VerticalAlign Length(float value) + { + return VerticalAlign(Tag::kLength, value); + } + static VerticalAlign Percentage(float value) + { + return VerticalAlign(Tag::kPercentage, value); + } - public: - Tag tag() const - { - return tag_; - } - float value() const - { - return value_; - } - std::string toCss() const override - { - switch (tag_) - { - case Tag::kBaseline: - return "baseline"; - case Tag::kSub: - return "sub"; - case Tag::kSuper: - return "super"; - case Tag::kTop: - return "top"; - case Tag::kTextTop: - return "text-top"; - case Tag::kMiddle: - return "middle"; - case Tag::kBottom: - return "bottom"; - case Tag::kTextBottom: - return "text-bottom"; - case Tag::kLength: - return std::to_string(value_) + "px"; - case Tag::kPercentage: - return std::to_string(value_) + "%"; - } - assert(false && "Invalid tag."); - } + public: + VerticalAlign() + : tag_(Tag::kBaseline) + , value_(0.0f) + { + } - private: - bool parse(const std::string &input) override - { - if (input == "baseline") - tag_ = Tag::kBaseline; - else if (input == "sub") - tag_ = Tag::kSub; - else if (input == "super") - tag_ = Tag::kSuper; - else if (input == "top") - tag_ = Tag::kTop; - else if (input == "text-top") - tag_ = Tag::kTextTop; - else if (input == "middle") - tag_ = Tag::kMiddle; - else if (input == "bottom") - tag_ = Tag::kBottom; - else if (input == "text-bottom") - tag_ = Tag::kTextBottom; - else - { - // Try to parse as length or percentage - if (input.back() == '%') + protected: + VerticalAlign(Tag tag, float value = 0.0f) + : tag_(tag) + , value_(value) + { + } + + public: + Tag tag() const + { + return tag_; + } + float value() const + { + return value_; + } + std::string toCss() const override + { + switch (tag_) { - try - { - float percentage = std::stof(input.substr(0, input.size() - 1)); - tag_ = Tag::kPercentage; - value_ = percentage; - } - catch (const std::exception &) - { - return false; - } + case Tag::kBaseline: + return "baseline"; + case Tag::kSub: + return "sub"; + case Tag::kSuper: + return "super"; + case Tag::kTop: + return "top"; + case Tag::kTextTop: + return "text-top"; + case Tag::kMiddle: + return "middle"; + case Tag::kBottom: + return "bottom"; + case Tag::kTextBottom: + return "text-bottom"; + case Tag::kLength: + return std::to_string(value_) + "px"; + case Tag::kPercentage: + return std::to_string(value_) + "%"; } - else if (input.find("px") != std::string::npos) + assert(false && "Invalid tag."); + } + + private: + bool parse(const std::string &input) override + { + if (input == "baseline") + tag_ = Tag::kBaseline; + else if (input == "sub") + tag_ = Tag::kSub; + else if (input == "super") + tag_ = Tag::kSuper; + else if (input == "top") + tag_ = Tag::kTop; + else if (input == "text-top") + tag_ = Tag::kTextTop; + else if (input == "middle") + tag_ = Tag::kMiddle; + else if (input == "bottom") + tag_ = Tag::kBottom; + else if (input == "text-bottom") + tag_ = Tag::kTextBottom; + else { - try + // Try to parse as length or percentage + if (input.back() == '%') + { + try + { + float percentage = std::stof(input.substr(0, input.size() - 1)); + tag_ = Tag::kPercentage; + value_ = percentage; + } + catch (const std::exception &) + { + return false; + } + } + else if (input.find("px") != std::string::npos) { - float length = std::stof(input.substr(0, input.size() - 2)); - tag_ = Tag::kLength; - value_ = length; + try + { + float length = std::stof(input.substr(0, input.size() - 2)); + tag_ = Tag::kLength; + value_ = length; + } + catch (const std::exception &) + { + return false; + } } - catch (const std::exception &) + else { return false; } } - else - { - return false; - } + return true; } - return true; - } - protected: - Tag tag_ = Tag::kBaseline; - float value_ = 0.0f; - }; -} + protected: + Tag tag_ = Tag::kBaseline; + float value_ = 0.0f; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/time.hpp b/src/client/cssom/values/specified/time.hpp index 87caeb503..16dcdc6c5 100644 --- a/src/client/cssom/values/specified/time.hpp +++ b/src/client/cssom/values/specified/time.hpp @@ -4,78 +4,81 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - - class Time : public generics::GenericTime, - public Parse, - public ToComputedValue + namespace client_cssom::values::specified { - friend class Parse; - using GenericTime::GenericTime; - private: - enum TimeUnit : uint8_t + class Time : public generics::GenericTime, + public Parse, + public ToComputedValue { - kSeconds, - kMilliseconds, - }; + friend class Parse; + using GenericTime::GenericTime; - public: - static constexpr const char *UNIT_SECONDS = "s"; - static constexpr const char *UNIT_MILLISECONDS = "ms"; + private: + enum TimeUnit : uint8_t + { + kSeconds, + kMilliseconds, + }; - public: - inline const char *unit() const - { - switch (unit_) + public: + static constexpr const char *UNIT_SECONDS = "s"; + static constexpr const char *UNIT_MILLISECONDS = "ms"; + + public: + inline const char *unit() const { - case kSeconds: - return UNIT_SECONDS; - case kMilliseconds: - return UNIT_MILLISECONDS; + switch (unit_) + { + case kSeconds: + return UNIT_SECONDS; + case kMilliseconds: + return UNIT_MILLISECONDS; + } + assert(false && "Invalid time unit."); + return ""; } - assert(false && "Invalid time unit."); - return ""; - } - inline bool isSeconds() const - { - return unit_ == kSeconds; - } - inline bool isMilliseconds() const - { - return unit_ == kMilliseconds; - } - - private: - bool parse(const std::string &input) override - { - if (input.back() == 's') + inline bool isSeconds() const { - unit_ = kSeconds; - seconds_ = Parse::ParseSingleValue(input.substr(0, input.size() - 1)); - return true; + return unit_ == kSeconds; } - else if (input.back() == 'm' && input.size() > 1 && input[input.size() - 2] == 's') + inline bool isMilliseconds() const { - unit_ = kMilliseconds; - CSSFloat milliseconds = Parse::ParseSingleValue(input.substr(0, input.size() - 2)); - seconds_ = milliseconds / 1000.0f; // Convert milliseconds to seconds. - return true; + return unit_ == kMilliseconds; } - else + + private: + bool parse(const std::string &input) override { - return false; // Invalid time format. + if (input.back() == 's') + { + unit_ = kSeconds; + seconds_ = Parse::ParseSingleValue(input.substr(0, input.size() - 1)); + return true; + } + else if (input.back() == 'm' && input.size() > 1 && input[input.size() - 2] == 's') + { + unit_ = kMilliseconds; + CSSFloat milliseconds = Parse::ParseSingleValue(input.substr(0, input.size() - 2)); + seconds_ = milliseconds / 1000.0f; // Convert milliseconds to seconds. + return true; + } + else + { + return false; // Invalid time format. + } } - } - public: - computed::Time toComputedValue(values::computed::Context &context) const override - { - return computed::Time(seconds_); - } + public: + computed::Time toComputedValue(values::computed::Context &context) const override + { + return computed::Time(seconds_); + } - private: - TimeUnit unit_; - }; -} + private: + TimeUnit unit_; + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/transform.hpp b/src/client/cssom/values/specified/transform.hpp index b2cd85e1e..342cea43e 100644 --- a/src/client/cssom/values/specified/transform.hpp +++ b/src/client/cssom/values/specified/transform.hpp @@ -10,540 +10,543 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - class TransformOperation; - using TransformOperationBase = generics::GenericTransformOperation; - - class TransformOperation : public TransformOperationBase, - public ToComputedValue + namespace client_cssom::values::specified { - using TransformOperationBase::GenericTransformOperation; - - public: - computed::TransformOperation toComputedValue(computed::Context &context) const override + class TransformOperation; + using TransformOperationBase = generics::GenericTransformOperation; + + class TransformOperation : public TransformOperationBase, + public ToComputedValue { - if (isMatrix()) - { - const auto &specified_matrix = getMatrix(); - return computed::TransformOperation::Matrix(specified_matrix.a().toComputedValue(context), - specified_matrix.b().toComputedValue(context), - specified_matrix.c().toComputedValue(context), - specified_matrix.d().toComputedValue(context), - specified_matrix.e().toComputedValue(context), - specified_matrix.f().toComputedValue(context)); - } - else if (isMatrix3D()) - { - const auto &specified_matrix3d = getMatrix3D(); - return computed::TransformOperation::Matrix3D(specified_matrix3d.m11().toComputedValue(context), - specified_matrix3d.m12().toComputedValue(context), - specified_matrix3d.m13().toComputedValue(context), - specified_matrix3d.m14().toComputedValue(context), - specified_matrix3d.m21().toComputedValue(context), - specified_matrix3d.m22().toComputedValue(context), - specified_matrix3d.m23().toComputedValue(context), - specified_matrix3d.m24().toComputedValue(context), - specified_matrix3d.m31().toComputedValue(context), - specified_matrix3d.m32().toComputedValue(context), - specified_matrix3d.m33().toComputedValue(context), - specified_matrix3d.m34().toComputedValue(context), - specified_matrix3d.m41().toComputedValue(context), - specified_matrix3d.m42().toComputedValue(context), - specified_matrix3d.m43().toComputedValue(context), - specified_matrix3d.m44().toComputedValue(context)); - } - else if (isSkew()) - { - const auto &specified_skew = getSkew(); - return computed::TransformOperation::Skew(specified_skew.x().toComputedValue(context), - specified_skew.y().toComputedValue(context)); - } - else if (isSkewX()) - { - const auto &specified_skew_x = getSkewX(); - return computed::TransformOperation::SkewX(specified_skew_x.angle().toComputedValue(context)); - } - else if (isSkewY()) - { - const auto &specified_skew_y = getSkewY(); - return computed::TransformOperation::SkewY(specified_skew_y.angle().toComputedValue(context)); - } - else if (isTranslate()) - { - const auto &specified_translate = getTranslate(); - return computed::TransformOperation::Translate(specified_translate.x().toComputedValue(context), - specified_translate.y().toComputedValue(context)); - } - else if (isTranslateX()) - { - const auto &specified_translate_x = getTranslateX(); - return computed::TransformOperation::TranslateX(specified_translate_x.x().toComputedValue(context)); - } - else if (isTranslateY()) - { - const auto &specified_translate_y = getTranslateY(); - return computed::TransformOperation::TranslateY(specified_translate_y.y().toComputedValue(context)); - } - else if (isTranslateZ()) - { - const auto &specified_translate_z = getTranslateZ(); - return computed::TransformOperation::TranslateZ(specified_translate_z.z().toComputedValue(context)); - } - else if (isTranslate3D()) - { - const auto &specified_translate_3d = getTranslate3D(); - return computed::TransformOperation::Translate3D( - specified_translate_3d.x().toComputedValue(context), - specified_translate_3d.y().toComputedValue(context), - specified_translate_3d.z().toComputedValue(context)); - } - else if (isScale()) - { - const auto &specified_scale = getScale(); - return computed::TransformOperation::Scale(specified_scale.number().toComputedValue(context), specified_scale.number().toComputedValue(context)); - } - else if (isScaleX()) - { - const auto &specified_scale_x = getScaleX(); - return computed::TransformOperation::ScaleX(specified_scale_x.x().toComputedValue(context)); - } - else if (isScaleY()) - { - const auto &specified_scale_y = getScaleY(); - return computed::TransformOperation::ScaleY(specified_scale_y.y().toComputedValue(context)); - } - else if (isScaleZ()) - { - const auto &specified_scale_z = getScaleZ(); - return computed::TransformOperation::ScaleZ(specified_scale_z.z().toComputedValue(context)); - } - else if (isScale3D()) - { - const auto &specified_scale_3d = getScale3D(); - return computed::TransformOperation::Scale3D(specified_scale_3d.x().toComputedValue(context), - specified_scale_3d.y().toComputedValue(context), - specified_scale_3d.z().toComputedValue(context)); - } - else if (isRotate()) - { - const auto &specified_rotate = getRotate(); - return computed::TransformOperation::Rotate(specified_rotate.angle().toComputedValue(context)); - } - else if (isRotateX()) - { - const auto &specified_rotate_x = getRotateX(); - return computed::TransformOperation::RotateX(specified_rotate_x.angle().toComputedValue(context)); - } - else if (isRotateY()) - { - const auto &specified_rotate_y = getRotateY(); - return computed::TransformOperation::RotateY(specified_rotate_y.angle().toComputedValue(context)); - } - else if (isRotateZ()) - { - const auto &specified_rotate_z = getRotateZ(); - return computed::TransformOperation::RotateZ(specified_rotate_z.angle().toComputedValue(context)); - } - else if (isRotate3D()) - { - const auto &specified_rotate_3d = getRotate3D(); - return computed::TransformOperation::Rotate3D(specified_rotate_3d.x().toComputedValue(context), - specified_rotate_3d.y().toComputedValue(context), - specified_rotate_3d.z().toComputedValue(context), - specified_rotate_3d.angle().toComputedValue(context)); - } + using TransformOperationBase::GenericTransformOperation; - assert(false && "Invalid transform operation type."); - } - }; + public: + computed::TransformOperation toComputedValue(computed::Context &context) const override + { + if (isMatrix()) + { + const auto &specified_matrix = getMatrix(); + return computed::TransformOperation::Matrix(specified_matrix.a().toComputedValue(context), + specified_matrix.b().toComputedValue(context), + specified_matrix.c().toComputedValue(context), + specified_matrix.d().toComputedValue(context), + specified_matrix.e().toComputedValue(context), + specified_matrix.f().toComputedValue(context)); + } + else if (isMatrix3D()) + { + const auto &specified_matrix3d = getMatrix3D(); + return computed::TransformOperation::Matrix3D(specified_matrix3d.m11().toComputedValue(context), + specified_matrix3d.m12().toComputedValue(context), + specified_matrix3d.m13().toComputedValue(context), + specified_matrix3d.m14().toComputedValue(context), + specified_matrix3d.m21().toComputedValue(context), + specified_matrix3d.m22().toComputedValue(context), + specified_matrix3d.m23().toComputedValue(context), + specified_matrix3d.m24().toComputedValue(context), + specified_matrix3d.m31().toComputedValue(context), + specified_matrix3d.m32().toComputedValue(context), + specified_matrix3d.m33().toComputedValue(context), + specified_matrix3d.m34().toComputedValue(context), + specified_matrix3d.m41().toComputedValue(context), + specified_matrix3d.m42().toComputedValue(context), + specified_matrix3d.m43().toComputedValue(context), + specified_matrix3d.m44().toComputedValue(context)); + } + else if (isSkew()) + { + const auto &specified_skew = getSkew(); + return computed::TransformOperation::Skew(specified_skew.x().toComputedValue(context), + specified_skew.y().toComputedValue(context)); + } + else if (isSkewX()) + { + const auto &specified_skew_x = getSkewX(); + return computed::TransformOperation::SkewX(specified_skew_x.angle().toComputedValue(context)); + } + else if (isSkewY()) + { + const auto &specified_skew_y = getSkewY(); + return computed::TransformOperation::SkewY(specified_skew_y.angle().toComputedValue(context)); + } + else if (isTranslate()) + { + const auto &specified_translate = getTranslate(); + return computed::TransformOperation::Translate(specified_translate.x().toComputedValue(context), + specified_translate.y().toComputedValue(context)); + } + else if (isTranslateX()) + { + const auto &specified_translate_x = getTranslateX(); + return computed::TransformOperation::TranslateX(specified_translate_x.x().toComputedValue(context)); + } + else if (isTranslateY()) + { + const auto &specified_translate_y = getTranslateY(); + return computed::TransformOperation::TranslateY(specified_translate_y.y().toComputedValue(context)); + } + else if (isTranslateZ()) + { + const auto &specified_translate_z = getTranslateZ(); + return computed::TransformOperation::TranslateZ(specified_translate_z.z().toComputedValue(context)); + } + else if (isTranslate3D()) + { + const auto &specified_translate_3d = getTranslate3D(); + return computed::TransformOperation::Translate3D( + specified_translate_3d.x().toComputedValue(context), + specified_translate_3d.y().toComputedValue(context), + specified_translate_3d.z().toComputedValue(context)); + } + else if (isScale()) + { + const auto &specified_scale = getScale(); + return computed::TransformOperation::Scale(specified_scale.number().toComputedValue(context), specified_scale.number().toComputedValue(context)); + } + else if (isScaleX()) + { + const auto &specified_scale_x = getScaleX(); + return computed::TransformOperation::ScaleX(specified_scale_x.x().toComputedValue(context)); + } + else if (isScaleY()) + { + const auto &specified_scale_y = getScaleY(); + return computed::TransformOperation::ScaleY(specified_scale_y.y().toComputedValue(context)); + } + else if (isScaleZ()) + { + const auto &specified_scale_z = getScaleZ(); + return computed::TransformOperation::ScaleZ(specified_scale_z.z().toComputedValue(context)); + } + else if (isScale3D()) + { + const auto &specified_scale_3d = getScale3D(); + return computed::TransformOperation::Scale3D(specified_scale_3d.x().toComputedValue(context), + specified_scale_3d.y().toComputedValue(context), + specified_scale_3d.z().toComputedValue(context)); + } + else if (isRotate()) + { + const auto &specified_rotate = getRotate(); + return computed::TransformOperation::Rotate(specified_rotate.angle().toComputedValue(context)); + } + else if (isRotateX()) + { + const auto &specified_rotate_x = getRotateX(); + return computed::TransformOperation::RotateX(specified_rotate_x.angle().toComputedValue(context)); + } + else if (isRotateY()) + { + const auto &specified_rotate_y = getRotateY(); + return computed::TransformOperation::RotateY(specified_rotate_y.angle().toComputedValue(context)); + } + else if (isRotateZ()) + { + const auto &specified_rotate_z = getRotateZ(); + return computed::TransformOperation::RotateZ(specified_rotate_z.angle().toComputedValue(context)); + } + else if (isRotate3D()) + { + const auto &specified_rotate_3d = getRotate3D(); + return computed::TransformOperation::Rotate3D(specified_rotate_3d.x().toComputedValue(context), + specified_rotate_3d.y().toComputedValue(context), + specified_rotate_3d.z().toComputedValue(context), + specified_rotate_3d.angle().toComputedValue(context)); + } - class Transform : public generics::GenericTransform, - public Parse, - public ToComputedValue - { - friend class Parse; - using generics::GenericTransform::GenericTransform; + assert(false && "Invalid transform operation type."); + } + }; - private: - bool parse(const std::string &input) override + class Transform : public generics::GenericTransform, + public Parse, + public ToComputedValue { - css_transform_parser::CSSTransformParser parser(input); - auto functions = parser.parse(); + friend class Parse; + using generics::GenericTransform::GenericTransform; - if (!parser.isValid()) + private: + bool parse(const std::string &input) override { - return false; - } - - // Clear existing operations - operations_.clear(); + css_transform_parser::CSSTransformParser parser(input); + auto functions = parser.parse(); - // Convert parsed functions to transform operations - for (const auto &func : functions) - { - if (!addTransformFunction(func)) + if (!parser.isValid()) { return false; } - } - return true; - } + // Clear existing operations + operations_.clear(); - public: - computed::Transform toComputedValue(computed::Context &context) const override - { - computed::Transform transform; - for (const auto &op : operations()) - transform.operations().push_back(op.toComputedValue(context)); - return transform; - } - - private: - bool addTransformFunction(const css_transform_parser::TransformFunction &func) - { - using namespace css_transform_parser; + // Convert parsed functions to transform operations + for (const auto &func : functions) + { + if (!addTransformFunction(func)) + { + return false; + } + } - switch (func.type) - { - case TransformFunctionType::kMatrix: - return addMatrix(func); - case TransformFunctionType::kMatrix3D: - return addMatrix3D(func); - case TransformFunctionType::kTranslate: - return addTranslate(func); - case TransformFunctionType::kTranslateX: - return addTranslateX(func); - case TransformFunctionType::kTranslateY: - return addTranslateY(func); - case TransformFunctionType::kTranslateZ: - return addTranslateZ(func); - case TransformFunctionType::kTranslate3D: - return addTranslate3D(func); - case TransformFunctionType::kScale: - return addScale(func); - case TransformFunctionType::kScaleX: - return addScaleX(func); - case TransformFunctionType::kScaleY: - return addScaleY(func); - case TransformFunctionType::kScaleZ: - return addScaleZ(func); - case TransformFunctionType::kScale3D: - return addScale3D(func); - case TransformFunctionType::kRotate: - return addRotate(func); - case TransformFunctionType::kRotateX: - return addRotateX(func); - case TransformFunctionType::kRotateY: - return addRotateY(func); - case TransformFunctionType::kRotateZ: - return addRotateZ(func); - case TransformFunctionType::kRotate3D: - return addRotate3D(func); - case TransformFunctionType::kSkew: - return addSkew(func); - case TransformFunctionType::kSkewX: - return addSkewX(func); - case TransformFunctionType::kSkewY: - return addSkewY(func); - case TransformFunctionType::kPerspective: - // Perspective is not implemented in the current transform operations - return true; // Skip for now - default: - return false; + return true; } - } - - bool addMatrix(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 6) - return false; - - operations_.push_back(TransformOperation::Matrix( - Number(func.values[0]), - Number(func.values[1]), - Number(func.values[2]), - Number(func.values[3]), - Number(func.values[4]), - Number(func.values[5]))); - return true; - } - - bool addMatrix3D(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 16) - return false; - - operations_.push_back(TransformOperation::Matrix3D( - Number(func.values[0]), Number(func.values[1]), Number(func.values[2]), Number(func.values[3]), Number(func.values[4]), Number(func.values[5]), Number(func.values[6]), Number(func.values[7]), Number(func.values[8]), Number(func.values[9]), Number(func.values[10]), Number(func.values[11]), Number(func.values[12]), Number(func.values[13]), Number(func.values[14]), Number(func.values[15]))); - return true; - } - - bool addTranslate(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 2) - return false; - - operations_.push_back(TransformOperation::Translate( - createLengthPercentage(func.values[0], func.units[0]), - createLengthPercentage(func.values[1], func.units[1]))); - return true; - } - - bool addTranslateX(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; - operations_.push_back(TransformOperation::TranslateX( - createLengthPercentage(func.values[0], func.units[0]))); - return true; - } - - bool addTranslateY(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; - - operations_.push_back(TransformOperation::TranslateY( - createLengthPercentage(func.values[0], func.units[0]))); - return true; - } - - bool addTranslateZ(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; - - operations_.push_back(TransformOperation::TranslateZ( - createLength(func.values[0], func.units[0]))); - return true; - } - - bool addTranslate3D(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 3) - return false; - - operations_.push_back(TransformOperation::Translate3D( - createLengthPercentage(func.values[0], func.units[0]), - createLengthPercentage(func.values[1], func.units[1]), - createLength(func.values[2], func.units[2]))); - return true; - } - - bool addScale(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() == 1) + public: + computed::Transform toComputedValue(computed::Context &context) const override { - // Uniform scaling: scale(x) is equivalent to scale(x, x) - operations_.push_back(TransformOperation::Scale( - Number(func.values[0]), - Number(func.values[0]))); + computed::Transform transform; + for (const auto &op : operations()) + transform.operations().push_back(op.toComputedValue(context)); + return transform; } - else if (func.values.size() == 2) + + private: + bool addTransformFunction(const css_transform_parser::TransformFunction &func) { - // Non-uniform scaling: scale(x, y) - use the first value for GenericScale - // The second value is ignored for now due to GenericScale limitations - operations_.push_back(TransformOperation::Scale( - Number(func.values[0]), - Number(func.values[0]))); + using namespace css_transform_parser; + + switch (func.type) + { + case TransformFunctionType::kMatrix: + return addMatrix(func); + case TransformFunctionType::kMatrix3D: + return addMatrix3D(func); + case TransformFunctionType::kTranslate: + return addTranslate(func); + case TransformFunctionType::kTranslateX: + return addTranslateX(func); + case TransformFunctionType::kTranslateY: + return addTranslateY(func); + case TransformFunctionType::kTranslateZ: + return addTranslateZ(func); + case TransformFunctionType::kTranslate3D: + return addTranslate3D(func); + case TransformFunctionType::kScale: + return addScale(func); + case TransformFunctionType::kScaleX: + return addScaleX(func); + case TransformFunctionType::kScaleY: + return addScaleY(func); + case TransformFunctionType::kScaleZ: + return addScaleZ(func); + case TransformFunctionType::kScale3D: + return addScale3D(func); + case TransformFunctionType::kRotate: + return addRotate(func); + case TransformFunctionType::kRotateX: + return addRotateX(func); + case TransformFunctionType::kRotateY: + return addRotateY(func); + case TransformFunctionType::kRotateZ: + return addRotateZ(func); + case TransformFunctionType::kRotate3D: + return addRotate3D(func); + case TransformFunctionType::kSkew: + return addSkew(func); + case TransformFunctionType::kSkewX: + return addSkewX(func); + case TransformFunctionType::kSkewY: + return addSkewY(func); + case TransformFunctionType::kPerspective: + // Perspective is not implemented in the current transform operations + return true; // Skip for now + default: + return false; + } } - else + + bool addMatrix(const css_transform_parser::TransformFunction &func) { - return false; - } - return true; - } + if (func.values.size() != 6) + return false; - bool addScaleX(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + operations_.push_back(TransformOperation::Matrix( + Number(func.values[0]), + Number(func.values[1]), + Number(func.values[2]), + Number(func.values[3]), + Number(func.values[4]), + Number(func.values[5]))); + return true; + } - operations_.push_back(TransformOperation::ScaleX( - Number(func.values[0]))); - return true; - } + bool addMatrix3D(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 16) + return false; - bool addScaleY(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + operations_.push_back(TransformOperation::Matrix3D( + Number(func.values[0]), Number(func.values[1]), Number(func.values[2]), Number(func.values[3]), Number(func.values[4]), Number(func.values[5]), Number(func.values[6]), Number(func.values[7]), Number(func.values[8]), Number(func.values[9]), Number(func.values[10]), Number(func.values[11]), Number(func.values[12]), Number(func.values[13]), Number(func.values[14]), Number(func.values[15]))); + return true; + } - operations_.push_back(TransformOperation::ScaleY( - Number(func.values[0]))); - return true; - } + bool addTranslate(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 2) + return false; - bool addScaleZ(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + operations_.push_back(TransformOperation::Translate( + createLengthPercentage(func.values[0], func.units[0]), + createLengthPercentage(func.values[1], func.units[1]))); + return true; + } - operations_.push_back(TransformOperation::ScaleZ( - Number(func.values[0]))); - return true; - } + bool addTranslateX(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 1) + return false; - bool addScale3D(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 3) - return false; + operations_.push_back(TransformOperation::TranslateX( + createLengthPercentage(func.values[0], func.units[0]))); + return true; + } - operations_.push_back(TransformOperation::Scale3D( - Number(func.values[0]), - Number(func.values[1]), - Number(func.values[2]))); - return true; - } + bool addTranslateY(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 1) + return false; - bool addRotate(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + operations_.push_back(TransformOperation::TranslateY( + createLengthPercentage(func.values[0], func.units[0]))); + return true; + } - operations_.push_back(TransformOperation::Rotate( - createAngle(func.values[0], func.units[0]))); - return true; - } + bool addTranslateZ(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 1) + return false; - bool addRotateX(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + operations_.push_back(TransformOperation::TranslateZ( + createLength(func.values[0], func.units[0]))); + return true; + } - operations_.push_back(TransformOperation::RotateX( - createAngle(func.values[0], func.units[0]))); - return true; - } + bool addTranslate3D(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 3) + return false; - bool addRotateY(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + operations_.push_back(TransformOperation::Translate3D( + createLengthPercentage(func.values[0], func.units[0]), + createLengthPercentage(func.values[1], func.units[1]), + createLength(func.values[2], func.units[2]))); + return true; + } - operations_.push_back(TransformOperation::RotateY( - createAngle(func.values[0], func.units[0]))); - return true; - } + bool addScale(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() == 1) + { + // Uniform scaling: scale(x) is equivalent to scale(x, x) + operations_.push_back(TransformOperation::Scale( + Number(func.values[0]), + Number(func.values[0]))); + } + else if (func.values.size() == 2) + { + // Non-uniform scaling: scale(x, y) - use the first value for GenericScale + // The second value is ignored for now due to GenericScale limitations + operations_.push_back(TransformOperation::Scale( + Number(func.values[0]), + Number(func.values[0]))); + } + else + { + return false; + } + return true; + } - bool addRotateZ(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + bool addScaleX(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 1) + return false; - operations_.push_back(TransformOperation::RotateZ( - createAngle(func.values[0], func.units[0]))); - return true; - } + operations_.push_back(TransformOperation::ScaleX( + Number(func.values[0]))); + return true; + } - bool addRotate3D(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 4) - return false; - - operations_.push_back(TransformOperation::Rotate3D( - Number(func.values[0]), - Number(func.values[1]), - Number(func.values[2]), - createAngle(func.values[3], func.units[3]))); - return true; - } - - bool addSkew(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 2) - return false; + bool addScaleY(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 1) + return false; - operations_.push_back(TransformOperation::Skew( - createAngle(func.values[0], func.units[0]), - createAngle(func.values[1], func.units[1]))); - return true; - } + operations_.push_back(TransformOperation::ScaleY( + Number(func.values[0]))); + return true; + } - bool addSkewX(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + bool addScaleZ(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 1) + return false; - operations_.push_back(TransformOperation::SkewX( - createAngle(func.values[0], func.units[0]))); - return true; - } + operations_.push_back(TransformOperation::ScaleZ( + Number(func.values[0]))); + return true; + } - bool addSkewY(const css_transform_parser::TransformFunction &func) - { - if (func.values.size() != 1) - return false; + bool addScale3D(const css_transform_parser::TransformFunction &func) + { + if (func.values.size() != 3) + return false; - operations_.push_back(TransformOperation::SkewY( - createAngle(func.values[0], func.units[0]))); - return true; - } + operations_.push_back(TransformOperation::Scale3D( + Number(func.values[0]), + Number(func.values[1]), + Number(func.values[2]))); + return true; + } - // Helper methods to create values - LengthPercentage createLengthPercentage(double value, const std::string &unit) - { - if (unit == "%") + bool addRotate(const css_transform_parser::TransformFunction &func) { - return LengthPercentage(computed::Percentage(value / 100.0f)); + if (func.values.size() != 1) + return false; + + operations_.push_back(TransformOperation::Rotate( + createAngle(func.values[0], func.units[0]))); + return true; } - else + + bool addRotateX(const css_transform_parser::TransformFunction &func) { - return LengthPercentage(createLength(value, unit)); + if (func.values.size() != 1) + return false; + + operations_.push_back(TransformOperation::RotateX( + createAngle(func.values[0], func.units[0]))); + return true; } - } - NoCalcLength createLength(double value, const std::string &unit) - { - if (unit == "px" || unit.empty()) + bool addRotateY(const css_transform_parser::TransformFunction &func) { - return NoCalcLength::FromPx(value); + if (func.values.size() != 1) + return false; + + operations_.push_back(TransformOperation::RotateY( + createAngle(func.values[0], func.units[0]))); + return true; } - else if (unit == "em") + + bool addRotateZ(const css_transform_parser::TransformFunction &func) { - return NoCalcLength(FontRelativeLength::Em(value)); + if (func.values.size() != 1) + return false; + + operations_.push_back(TransformOperation::RotateZ( + createAngle(func.values[0], func.units[0]))); + return true; } - else if (unit == "rem") + + bool addRotate3D(const css_transform_parser::TransformFunction &func) { - return NoCalcLength(FontRelativeLength::Rem(value)); + if (func.values.size() != 4) + return false; + + operations_.push_back(TransformOperation::Rotate3D( + Number(func.values[0]), + Number(func.values[1]), + Number(func.values[2]), + createAngle(func.values[3], func.units[3]))); + return true; } - else + + bool addSkew(const css_transform_parser::TransformFunction &func) { - // Default to pixels for unknown units - return NoCalcLength::FromPx(value); + if (func.values.size() != 2) + return false; + + operations_.push_back(TransformOperation::Skew( + createAngle(func.values[0], func.units[0]), + createAngle(func.values[1], func.units[1]))); + return true; } - } - Angle createAngle(double value, const std::string &unit) - { - if (unit == "deg" || unit.empty()) + bool addSkewX(const css_transform_parser::TransformFunction &func) { - return Angle::Deg(value); + if (func.values.size() != 1) + return false; + + operations_.push_back(TransformOperation::SkewX( + createAngle(func.values[0], func.units[0]))); + return true; } - else if (unit == "rad") + + bool addSkewY(const css_transform_parser::TransformFunction &func) { - return Angle::Rad(value); + if (func.values.size() != 1) + return false; + + operations_.push_back(TransformOperation::SkewY( + createAngle(func.values[0], func.units[0]))); + return true; } - else if (unit == "grad") + + // Helper methods to create values + LengthPercentage createLengthPercentage(double value, const std::string &unit) { - return Angle::Grad(value); + if (unit == "%") + { + return LengthPercentage(computed::Percentage(value / 100.0f)); + } + else + { + return LengthPercentage(createLength(value, unit)); + } } - else if (unit == "turn") + + NoCalcLength createLength(double value, const std::string &unit) { - return Angle::Turn(value); + if (unit == "px" || unit.empty()) + { + return NoCalcLength::FromPx(value); + } + else if (unit == "em") + { + return NoCalcLength(FontRelativeLength::Em(value)); + } + else if (unit == "rem") + { + return NoCalcLength(FontRelativeLength::Rem(value)); + } + else + { + // Default to pixels for unknown units + return NoCalcLength::FromPx(value); + } } - else + + Angle createAngle(double value, const std::string &unit) { - // Default to degrees for unknown units - return Angle::Deg(value); + if (unit == "deg" || unit.empty()) + { + return Angle::Deg(value); + } + else if (unit == "rad") + { + return Angle::Rad(value); + } + else if (unit == "grad") + { + return Angle::Grad(value); + } + else if (unit == "turn") + { + return Angle::Turn(value); + } + else + { + // Default to degrees for unknown units + return Angle::Deg(value); + } } - } - }; -} + }; + } +} // namespace endor diff --git a/src/client/cssom/values/specified/url.hpp b/src/client/cssom/values/specified/url.hpp index 540e4baca..38f74945d 100644 --- a/src/client/cssom/values/specified/url.hpp +++ b/src/client/cssom/values/specified/url.hpp @@ -3,7 +3,10 @@ #include #include -namespace client_cssom::values::specified +namespace endor { - using UrlOrNone = generics::GenericUrlOrNone; -} + namespace client_cssom::values::specified + { + using UrlOrNone = generics::GenericUrlOrNone; + } +} // namespace endor diff --git a/src/client/cssom/variable_reference_tracker.cpp b/src/client/cssom/variable_reference_tracker.cpp index faebc8332..20400d32f 100644 --- a/src/client/cssom/variable_reference_tracker.cpp +++ b/src/client/cssom/variable_reference_tracker.cpp @@ -1,89 +1,92 @@ #include "./variable_reference_tracker.hpp" #include "./values/computed/context.hpp" -namespace client_cssom +namespace endor { - using namespace std; - - // Static empty set for returning when no dependencies exist - const unordered_set VariableReferenceTracker::empty_set_; - - void VariableReferenceTracker::trackDependency(const string &variable_name, const string &property_name) + namespace client_cssom { - variable_dependencies_[variable_name].insert(property_name); - } + using namespace std; - void VariableReferenceTracker::clearDependencies(const string &property_name) - { - // Remove this property from all variable dependency sets - for (auto &pair : variable_dependencies_) + // Static empty set for returning when no dependencies exist + const unordered_set VariableReferenceTracker::empty_set_; + + void VariableReferenceTracker::trackDependency(const string &variable_name, const string &property_name) { - pair.second.erase(property_name); + variable_dependencies_[variable_name].insert(property_name); } - // Clean up empty dependency sets - for (auto it = variable_dependencies_.begin(); it != variable_dependencies_.end();) + void VariableReferenceTracker::clearDependencies(const string &property_name) { - if (it->second.empty()) + // Remove this property from all variable dependency sets + for (auto &pair : variable_dependencies_) { - it = variable_dependencies_.erase(it); + pair.second.erase(property_name); } - else + + // Clean up empty dependency sets + for (auto it = variable_dependencies_.begin(); it != variable_dependencies_.end();) { - ++it; + if (it->second.empty()) + { + it = variable_dependencies_.erase(it); + } + else + { + ++it; + } } } - } - void VariableReferenceTracker::clearVariableDependencies(const string &variable_name) - { - variable_dependencies_.erase(variable_name); - } + void VariableReferenceTracker::clearVariableDependencies(const string &variable_name) + { + variable_dependencies_.erase(variable_name); + } - const unordered_set &VariableReferenceTracker::getDependents(const string &variable_name) const - { - auto it = variable_dependencies_.find(variable_name); - return (it != variable_dependencies_.end()) ? it->second : empty_set_; - } + const unordered_set &VariableReferenceTracker::getDependents(const string &variable_name) const + { + auto it = variable_dependencies_.find(variable_name); + return (it != variable_dependencies_.end()) ? it->second : empty_set_; + } - bool VariableReferenceTracker::hasDependents(const string &variable_name) const - { - auto it = variable_dependencies_.find(variable_name); - return it != variable_dependencies_.end() && !it->second.empty(); - } + bool VariableReferenceTracker::hasDependents(const string &variable_name) const + { + auto it = variable_dependencies_.find(variable_name); + return it != variable_dependencies_.end() && !it->second.empty(); + } - void VariableReferenceTracker::setVariableChangeCallback(VariableChangeCallback callback) - { - change_callback_ = move(callback); - } + void VariableReferenceTracker::setVariableChangeCallback(VariableChangeCallback callback) + { + change_callback_ = move(callback); + } - void VariableReferenceTracker::notifyVariableChanged(const string &variable_name, - const string &new_value, - const values::computed::Context &context) - { - if (change_callback_ && hasDependents(variable_name)) + void VariableReferenceTracker::notifyVariableChanged(const string &variable_name, + const string &new_value, + const values::computed::Context &context) { - const auto &dependents = getDependents(variable_name); - for (const string &property_name : dependents) + if (change_callback_ && hasDependents(variable_name)) { - change_callback_(property_name, new_value, context); + const auto &dependents = getDependents(variable_name); + for (const string &property_name : dependents) + { + change_callback_(property_name, new_value, context); + } } } - } - size_t VariableReferenceTracker::getDependencyCount() const - { - size_t count = 0; - for (const auto &pair : variable_dependencies_) + size_t VariableReferenceTracker::getDependencyCount() const { - count += pair.second.size(); + size_t count = 0; + for (const auto &pair : variable_dependencies_) + { + count += pair.second.size(); + } + return count; } - return count; - } - void VariableReferenceTracker::clear() - { - variable_dependencies_.clear(); - } + void VariableReferenceTracker::clear() + { + variable_dependencies_.clear(); + } -} // namespace client_cssom \ No newline at end of file + } // namespace client_cssom +} // namespace endor \ No newline at end of file diff --git a/src/client/cssom/variable_reference_tracker.hpp b/src/client/cssom/variable_reference_tracker.hpp index 23e6f9189..b2c4c7c0e 100644 --- a/src/client/cssom/variable_reference_tracker.hpp +++ b/src/client/cssom/variable_reference_tracker.hpp @@ -5,93 +5,96 @@ #include #include -namespace client_cssom +namespace endor { - namespace values + namespace client_cssom { - namespace computed + namespace values { - class Context; + namespace computed + { + class Context; + } } - } - /** + /** * Manages variable references and dependency tracking for CSS custom properties. * Provides functionality to track which properties depend on which variables * and trigger recomputation when variables change. */ - class VariableReferenceTracker - { - public: - using VariableChangeCallback = std::function; + class VariableReferenceTracker + { + public: + using VariableChangeCallback = std::function; - /** + /** * Track that a property depends on a variable. * @param variable_name The CSS variable name (e.g., "--primary-color") * @param property_name The property that uses this variable */ - void trackDependency(const std::string &variable_name, const std::string &property_name); + void trackDependency(const std::string &variable_name, const std::string &property_name); - /** + /** * Clear all dependencies for a property. * @param property_name The property to clear dependencies for */ - void clearDependencies(const std::string &property_name); + void clearDependencies(const std::string &property_name); - /** + /** * Clear all dependencies for a variable. * @param variable_name The variable to clear dependencies for */ - void clearVariableDependencies(const std::string &variable_name); + void clearVariableDependencies(const std::string &variable_name); - /** + /** * Get all properties that depend on a given variable. * @param variable_name The variable name * @return Set of property names that depend on this variable */ - const std::unordered_set &getDependents(const std::string &variable_name) const; + const std::unordered_set &getDependents(const std::string &variable_name) const; - /** + /** * Check if a variable has any dependents. * @param variable_name The variable name * @return True if the variable has dependent properties */ - bool hasDependents(const std::string &variable_name) const; + bool hasDependents(const std::string &variable_name) const; - /** + /** * Set a callback to be called when variable dependencies need recomputation. * @param callback Function to call when properties need to be recomputed */ - void setVariableChangeCallback(VariableChangeCallback callback); + void setVariableChangeCallback(VariableChangeCallback callback); - /** + /** * Trigger recomputation of all properties that depend on a variable. * @param variable_name The variable that changed * @param new_value The new value of the variable * @param context The computation context */ - void notifyVariableChanged(const std::string &variable_name, const std::string &new_value, const values::computed::Context &context); + void notifyVariableChanged(const std::string &variable_name, const std::string &new_value, const values::computed::Context &context); - /** + /** * Get the total number of tracked dependencies. * @return Number of tracked variable->property dependencies */ - size_t getDependencyCount() const; + size_t getDependencyCount() const; - /** + /** * Clear all tracked dependencies. */ - void clear(); + void clear(); - private: - // Maps variable names to sets of properties that depend on them - std::unordered_map> variable_dependencies_; + private: + // Maps variable names to sets of properties that depend on them + std::unordered_map> variable_dependencies_; - // Callback for when variables change - VariableChangeCallback change_callback_; + // Callback for when variables change + VariableChangeCallback change_callback_; - // Empty set for returning when no dependencies exist - static const std::unordered_set empty_set_; - }; + // Empty set for returning when no dependencies exist + static const std::unordered_set empty_set_; + }; -} // namespace client_cssom + } // namespace client_cssom +} // namespace endor diff --git a/src/client/dom/attr.cpp b/src/client/dom/attr.cpp index 883815767..23c8c6964 100644 --- a/src/client/dom/attr.cpp +++ b/src/client/dom/attr.cpp @@ -1,46 +1,49 @@ #include "./attr.hpp" #include "./element.hpp" -namespace dom +namespace endor { - using namespace std; - - Attr::Attr(std::string name, std::string initialValue, shared_ptr ownerElement) - : Node(NodeType::ATTRIBUTE_NODE, name, ownerElement->getOwnerDocumentReference()) - , name(name) - , value(initialValue) - , specified(true) - , ownerElement(ownerElement) + namespace dom { - string nameSource = name; + using namespace std; - // parse name into `prefix` and `localName` - size_t colonPos = nameSource.find(':'); - if (colonPos != string::npos) + Attr::Attr(std::string name, std::string initialValue, shared_ptr ownerElement) + : Node(NodeType::ATTRIBUTE_NODE, name, ownerElement->getOwnerDocumentReference()) + , name(name) + , value(initialValue) + , specified(true) + , ownerElement(ownerElement) { - prefix = nameSource.substr(0, colonPos); - localName = nameSource.substr(colonPos + 1); + string nameSource = name; + + // parse name into `prefix` and `localName` + size_t colonPos = nameSource.find(':'); + if (colonPos != string::npos) + { + prefix = nameSource.substr(0, colonPos); + localName = nameSource.substr(colonPos + 1); + } + else + { + localName = nameSource; + } } - else + + Attr::Attr(pugi::xml_attribute attr, shared_ptr ownerElement) + : Attr(attr.name(), attr.value(), ownerElement) { - localName = nameSource; } - } - - Attr::Attr(pugi::xml_attribute attr, shared_ptr ownerElement) - : Attr(attr.name(), attr.value(), ownerElement) - { - } - Attr::Attr(Attr &other) - : Node(other) - , localName(other.localName) - , name(other.name) - , namespaceURI(other.namespaceURI) - , prefix(other.prefix) - , specified(other.specified) - , value(other.value) - , ownerElement(other.ownerElement) - { + Attr::Attr(Attr &other) + : Node(other) + , localName(other.localName) + , name(other.name) + , namespaceURI(other.namespaceURI) + , prefix(other.prefix) + , specified(other.specified) + , value(other.value) + , ownerElement(other.ownerElement) + { + } } -} +} // namespace endor diff --git a/src/client/dom/attr.hpp b/src/client/dom/attr.hpp index 5a8e8f647..337f24d61 100644 --- a/src/client/dom/attr.hpp +++ b/src/client/dom/attr.hpp @@ -8,36 +8,39 @@ using namespace std; -namespace dom +namespace endor { - class Element; - class Attr : public Node + namespace dom { - public: - inline static shared_ptr Make(shared_ptr ownerElement, std::string name, std::string initialValue) + class Element; + class Attr : public Node { - return make_shared(name, initialValue, ownerElement); - } - inline static shared_ptr Make(shared_ptr ownerElement, pugi::xml_attribute attr) - { - assert(attr.name() != nullptr && "The attribute name is null."); - assert(attr.value() != nullptr && "The attribute value is null."); - return make_shared(attr, ownerElement); - } + public: + inline static shared_ptr Make(shared_ptr ownerElement, std::string name, std::string initialValue) + { + return make_shared(name, initialValue, ownerElement); + } + inline static shared_ptr Make(shared_ptr ownerElement, pugi::xml_attribute attr) + { + assert(attr.name() != nullptr && "The attribute name is null."); + assert(attr.value() != nullptr && "The attribute value is null."); + return make_shared(attr, ownerElement); + } - public: - Attr(std::string name, std::string initialValue, shared_ptr ownerElement); - Attr(pugi::xml_attribute attr, shared_ptr ownerElement); - Attr(Attr &other); - ~Attr() = default; + public: + Attr(std::string name, std::string initialValue, shared_ptr ownerElement); + Attr(pugi::xml_attribute attr, shared_ptr ownerElement); + Attr(Attr &other); + ~Attr() = default; - public: - string localName; - string name; - string namespaceURI; - string prefix; - bool specified; - string value; - shared_ptr ownerElement; - }; -} + public: + string localName; + string name; + string namespaceURI; + string prefix; + bool specified; + string value; + shared_ptr ownerElement; + }; + } +} // namespace endor diff --git a/src/client/dom/browsing_context.cpp b/src/client/dom/browsing_context.cpp index 70849bb54..aa71d70e8 100644 --- a/src/client/dom/browsing_context.cpp +++ b/src/client/dom/browsing_context.cpp @@ -2,49 +2,52 @@ #include "./browsing_context.hpp" #include "../html/html_script_element.hpp" -namespace dom +namespace endor { - uint32_t BrowsingContext::registerScriptForExecution(std::shared_ptr script) + namespace dom { - static TrIdGenerator idgen(0x1); - uint32_t script_id = idgen.get(); - script_execution_queue.emplace_back(script_id, std::weak_ptr(script)); + uint32_t BrowsingContext::registerScriptForExecution(std::shared_ptr script) + { + static TrIdGenerator idgen(0x1); + uint32_t script_id = idgen.get(); + script_execution_queue.emplace_back(script_id, std::weak_ptr(script)); - // Try to execute if this is the first script in the queue - tryExecuteNextScript(); - return script_id; - } + // Try to execute if this is the first script in the queue + tryExecuteNextScript(); + return script_id; + } - void BrowsingContext::tryExecuteNextScript() - { - while (!script_execution_queue.empty()) + void BrowsingContext::tryExecuteNextScript() { - auto &handle = script_execution_queue.front(); - auto script = handle.scriptElement.lock(); - - // If script was destroyed, remove from queue and continue - if (!script) + while (!script_execution_queue.empty()) { - script_execution_queue.pop_front(); - continue; - } + auto &handle = script_execution_queue.front(); + auto script = handle.scriptElement.lock(); - // Execute the script and it will return false if not ready yet - if (!script->executeScriptFromQueue()) - return; // Script will call notifyScriptExecutionComplete when done + // If script was destroyed, remove from queue and continue + if (!script) + { + script_execution_queue.pop_front(); + continue; + } - // Script not ready yet, stop trying - break; + // Execute the script and it will return false if not ready yet + if (!script->executeScriptFromQueue()) + return; // Script will call notifyScriptExecutionComplete when done + + // Script not ready yet, stop trying + break; + } } - } - void BrowsingContext::notifyScriptExecutionComplete(uint32_t script_id) - { - if (!script_execution_queue.empty() && - script_execution_queue.front().scriptId == script_id) + void BrowsingContext::notifyScriptExecutionComplete(uint32_t script_id) { - script_execution_queue.pop_front(); - tryExecuteNextScript(); + if (!script_execution_queue.empty() && + script_execution_queue.front().scriptId == script_id) + { + script_execution_queue.pop_front(); + tryExecuteNextScript(); + } } } -} +} // namespace endor diff --git a/src/client/dom/browsing_context.hpp b/src/client/dom/browsing_context.hpp index f7e449beb..3d7209d8d 100644 --- a/src/client/dom/browsing_context.hpp +++ b/src/client/dom/browsing_context.hpp @@ -12,37 +12,39 @@ #include "./runtime_context.hpp" #include "./document.hpp" -namespace dom +namespace endor { - enum class InputType + namespace dom { - URL, - Source, - }; - - // Forward declaration to avoid circular dependency - class HTMLScriptElement; + enum class InputType + { + URL, + Source, + }; - // Script execution handle to decouple from HTMLScriptElement - struct ScriptExecutionHandle - { - uint32_t scriptId; - std::weak_ptr scriptElement; + // Forward declaration to avoid circular dependency + class HTMLScriptElement; - ScriptExecutionHandle(uint32_t id, std::weak_ptr element) - : scriptId(id) - , scriptElement(element) + // Script execution handle to decouple from HTMLScriptElement + struct ScriptExecutionHandle { - } - }; + uint32_t scriptId; + std::weak_ptr scriptElement; - class BrowsingContext : public RuntimeContext - { - public: - using RuntimeContext::RuntimeContext; + ScriptExecutionHandle(uint32_t id, std::weak_ptr element) + : scriptId(id) + , scriptElement(element) + { + } + }; + + class BrowsingContext : public RuntimeContext + { + public: + using RuntimeContext::RuntimeContext; - public: - /** + public: + /** * Create a new document. * * @param url The url of the document. @@ -51,106 +53,107 @@ namespace dom * @param base_uri The base URI of the document, used for resolving relative URLs. * @returns The created document. */ - template - shared_ptr create(const std::string &url, - DOMParsingType type, - InputType input_type, - const std::string &base_uri) - { - shared_ptr document; - if (type == DOMParsingType::HTML) - document = make_shared(getSharedPtr(), true); - else - throw std::runtime_error("Unsupported document type"); - - if (input_type == InputType::Source) - { - document->setSource(url); - document->baseURI = base_uri; - } - else if (input_type == InputType::URL) + template + shared_ptr create(const std::string &url, + DOMParsingType type, + InputType input_type, + const std::string &base_uri) { - document->setUrl(url); + shared_ptr document; + if (type == DOMParsingType::HTML) + document = make_shared(getSharedPtr(), true); + else + throw std::runtime_error("Unsupported document type"); + + if (input_type == InputType::Source) + { + document->setSource(url); + document->baseURI = base_uri; + } + else if (input_type == InputType::URL) + { + document->setUrl(url); + } + else + { + assert(false && "The input type must be url or source"); + } + + documents.push_back(document); + return document; } - else - { - assert(false && "The input type must be url or source"); - } - - documents.push_back(document); - return document; - } - /** + /** * Get the active document, which is the last document in the documents list. */ - inline std::shared_ptr getActiveDocument() - { - assert(activeDocument != nullptr && "The active document is null"); - return activeDocument; - } + inline std::shared_ptr getActiveDocument() + { + assert(activeDocument != nullptr && "The active document is null"); + return activeDocument; + } - /** + /** * Open the given document, it will start loading the document. * * @param document The document to open. */ - template - void open(shared_ptr document) - { - // Open the document to start loading - document->open(); + template + void open(shared_ptr document) + { + // Open the document to start loading + document->open(); - // Set the active document if it's an HTML document - if constexpr (std::is_same::value) - activeDocument = std::static_pointer_cast(document); - } + // Set the active document if it's an HTML document + if constexpr (std::is_same::value) + activeDocument = std::static_pointer_cast(document); + } - /** + /** * Create a new script object from this context. * * @param url The URL of the script. * @param type The type of the script: classic or module. * @returns The new DOM script object. */ - shared_ptr createScript(const std::string &url, SourceTextType type) - { - return scriptingContext->create(getSharedPtr(), url, type); - } + shared_ptr createScript(const std::string &url, SourceTextType type) + { + return scriptingContext->create(getSharedPtr(), url, type); + } - /** + /** * Parse the input json string and update the import map. * * @param json The JSON string of the import map. * @returns Whether the input json is valid. */ - inline bool updateImportMap(const std::string &json) - { - return scriptingContext->updateImportMapFromJSON(json); - } + inline bool updateImportMap(const std::string &json) + { + return scriptingContext->updateImportMapFromJSON(json); + } - /** + /** * Register a script for document-order execution. * Returns a unique script ID for tracking. */ - uint32_t registerScriptForExecution(std::shared_ptr script); + uint32_t registerScriptForExecution(std::shared_ptr script); - /** + /** * Try to execute the next script in the queue if it's ready. */ - void tryExecuteNextScript(); + void tryExecuteNextScript(); - /** + /** * Notify that a script has completed execution. */ - void notifyScriptExecutionComplete(uint32_t script_id); - - public: - vector> documents; - shared_ptr activeDocument = nullptr; - - private: - // Script execution queue using handles to avoid circular dependency - std::deque script_execution_queue; - }; -} + void notifyScriptExecutionComplete(uint32_t script_id); + + public: + vector> documents; + shared_ptr activeDocument = nullptr; + + private: + // Script execution queue using handles to avoid circular dependency + std::deque script_execution_queue; + }; + } +} // namespace endor diff --git a/src/client/dom/character_data.cpp b/src/client/dom/character_data.cpp index 6e1145751..654243121 100644 --- a/src/client/dom/character_data.cpp +++ b/src/client/dom/character_data.cpp @@ -2,206 +2,209 @@ #include "./document.hpp" #include "./element.hpp" -namespace dom +namespace endor { - using namespace std; - - string replaceAll(const string &str, const string &from, const string &to) + namespace dom { - if (str.empty() || str.size() < from.size()) - return str; + using namespace std; - string result = str; - size_t start_pos = 0; - while ((start_pos = result.find(from, start_pos)) != string::npos) + string replaceAll(const string &str, const string &from, const string &to) { - result.replace(start_pos, from.length(), to); - start_pos += to.length(); - } - return result; - } + if (str.empty() || str.size() < from.size()) + return str; - string processTextContent(const char *text) - { - if (text == nullptr || strlen(text) == 0) - return ""; + string result = str; + size_t start_pos = 0; + while ((start_pos = result.find(from, start_pos)) != string::npos) + { + result.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + return result; + } - string result(text); - try + string processTextContent(const char *text) { - // Remove the new line characters - result = replaceAll(result, "\n", ""); - result = replaceAll(result, "\r", ""); - - // Replace the special characters - result = replaceAll(result, "<", "<"); - result = replaceAll(result, ">", ">"); - result = replaceAll(result, "&", "&"); - result = replaceAll(result, """, "\""); - result = replaceAll(result, "'", "'"); + if (text == nullptr || strlen(text) == 0) + return ""; - // Trim the beginning and ending spaces + string result(text); + try + { + // Remove the new line characters + result = replaceAll(result, "\n", ""); + result = replaceAll(result, "\r", ""); + + // Replace the special characters + result = replaceAll(result, "<", "<"); + result = replaceAll(result, ">", ">"); + result = replaceAll(result, "&", "&"); + result = replaceAll(result, """, "\""); + result = replaceAll(result, "'", "'"); + + // Trim the beginning and ending spaces + { + size_t start = result.find_first_not_of(" \t\n\r"); + if (start == string::npos || start >= result.length()) + start = 0; + size_t end = result.find_last_not_of(" \t\n\r"); + if (end == string::npos || end >= result.length()) + end = result.length(); + result = result.substr(start, end - start + 1); + } + } + catch (const std::exception &e) { - size_t start = result.find_first_not_of(" \t\n\r"); - if (start == string::npos || start >= result.length()) - start = 0; - size_t end = result.find_last_not_of(" \t\n\r"); - if (end == string::npos || end >= result.length()) - end = result.length(); - result = result.substr(start, end - start + 1); + cerr << "Failed to process the text content: " << e.what() << endl; + cerr << " input text: (" << text << ")" << endl; + result = ""; } + return result; } - catch (const std::exception &e) + + CharacterData::CharacterData(pugi::xml_node node, shared_ptr ownerDocument) + : Node(node, ownerDocument) { - cerr << "Failed to process the text content: " << e.what() << endl; - cerr << " input text: (" << text << ")" << endl; - result = ""; + if (node.type() == pugi::xml_node_type::node_pcdata) + data_ = processTextContent(node.value()); } - return result; - } - - CharacterData::CharacterData(pugi::xml_node node, shared_ptr ownerDocument) - : Node(node, ownerDocument) - { - if (node.type() == pugi::xml_node_type::node_pcdata) - data_ = processTextContent(node.value()); - } - - CharacterData::CharacterData(NodeType nodeType, std::string nodeName, const string &data, shared_ptr ownerDocument) - : Node(nodeType, nodeName, ownerDocument) - , data_(data) - { - } - - CharacterData::CharacterData(const CharacterData &other) - : Node(other) - , data_(other.data_) - , nextElementSibling_(other.nextElementSibling_) - , previousElementSibling_(other.previousElementSibling_) - { - } - shared_ptr CharacterData::nextElementSibling() - { - return nullptr; - } + CharacterData::CharacterData(NodeType nodeType, std::string nodeName, const string &data, shared_ptr ownerDocument) + : Node(nodeType, nodeName, ownerDocument) + , data_(data) + { + } - shared_ptr CharacterData::previousElementSibling() - { - return nullptr; - } + CharacterData::CharacterData(const CharacterData &other) + : Node(other) + , data_(other.data_) + , nextElementSibling_(other.nextElementSibling_) + , previousElementSibling_(other.previousElementSibling_) + { + } - void CharacterData::remove() - { - if (parentNode.expired()) - return; + shared_ptr CharacterData::nextElementSibling() + { + return nullptr; + } - auto parent = parentNode.lock(); - if (parent == nullptr) - return; + shared_ptr CharacterData::previousElementSibling() + { + return nullptr; + } - parent->removeChild(shared_from_this()); - } + void CharacterData::remove() + { + if (parentNode.expired()) + return; - void CharacterData::appendData(const string &data) - { - data_ += data; - } + auto parent = parentNode.lock(); + if (parent == nullptr) + return; - void CharacterData::deleteData(size_t offset, size_t count) - { - if (offset >= data_.length()) - return; + parent->removeChild(shared_from_this()); + } - data_.erase(offset, count); - } + void CharacterData::appendData(const string &data) + { + data_ += data; + } - void CharacterData::insertData(size_t offset, const string &data) - { - if (offset >= data_.length()) - return; + void CharacterData::deleteData(size_t offset, size_t count) + { + if (offset >= data_.length()) + return; - data_.insert(offset, data); - } + data_.erase(offset, count); + } - void CharacterData::replaceData(size_t offset, size_t count, const string &data) - { - if (offset >= data_.length()) - return; + void CharacterData::insertData(size_t offset, const string &data) + { + if (offset >= data_.length()) + return; - data_.replace(offset, count, data); - } + data_.insert(offset, data); + } - void CharacterData::replaceWith(vector> nodes) - { - if (nodes.empty()) + void CharacterData::replaceData(size_t offset, size_t count, const string &data) { - // If no arguments are passed in, this method removes the node from the DOM tree. - remove(); - return; + if (offset >= data_.length()) + return; + + data_.replace(offset, count, data); } - string stringData; - for (auto &node : nodes) + void CharacterData::replaceWith(vector> nodes) { - if (node == nullptr) - continue; + if (nodes.empty()) + { + // If no arguments are passed in, this method removes the node from the DOM tree. + remove(); + return; + } - if (node->isCharacterData()) + string stringData; + for (auto &node : nodes) { - auto charDataNode = static_pointer_cast(node); - stringData += charDataNode->data(); + if (node == nullptr) + continue; + + if (node->isCharacterData()) + { + auto charDataNode = static_pointer_cast(node); + stringData += charDataNode->data(); + } } + + // Replace the data of this node with the concatenated string data. + data_ = stringData; } - // Replace the data of this node with the concatenated string data. - data_ = stringData; - } + string CharacterData::substringData(size_t offset, size_t count) + { + if (offset >= data_.length()) + return ""; + return data_.substr(offset, count); + } - string CharacterData::substringData(size_t offset, size_t count) - { - if (offset >= data_.length()) - return ""; - return data_.substr(offset, count); - } + void CharacterData::before(vector> nodes) + { + auto parent = parentNode.lock(); + if (parent == nullptr || nodes.size() == 0) + return; - void CharacterData::before(vector> nodes) - { - auto parent = parentNode.lock(); - if (parent == nullptr || nodes.size() == 0) - return; + for (auto &node : nodes) + { + if (node == nullptr) + continue; + parent->insertBefore(node, static_pointer_cast(shared_from_this())); + } + } - for (auto &node : nodes) + void CharacterData::before(string text) { - if (node == nullptr) - continue; - parent->insertBefore(node, static_pointer_cast(shared_from_this())); + before(getOwnerDocumentChecked().createTextNode(text)); } - } - - void CharacterData::before(string text) - { - before(getOwnerDocumentChecked().createTextNode(text)); - } - void CharacterData::after(vector> nodes) - { - auto parent = parentNode.lock(); - if (parent == nullptr || nodes.size() == 0) - return; - - auto next = nextSibling(); - for (auto &node : nodes) + void CharacterData::after(vector> nodes) { - if (node == nullptr) - continue; + auto parent = parentNode.lock(); + if (parent == nullptr || nodes.size() == 0) + return; - parent->insertBefore(node, next); + auto next = nextSibling(); + for (auto &node : nodes) + { + if (node == nullptr) + continue; + + parent->insertBefore(node, next); + } } - } - void CharacterData::after(string text) - { - after(getOwnerDocumentChecked().createTextNode(text)); + void CharacterData::after(string text) + { + after(getOwnerDocumentChecked().createTextNode(text)); + } } -} +} // namespace endor diff --git a/src/client/dom/character_data.hpp b/src/client/dom/character_data.hpp index 3d51b3a6f..fdeb3233a 100644 --- a/src/client/dom/character_data.hpp +++ b/src/client/dom/character_data.hpp @@ -3,61 +3,64 @@ #include #include "./node.hpp" -namespace dom +namespace endor { - class Element; - class Document; - - class CharacterData : public Node + namespace dom { - public: - CharacterData(pugi::xml_node node, std::shared_ptr ownerDocument); - CharacterData(NodeType nodeType, std::string nodeName, const std::string &data, std::shared_ptr ownerDocument); - CharacterData(const CharacterData &other); - ~CharacterData() = default; + class Element; + class Document; - public: - std::string &data() - { - return data_; - } - const size_t length() const + class CharacterData : public Node { - return data_.length(); - } - std::shared_ptr nextElementSibling(); - std::shared_ptr previousElementSibling(); + public: + CharacterData(pugi::xml_node node, std::shared_ptr ownerDocument); + CharacterData(NodeType nodeType, std::string nodeName, const std::string &data, std::shared_ptr ownerDocument); + CharacterData(const CharacterData &other); + ~CharacterData() = default; - public: - void remove(); - void appendData(const std::string &data); - void deleteData(size_t offset, size_t count); - void insertData(size_t offset, const std::string &data); - void replaceData(size_t offset, size_t count, const std::string &data); - void replaceWith(std::vector> nodes); - std::string substringData(size_t offset, size_t count); - void before(std::vector> nodes); - void before(std::string text); - inline void before(std::shared_ptr node) - { - before(std::vector>{node}); - } - void after(std::vector> nodes); - void after(std::string text); - inline void after(std::shared_ptr node) - { - after(std::vector>{node}); - } + public: + std::string &data() + { + return data_; + } + const size_t length() const + { + return data_.length(); + } + std::shared_ptr nextElementSibling(); + std::shared_ptr previousElementSibling(); - private: - bool isCharacterData() const override final - { - return true; - } + public: + void remove(); + void appendData(const std::string &data); + void deleteData(size_t offset, size_t count); + void insertData(size_t offset, const std::string &data); + void replaceData(size_t offset, size_t count, const std::string &data); + void replaceWith(std::vector> nodes); + std::string substringData(size_t offset, size_t count); + void before(std::vector> nodes); + void before(std::string text); + inline void before(std::shared_ptr node) + { + before(std::vector>{node}); + } + void after(std::vector> nodes); + void after(std::string text); + inline void after(std::shared_ptr node) + { + after(std::vector>{node}); + } + + private: + bool isCharacterData() const override final + { + return true; + } - protected: - std::string data_; - std::weak_ptr nextElementSibling_; - std::weak_ptr previousElementSibling_; - }; -} + protected: + std::string data_; + std::weak_ptr nextElementSibling_; + std::weak_ptr previousElementSibling_; + }; + } +} // namespace endor diff --git a/src/client/dom/comment.cpp b/src/client/dom/comment.cpp index dab718f15..7d793b795 100644 --- a/src/client/dom/comment.cpp +++ b/src/client/dom/comment.cpp @@ -1,11 +1,14 @@ #include "./comment.hpp" -namespace dom +namespace endor { - using namespace std; - - Comment::Comment(const string &content, shared_ptr ownerDocument) - : CharacterData(NodeType::COMMENT_NODE, "#comment", content, ownerDocument) + namespace dom { + using namespace std; + + Comment::Comment(const string &content, shared_ptr ownerDocument) + : CharacterData(NodeType::COMMENT_NODE, "#comment", content, ownerDocument) + { + } } -} +} // namespace endor diff --git a/src/client/dom/comment.hpp b/src/client/dom/comment.hpp index d2b23fbc0..12e1e8248 100644 --- a/src/client/dom/comment.hpp +++ b/src/client/dom/comment.hpp @@ -3,31 +3,34 @@ #include #include "./character_data.hpp" -namespace dom +namespace endor { - class Comment final : public CharacterData + namespace dom { - using CharacterData::CharacterData; - - public: - static std::shared_ptr CreateComment(pugi::xml_node node, std::shared_ptr ownerDocument) - { - return std::make_shared(node, ownerDocument); - } - static std::shared_ptr CloneComment(shared_ptr srcComment) + class Comment final : public CharacterData { - auto commentNode = dynamic_pointer_cast(srcComment); - assert(commentNode != nullptr && "The source node is not a comment node."); - return make_shared(*commentNode); - } + using CharacterData::CharacterData; - public: - Comment(const std::string &content = "", std::shared_ptr ownerDocument = nullptr); + public: + static std::shared_ptr CreateComment(pugi::xml_node node, std::shared_ptr ownerDocument) + { + return std::make_shared(node, ownerDocument); + } + static std::shared_ptr CloneComment(shared_ptr srcComment) + { + auto commentNode = dynamic_pointer_cast(srcComment); + assert(commentNode != nullptr && "The source node is not a comment node."); + return make_shared(*commentNode); + } - public: - bool isComment() const override final - { - return true; - } - }; -} + public: + Comment(const std::string &content = "", std::shared_ptr ownerDocument = nullptr); + + public: + bool isComment() const override final + { + return true; + } + }; + } +} // namespace endor diff --git a/src/client/dom/document-inl.hpp b/src/client/dom/document-inl.hpp index 1569efe2d..2e1d7be44 100644 --- a/src/client/dom/document-inl.hpp +++ b/src/client/dom/document-inl.hpp @@ -2,16 +2,19 @@ #include "./document.hpp" -namespace dom +namespace endor { - template - requires std::is_base_of_v - std::shared_ptr Document::As(std::shared_ptr document) + namespace dom { - if (document != nullptr && - document->documentType == T::kDocumentType) - return std::dynamic_pointer_cast(document); - else - return nullptr; + template + requires std::is_base_of_v + std::shared_ptr Document::As(std::shared_ptr document) + { + if (document != nullptr && + document->documentType == T::kDocumentType) + return std::dynamic_pointer_cast(document); + else + return nullptr; + } } -} +} // namespace endor diff --git a/src/client/dom/document.cpp b/src/client/dom/document.cpp index b6385d48e..071ed6c3d 100644 --- a/src/client/dom/document.cpp +++ b/src/client/dom/document.cpp @@ -14,774 +14,777 @@ #include "./document_renderer.hpp" #include "./browsing_context.hpp" -namespace dom +namespace endor { - using namespace std; - using namespace pugi; - - shared_ptr Document::Make(string contentType, DocumentType documentType, shared_ptr browsingContext, bool autoConnect) - { - return make_shared(contentType, documentType, browsingContext, autoConnect); - } - - Document::Document(string contentType, DocumentType documentType, shared_ptr browsingContext, bool autoConnect) - : Node(NodeType::DOCUMENT_NODE, "#document", nullopt) - , contentType(contentType) - , documentType(documentType) - , scene(TrClientContextPerProcess::Get()->builtinScene) - , browsingContext(browsingContext) - , auto_connect_(autoConnect) - , default_view_(TrClientContextPerProcess::Get()->window) - , timeline_(make_shared()) - { - assert(browsingContext != nullptr); - assert(default_view_.lock() != nullptr); - doc_internal_ = make_shared(); - } - - void Document::setUrl(const string &url) + namespace dom { - if (browsingContext == nullptr) - throw std::runtime_error("BrowsingContext is not set"); + using namespace std; + using namespace pugi; - bool loadSource = false; - if (url.find_first_of("http:") == 0 || - url.find_first_of("https:") == 0 || - url.find_first_of("file:") == 0) + shared_ptr Document::Make(string contentType, DocumentType documentType, shared_ptr browsingContext, bool autoConnect) { - baseURI = url; - loadSource = true; + return make_shared(contentType, documentType, browsingContext, autoConnect); } - else + + Document::Document(string contentType, DocumentType documentType, shared_ptr browsingContext, bool autoConnect) + : Node(NodeType::DOCUMENT_NODE, "#document", nullopt) + , contentType(contentType) + , documentType(documentType) + , scene(TrClientContextPerProcess::Get()->builtinScene) + , browsingContext(browsingContext) + , auto_connect_(autoConnect) + , default_view_(TrClientContextPerProcess::Get()->window) + , timeline_(make_shared()) { - baseURI = "about:blank"; - loadSource = false; - cerr << "Warning: The URL is not a valid HTTP or file URL, using about:blank instead." << endl; - cerr << "The URL: " << url << endl; + assert(browsingContext != nullptr); + assert(default_view_.lock() != nullptr); + doc_internal_ = make_shared(); } - // Set the document URI from the Node interface. - document_uri_ = baseURI; + void Document::setUrl(const string &url) + { + if (browsingContext == nullptr) + throw std::runtime_error("BrowsingContext is not set"); - if (loadSource) - browsingContext->fetchTextSourceResource(url, [this](const string &source) - { setSource(source); }); - else - setSource(""); - } + bool loadSource = false; + if (url.find_first_of("http:") == 0 || + url.find_first_of("https:") == 0 || + url.find_first_of("file:") == 0) + { + baseURI = url; + loadSource = true; + } + else + { + baseURI = "about:blank"; + loadSource = false; + cerr << "Warning: The URL is not a valid HTTP or file URL, using about:blank instead." << endl; + cerr << "The URL: " << url << endl; + } - // This class is used to trace the source code of the document and print the line containing the offset with a - // caret (^) indicating the position. - class SourceTrace - { - public: - SourceTrace(const string &source, size_t offset) - : source(source) - , offset(offset) - { + // Set the document URI from the Node interface. + document_uri_ = baseURI; + + if (loadSource) + browsingContext->fetchTextSourceResource(url, [this](const string &source) + { setSource(source); }); + else + setSource(""); } - friend ostream &operator<<(ostream &os, const SourceTrace &trace) + // This class is used to trace the source code of the document and print the line containing the offset with a + // caret (^) indicating the position. + class SourceTrace { - auto &source = trace.source; - auto &offset = trace.offset; - - // Print the first line - // TODO(yorkie): support filename? - os << "(source):"; - - if (source.empty()) + public: + SourceTrace(const string &source, size_t offset) + : source(source) + , offset(offset) { - // Empty source string, nothing to print. - os << offset << endl - << "^" << endl; - return os; } - // 1. Search for the line containing the offset. - size_t line_num = 0; - size_t line_start = 0; - size_t current = 0; - bool found_line = false; - size_t line_length = 0; - - while (current <= source.length()) + friend ostream &operator<<(ostream &os, const SourceTrace &trace) { - size_t line_end = source.find('\n', current); - if (line_end == string::npos) - line_end = source.length(); - else - line_num++; + auto &source = trace.source; + auto &offset = trace.offset; - line_length = line_end - current; - if (offset >= current && offset <= line_end) + // Print the first line + // TODO(yorkie): support filename? + os << "(source):"; + + if (source.empty()) { - line_start = current; - found_line = true; - break; + // Empty source string, nothing to print. + os << offset << endl + << "^" << endl; + return os; } - if (line_end == source.length()) - break; // String ends, no more lines to check. + // 1. Search for the line containing the offset. + size_t line_num = 0; + size_t line_start = 0; + size_t current = 0; + bool found_line = false; + size_t line_length = 0; - current = line_end + 1; - } + while (current <= source.length()) + { + size_t line_end = source.find('\n', current); + if (line_end == string::npos) + line_end = source.length(); + else + line_num++; - // 2. Extract the line containing the offset. - string line; - if (found_line) - { - line = source.substr(line_start, line_length); - } - else - { - // If the offset is not found, we take the last line of the source. - size_t last_line_start = source.find_last_of('\n'); - if (last_line_start == string::npos) + line_length = line_end - current; + if (offset >= current && offset <= line_end) + { + line_start = current; + found_line = true; + break; + } + + if (line_end == source.length()) + break; // String ends, no more lines to check. + + current = line_end + 1; + } + + // 2. Extract the line containing the offset. + string line; + if (found_line) { - line = source; + line = source.substr(line_start, line_length); } else { - line = source.substr(last_line_start + 1); + // If the offset is not found, we take the last line of the source. + size_t last_line_start = source.find_last_of('\n'); + if (last_line_start == string::npos) + { + line = source; + } + else + { + line = source.substr(last_line_start + 1); + } } + + // 3. Calculate the display column position. + size_t line_offset = (found_line) ? (offset - line_start) : line.length(); + size_t display_col = 0; + + // Calculate the display column position based on tabs and spaces. + for (size_t i = 0; i < line_offset && i < line.length(); ++i) + { + if (line[i] == '\t') + display_col = display_col + (8 - (display_col % 8)); + else + ++display_col; + } + + os << line_num << ":" << (display_col + 1) << endl + << line << endl + << string(display_col, ' ') << '^'; + return os; } - // 3. Calculate the display column position. - size_t line_offset = (found_line) ? (offset - line_start) : line.length(); - size_t display_col = 0; + public: + string_view source; + size_t offset; + }; - // Calculate the display column position based on tabs and spaces. - for (size_t i = 0; i < line_offset && i < line.length(); ++i) + void Document::setSource(const string &source, bool isFragment) + { + string inputText(source); + if (documentType == DocumentType::kHTML) + fixSource(inputText); // Fix the source string if it's an HTML document. + + // Parse the XML document. + auto flag = pugi::parse_default | pugi::parse_ws_pcdata | pugi::parse_comments; + if (isFragment) + flag |= pugi::parse_fragment; + if (documentType == DocumentType::kHTML) + flag |= pugi::parse_unquoted_attributes; + + auto r = doc_internal_->load_string(inputText.c_str(), flag); + if (r.status != pugi::xml_parse_status::status_ok) [[unlikely]] { - if (line[i] == '\t') - display_col = display_col + (8 - (display_col % 8)); - else - ++display_col; + cerr << SourceTrace(inputText, r.offset) << endl + << "SyntaxError: " << r.description() << endl + << string(4, ' ') << "at Document::setSource()" << endl + << endl; } - os << line_num << ":" << (display_col + 1) << endl - << line << endl - << string(display_col, ' ') << '^'; - return os; - } - - public: - string_view source; - size_t offset; - }; + resetFrom(doc_internal_, getPtr()); + is_source_loaded_ = true; - void Document::setSource(const string &source, bool isFragment) - { - string inputText(source); - if (documentType == DocumentType::kHTML) - fixSource(inputText); // Fix the source string if it's an HTML document. + // + // Update fields after the document are parsed. + // - // Parse the XML document. - auto flag = pugi::parse_default | pugi::parse_ws_pcdata | pugi::parse_comments; - if (isFragment) - flag |= pugi::parse_fragment; - if (documentType == DocumentType::kHTML) - flag |= pugi::parse_unquoted_attributes; + // Find the head and body elements. + for (auto childNode : documentElement()->childNodes) + { + if (childNode->nodeType == NodeType::ELEMENT_NODE) + { + if (childNode->nodeName == "head") + head_element_ = dynamic_pointer_cast(childNode); + else if (childNode->nodeName == "body") + body_element_ = dynamic_pointer_cast(childNode); + } + } - auto r = doc_internal_->load_string(inputText.c_str(), flag); - if (r.status != pugi::xml_parse_status::status_ok) [[unlikely]] - { - cerr << SourceTrace(inputText, r.offset) << endl - << "SyntaxError: " << r.description() << endl - << string(4, ' ') << "at Document::setSource()" << endl - << endl; - } + // Clear list and maps. + all_elements_list_.clear(); + element_map_by_id_.clear(); - resetFrom(doc_internal_, getPtr()); - is_source_loaded_ = true; + // Update the element list and maps. + auto initElementsCache = [this](shared_ptr childNode) + { + onNodeAdded(childNode, true, false); + return true; + }; + iterateChildNodes(initElementsCache); - // - // Update fields after the document are parsed. - // + if (should_open_) + openInternal(); + } - // Find the head and body elements. - for (auto childNode : documentElement()->childNodes) + void Document::open() { - if (childNode->nodeType == NodeType::ELEMENT_NODE) - { - if (childNode->nodeName == "head") - head_element_ = dynamic_pointer_cast(childNode); - else if (childNode->nodeName == "body") - body_element_ = dynamic_pointer_cast(childNode); - } + should_open_ = true; + if (is_source_loaded_) + openInternal(); } - // Clear list and maps. - all_elements_list_.clear(); - element_map_by_id_.clear(); - - // Update the element list and maps. - auto initElementsCache = [this](shared_ptr childNode) + std::shared_ptr Document::createDocumentFragment() { - onNodeAdded(childNode, true, false); - return true; - }; - iterateChildNodes(initElementsCache); - - if (should_open_) - openInternal(); - } - - void Document::open() - { - should_open_ = true; - if (is_source_loaded_) - openInternal(); - } + return make_shared(getPtr()); + } - std::shared_ptr Document::createDocumentFragment() - { - return make_shared(getPtr()); - } + std::shared_ptr Document::createElement(const string &localName) + { + return createElementNS("http://www.w3.org/1999/xhtml", localName); + } - std::shared_ptr Document::createElement(const string &localName) - { - return createElementNS("http://www.w3.org/1999/xhtml", localName); - } + std::shared_ptr Document::createElementNS(const string &namespaceURI, const string &qualifiedName) + { + return Element::CreateElement(namespaceURI, qualifiedName, getPtr(), true); + } - std::shared_ptr Document::createElementNS(const string &namespaceURI, const string &qualifiedName) - { - return Element::CreateElement(namespaceURI, qualifiedName, getPtr(), true); - } + std::shared_ptr Document::createTextNode(const string &data) + { + return make_shared(data, getPtr()); + } - std::shared_ptr Document::createTextNode(const string &data) - { - return make_shared(data, getPtr()); - } + std::shared_ptr Document::createComment(const string &data) + { + return make_shared(data, getPtr()); + } - std::shared_ptr Document::createComment(const string &data) - { - return make_shared(data, getPtr()); - } + std::shared_ptr Document::importNode(const std::shared_ptr node, bool deep) + { + if (Node::Is(node)) + throw runtime_error("The node is a document node, which cannot be imported."); - std::shared_ptr Document::importNode(const std::shared_ptr node, bool deep) - { - if (Node::Is(node)) - throw runtime_error("The node is a document node, which cannot be imported."); + auto importedNode = node->cloneNode(deep); + importedNode->updateFieldsFromDocument(getPtr()); + return importedNode; + } - auto importedNode = node->cloneNode(deep); - importedNode->updateFieldsFromDocument(getPtr()); - return importedNode; - } + shared_ptr Document::getElementById(const string &id) + { + auto it = element_map_by_id_.find(id); + if (it != element_map_by_id_.end()) + return it->second; + else + return nullptr; + } - shared_ptr Document::getElementById(const string &id) - { - auto it = element_map_by_id_.find(id); - if (it != element_map_by_id_.end()) - return it->second; - else - return nullptr; - } + std::vector> Document::getElementsByClassName(const string &className) + { + std::vector> elements; + for (auto element : all_elements_list_) + { + if (element->hasAttribute("class")) + { + auto classes = element->getAttribute("class"); + if (classes.find(className) != std::string::npos) + elements.push_back(element); + } + } + return elements; + } - std::vector> Document::getElementsByClassName(const string &className) - { - std::vector> elements; - for (auto element : all_elements_list_) + vector> Document::getElementsByName(const string &name) { - if (element->hasAttribute("class")) + vector> elements; + for (auto element : all_elements_list_) { - auto classes = element->getAttribute("class"); - if (classes.find(className) != std::string::npos) - elements.push_back(element); + if (element->hasAttribute("name")) + { + auto elementName = element->getAttribute("name"); + if (elementName == name) + elements.push_back(element); + } } + return elements; } - return elements; - } - vector> Document::getElementsByName(const string &name) - { - vector> elements; - for (auto element : all_elements_list_) + vector> Document::getElementsByTagName(const string &tagName) { - if (element->hasAttribute("name")) + vector> elements; + for (auto element : all_elements_list_) { - auto elementName = element->getAttribute("name"); - if (elementName == name) + if (element->is(tagName)) elements.push_back(element); } + return elements; } - return elements; - } - vector> Document::getElementsByTagName(const string &tagName) - { - vector> elements; - for (auto element : all_elements_list_) + shared_ptr Document::querySelector(const string &selectors) { - if (element->is(tagName)) - elements.push_back(element); - } - return elements; - } + auto s = client_cssom::selectors::CSSelectorParser::parseSelectors(selectors); + if (s == nullopt) + throw runtime_error("Failed to parse the CSS selectors: " + selectors); - shared_ptr Document::querySelector(const string &selectors) - { - auto s = client_cssom::selectors::CSSelectorParser::parseSelectors(selectors); - if (s == nullopt) - throw runtime_error("Failed to parse the CSS selectors: " + selectors); + auto selectorList = s.value(); + for (const auto &element : all_elements_list_) + { + if (!Node::Is(element)) + continue; + if (client_cssom::selectors::matchesSelectorList(selectorList, Node::As(element))) + return element; + } + return nullptr; + } - auto selectorList = s.value(); - for (const auto &element : all_elements_list_) + NodeList Document::querySelectorAll(const string &selectors) { - if (!Node::Is(element)) - continue; - if (client_cssom::selectors::matchesSelectorList(selectorList, Node::As(element))) - return element; - } - return nullptr; - } + auto s = client_cssom::selectors::CSSelectorParser::parseSelectors(selectors); + if (s == nullopt) + throw runtime_error("Failed to parse the CSS selectors: " + selectors); - NodeList Document::querySelectorAll(const string &selectors) - { - auto s = client_cssom::selectors::CSSelectorParser::parseSelectors(selectors); - if (s == nullopt) - throw runtime_error("Failed to parse the CSS selectors: " + selectors); + NodeList elements(false); + auto selectorList = s.value(); + for (auto element : all_elements_list_) + { + if (!Node::Is(element)) + continue; + if (client_cssom::selectors::matchesSelectorList(selectorList, Node::As(element))) + elements.push_back(element); + } + return elements; + } - NodeList elements(false); - auto selectorList = s.value(); - for (auto element : all_elements_list_) + void Document::appendStyleSheet(shared_ptr sheet) { - if (!Node::Is(element)) - continue; - if (client_cssom::selectors::matchesSelectorList(selectorList, Node::As(element))) - elements.push_back(element); + stylesheets_.push_back(sheet); + onStyleSheetsDidChange(); } - return elements; - } - - void Document::appendStyleSheet(shared_ptr sheet) - { - stylesheets_.push_back(sheet); - onStyleSheetsDidChange(); - } - - void Document::write(const std::string &markup) - { - // TODO(yorkie): implement the write logic. - // This method is used to write the markup into the document. - cerr << "Document::write() is not implemented yet." << endl - << "Markup: " << markup << endl - << "This method is used to write the markup into the document." << endl; - } - void Document::writeln(const std::string &markup) - { - // Write the markup followed by a newline - write(markup + "\n"); - } + void Document::write(const std::string &markup) + { + // TODO(yorkie): implement the write logic. + // This method is used to write the markup into the document. + cerr << "Document::write() is not implemented yet." << endl + << "Markup: " << markup << endl + << "This method is used to write the markup into the document." << endl; + } - void Document::onNodeAdded(const std::shared_ptr node, bool fast_insert, bool recursive) - { - if (TR_UNLIKELY(node == nullptr)) - return; + void Document::writeln(const std::string &markup) + { + // Write the markup followed by a newline + write(markup + "\n"); + } - if (node->isElement()) + void Document::onNodeAdded(const std::shared_ptr node, bool fast_insert, bool recursive) { - shared_ptr element = Node::As(node); + if (TR_UNLIKELY(node == nullptr)) + return; - // Fast inserting elements into the list without checking for duplicates. - // FIXME(yorkie): At initialization, we can ensure that the elements are unique. - if (fast_insert) + if (node->isElement()) { - all_elements_list_.push_back(element); - } - else - { - // Check if element is already in the list to avoid duplicates - auto it = std::find(all_elements_list_.begin(), all_elements_list_.end(), element); - if (it == all_elements_list_.end()) + shared_ptr element = Node::As(node); + + // Fast inserting elements into the list without checking for duplicates. + // FIXME(yorkie): At initialization, we can ensure that the elements are unique. + if (fast_insert) { - // If not, add it to the all elements list all_elements_list_.push_back(element); } - } + else + { + // Check if element is already in the list to avoid duplicates + auto it = std::find(all_elements_list_.begin(), all_elements_list_.end(), element); + if (it == all_elements_list_.end()) + { + // If not, add it to the all elements list + all_elements_list_.push_back(element); + } + } - // Add the element to the element map by id - if (!element->id.empty()) - element_map_by_id_[element->id] = element; + // Add the element to the element map by id + if (!element->id.empty()) + element_map_by_id_[element->id] = element; - // Check if this is a viewport meta element and apply it - auto meta_element = std::dynamic_pointer_cast(element); - if (meta_element && meta_element->isViewportMeta()) + // Check if this is a viewport meta element and apply it + auto meta_element = std::dynamic_pointer_cast(element); + if (meta_element && meta_element->isViewportMeta()) + { + onViewportMetaChanged(meta_element); + } + } + + if (recursive) { - onViewportMetaChanged(meta_element); + // Recursively add child nodes + for (auto childNode : node->childNodes) + onNodeAdded(childNode, fast_insert, true); } } - if (recursive) + void Document::onNodeRemoved(const shared_ptr node, bool recursive) { - // Recursively add child nodes - for (auto childNode : node->childNodes) - onNodeAdded(childNode, fast_insert, true); - } - } + if (TR_UNLIKELY(node == nullptr)) + return; - void Document::onNodeRemoved(const shared_ptr node, bool recursive) - { - if (TR_UNLIKELY(node == nullptr)) - return; + if (node->isElement()) + { + shared_ptr element = Node::As(node); - if (node->isElement()) - { - shared_ptr element = Node::As(node); + // Remove the element from the all elements list + auto it = std::remove(all_elements_list_.begin(), all_elements_list_.end(), element); + if (it != all_elements_list_.end()) + all_elements_list_.erase(it, all_elements_list_.end()); - // Remove the element from the all elements list - auto it = std::remove(all_elements_list_.begin(), all_elements_list_.end(), element); - if (it != all_elements_list_.end()) - all_elements_list_.erase(it, all_elements_list_.end()); + // Remove the element from the element map by id + if (!element->id.empty()) + element_map_by_id_.erase(element->id); + } - // Remove the element from the element map by id - if (!element->id.empty()) - element_map_by_id_.erase(element->id); + if (recursive) + { + // Recursively remove child nodes + for (auto childNode : node->childNodes) + onNodeRemoved(childNode, true); + } } - if (recursive) + void Document::openInternal() { - // Recursively remove child nodes - for (auto childNode : node->childNodes) - onNodeRemoved(childNode, true); + // Connect the window and document before opening this document. + { + auto window = TrClientContextPerProcess::Get()->window; + assert(window != nullptr); + window->configureDocument(getPtr()); + default_view_ = window; + onDocumentOpened(); + } + + // Start connecting the document's children automatically if the flag is set. + if (auto_connect_) + { + connect(); + load(); + } } - } - void Document::openInternal() - { - // Connect the window and document before opening this document. + string &Document::fixSource(string &source) { - auto window = TrClientContextPerProcess::Get()->window; - assert(window != nullptr); - window->configureDocument(getPtr()); - default_view_ = window; - onDocumentOpened(); + string invalidComment = ""; + string defaultComment = ""; // replace the invalid comment with the default comment. + + size_t pos = 0; + while ((pos = source.find(invalidComment, pos)) != string::npos) + { + source.replace(pos, invalidComment.size(), defaultComment); + pos += defaultComment.length(); + } + return source; } - // Start connecting the document's children automatically if the flag is set. - if (auto_connect_) + std::string Document::cookie() const { - connect(); - load(); + std::string cookies_str; + for (const auto &cookie : cookies_) + { + if (!cookies_str.empty()) + cookies_str += "; "; + cookies_str += cookie.first + "=" + cookie.second; + } + return cookies_str; } - } - - string &Document::fixSource(string &source) - { - string invalidComment = ""; - string defaultComment = ""; // replace the invalid comment with the default comment. - size_t pos = 0; - while ((pos = source.find(invalidComment, pos)) != string::npos) + void Document::setCookie(const std::string &new_cookies) { - source.replace(pos, invalidComment.size(), defaultComment); - pos += defaultComment.length(); + // TODO(yorkie): implement the cookie parsing and setting logic. + // See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie } - return source; - } - std::string Document::cookie() const - { - std::string cookies_str; - for (const auto &cookie : cookies_) + shared_ptr Document::firstElementChild() const { - if (!cookies_str.empty()) - cookies_str += "; "; - cookies_str += cookie.first + "=" + cookie.second; + for (auto childNode : childNodes) + { + if (childNode->nodeType == NodeType::ELEMENT_NODE) + return Node::As(childNode); + } + return nullptr; } - return cookies_str; - } - - void Document::setCookie(const std::string &new_cookies) - { - // TODO(yorkie): implement the cookie parsing and setting logic. - // See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie - } - shared_ptr Document::firstElementChild() const - { - for (auto childNode : childNodes) + string XMLDocument::SerializeFragment(const shared_ptr node, bool wellFormed) { - if (childNode->nodeType == NodeType::ELEMENT_NODE) - return Node::As(childNode); + // TODO + return ""; } - return nullptr; - } - string XMLDocument::SerializeFragment(const shared_ptr node, bool wellFormed) - { - // TODO - return ""; - } - - vector> XMLDocument::ParseFragment(const shared_ptr contextElement, - const string &input) - { - throw runtime_error("The XMLDocument::ParseFragment method is not implemented yet."); - } + vector> XMLDocument::ParseFragment(const shared_ptr contextElement, + const string &input) + { + throw runtime_error("The XMLDocument::ParseFragment method is not implemented yet."); + } - XMLDocument::XMLDocument(shared_ptr browsingContext, bool autoConnect) - : Document("text/xml", DocumentType::kXML, browsingContext, autoConnect) - { - } + XMLDocument::XMLDocument(shared_ptr browsingContext, bool autoConnect) + : Document("text/xml", DocumentType::kXML, browsingContext, autoConnect) + { + } - // This follows the fragment serializing algorithm steps. - // - // See https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-serializing-algorithm-steps - string HTMLDocument::SerializeFragment(const shared_ptr node, bool serializableShadowRoots) - { - // 1. If the node serializes as void, then return the empty string. - if (node == nullptr) - return ""; + // This follows the fragment serializing algorithm steps. + // + // See https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-serializing-algorithm-steps + string HTMLDocument::SerializeFragment(const shared_ptr node, bool serializableShadowRoots) + { + // 1. If the node serializes as void, then return the empty string. + if (node == nullptr) + return ""; - // 2. Let `s` be a string, and initialize it to the empty string. - string s(""); + // 2. Let `s` be a string, and initialize it to the empty string. + string s(""); - // 3. If the node is a template element, then let the node instead be the template element's template contents (a DocumentFragment node). - // TODO: