/* * Copyright (c) 2026, Callum Law * * SPDX-License-Identifier: BSD-2-Clause */ #include "CSSFontFeatureValuesMap.h" #include #include #include #include namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSFontFeatureValuesMap); GC::Ref CSSFontFeatureValuesMap::create(JS::Realm& realm, size_t max_value_count, GC::Ref parent_rule) { return realm.create(realm, max_value_count, parent_rule); } CSSFontFeatureValuesMap::CSSFontFeatureValuesMap(JS::Realm& realm, size_t max_value_count, GC::Ref parent_rule) : Bindings::PlatformObject(realm) , m_map_entries(JS::Map::create(realm)) , m_max_value_count(max_value_count) , m_parent_rule(parent_rule) { } WebIDL::ExceptionOr CSSFontFeatureValuesMap::set(String const& feature_value_name, Variant> const& values) { // https://drafts.csswg.org/css-fonts-4/#cssfontfeaturevaluesmap // The CSSFontFeatureValuesMap interface uses the default map class methods but the set method has different // behavior. It takes a sequence of unsigned integers and associates it with a given featureValueName. The method // behaves the same as the default map class method except that // a single unsigned long value is treated as a sequence of a single value. Vector value_vector = values.visit( [](u32 single_value) { return Vector { single_value }; }, [](Vector value_vector) { return value_vector; }); // The method throws an exception if an invalid number of values is passed in. if (value_vector.is_empty()) return WebIDL::InvalidAccessError::create(realm(), "CSSFontFeatureValuesMap.set requires at least one value."_utf16); // If the associated feature value block only allows a limited number of values, the set method throws an // InvalidAccessError exception when the input sequence to set contains more than the limited number of values. See // the description of multi-valued feature value definitions for details on the maximum number of values allowed for // a given type of feature value block. if (value_vector.size() > m_max_value_count) return WebIDL::InvalidAccessError::create(realm(), Utf16String::formatted("CSSFontFeatureValuesMap.set only allows a maximum of {} values for the associated feature", m_max_value_count)); Vector wrapped_values; wrapped_values.ensure_capacity(value_vector.size()); for (auto const& value : value_vector) wrapped_values.append(JS::Value { value }); m_map_entries->map_set(JS::PrimitiveString::create(vm(), feature_value_name), JS::Array::create_from(realm(), wrapped_values.span())); m_parent_rule->clear_caches(); return {}; } void CSSFontFeatureValuesMap::on_map_modified_from_js(Badge) { m_parent_rule->clear_caches(); } OrderedHashMap> CSSFontFeatureValuesMap::to_ordered_hash_map() const { OrderedHashMap> result; for (auto const& entry : *m_map_entries) { auto key = MUST(entry.key.to_string(vm())); auto const& array = as(entry.value.as_object()); auto array_length = MUST(MUST(array.get(vm().names.length)).to_length(vm())); Vector values; values.ensure_capacity(array_length); for (size_t i = 0; i < array_length; ++i) values.append(MUST(array.get_without_side_effects(JS::PropertyKey { i }).to_u32(vm()))); result.set(key, values); } return result; } void CSSFontFeatureValuesMap::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSFontFeatureValuesMap); Base::initialize(realm); } void CSSFontFeatureValuesMap::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_map_entries); visitor.visit(m_parent_rule); } }