SVGGradientElement.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /*
  2. * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/Bindings/Intrinsics.h>
  7. #include <LibWeb/Bindings/SVGGradientElementPrototype.h>
  8. #include <LibWeb/DOM/Document.h>
  9. #include <LibWeb/Painting/PaintStyle.h>
  10. #include <LibWeb/SVG/AttributeNames.h>
  11. #include <LibWeb/SVG/SVGGradientElement.h>
  12. #include <LibWeb/SVG/SVGGraphicsElement.h>
  13. namespace Web::SVG {
  14. SVGGradientElement::SVGGradientElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  15. : SVGElement(document, move(qualified_name))
  16. {
  17. }
  18. void SVGGradientElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
  19. {
  20. Base::attribute_changed(name, old_value, value, namespace_);
  21. if (name == AttributeNames::gradientUnits) {
  22. m_gradient_units = AttributeParser::parse_units(value.value_or(String {}));
  23. } else if (name == AttributeNames::spreadMethod) {
  24. m_spread_method = AttributeParser::parse_spread_method(value.value_or(String {}));
  25. } else if (name == AttributeNames::gradientTransform) {
  26. if (auto transform_list = AttributeParser::parse_transform(value.value_or(String {})); transform_list.has_value()) {
  27. m_gradient_transform = transform_from_transform_list(*transform_list);
  28. } else {
  29. m_gradient_transform = {};
  30. }
  31. }
  32. }
  33. GradientUnits SVGGradientElement::gradient_units() const
  34. {
  35. HashTable<SVGGradientElement const*> seen_gradients;
  36. return gradient_units_impl(seen_gradients);
  37. }
  38. GradientUnits SVGGradientElement::gradient_units_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
  39. {
  40. if (m_gradient_units.has_value())
  41. return *m_gradient_units;
  42. if (auto gradient = linked_gradient(seen_gradients))
  43. return gradient->gradient_units_impl(seen_gradients);
  44. return GradientUnits::ObjectBoundingBox;
  45. }
  46. SpreadMethod SVGGradientElement::spread_method() const
  47. {
  48. HashTable<SVGGradientElement const*> seen_gradients;
  49. return spread_method_impl(seen_gradients);
  50. }
  51. SpreadMethod SVGGradientElement::spread_method_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
  52. {
  53. if (m_spread_method.has_value())
  54. return *m_spread_method;
  55. if (auto gradient = linked_gradient(seen_gradients))
  56. return gradient->spread_method_impl(seen_gradients);
  57. return SpreadMethod::Pad;
  58. }
  59. Optional<Gfx::AffineTransform> SVGGradientElement::gradient_transform() const
  60. {
  61. HashTable<SVGGradientElement const*> seen_gradients;
  62. return gradient_transform_impl(seen_gradients);
  63. }
  64. Optional<Gfx::AffineTransform> SVGGradientElement::gradient_transform_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
  65. {
  66. if (m_gradient_transform.has_value())
  67. return m_gradient_transform;
  68. if (auto gradient = linked_gradient(seen_gradients))
  69. return gradient->gradient_transform_impl(seen_gradients);
  70. return {};
  71. }
  72. // The gradient transform, appropriately scaled and combined with the paint transform.
  73. Gfx::AffineTransform SVGGradientElement::gradient_paint_transform(SVGPaintContext const& paint_context) const
  74. {
  75. Gfx::AffineTransform gradient_paint_transform = paint_context.paint_transform;
  76. auto const& bounding_box = paint_context.path_bounding_box;
  77. if (gradient_units() == SVGUnits::ObjectBoundingBox) {
  78. // Scale points from 0..1 to bounding box coordinates:
  79. gradient_paint_transform.scale(bounding_box.width(), bounding_box.height());
  80. } else {
  81. // Translate points from viewport to bounding box coordinates:
  82. gradient_paint_transform.translate(paint_context.viewport.location() - bounding_box.location());
  83. }
  84. if (auto transform = gradient_transform(); transform.has_value())
  85. gradient_paint_transform.multiply(transform.value());
  86. return gradient_paint_transform;
  87. }
  88. void SVGGradientElement::add_color_stops(Painting::SVGGradientPaintStyle& paint_style) const
  89. {
  90. auto largest_offset = 0.0f;
  91. for_each_color_stop([&](auto& stop) {
  92. // https://svgwg.org/svg2-draft/pservers.html#StopNotes
  93. // Gradient offset values less than 0 (or less than 0%) are rounded up to 0%.
  94. // Gradient offset values greater than 1 (or greater than 100%) are rounded down to 100%.
  95. float stop_offset = AK::clamp(stop.stop_offset().value(), 0.0f, 1.0f);
  96. // Each gradient offset value is required to be equal to or greater than the previous gradient
  97. // stop's offset value. If a given gradient stop's offset value is not equal to or greater than all
  98. // previous offset values, then the offset value is adjusted to be equal to the largest of all previous
  99. // offset values.
  100. stop_offset = AK::max(stop_offset, largest_offset);
  101. largest_offset = stop_offset;
  102. paint_style.add_color_stop(stop_offset, stop.stop_color().with_opacity(stop.stop_opacity()));
  103. });
  104. }
  105. GC::Ptr<SVGGradientElement const> SVGGradientElement::linked_gradient(HashTable<SVGGradientElement const*>& seen_gradients) const
  106. {
  107. // FIXME: This entire function is an ad-hoc hack!
  108. // It can only resolve #<ids> in the same document.
  109. auto link = has_attribute(AttributeNames::href) ? get_attribute(AttributeNames::href) : get_attribute("xlink:href"_fly_string);
  110. if (auto href = link; href.has_value() && !link->is_empty()) {
  111. auto url = document().encoding_parse_url(*href);
  112. auto id = url.fragment();
  113. if (!id.has_value() || id->is_empty())
  114. return {};
  115. auto element = document().get_element_by_id(id.value());
  116. if (!element)
  117. return {};
  118. if (element == this)
  119. return {};
  120. if (!is<SVGGradientElement>(*element))
  121. return {};
  122. if (seen_gradients.set(&verify_cast<SVGGradientElement>(*element)) != AK::HashSetResult::InsertedNewEntry)
  123. return {};
  124. return &verify_cast<SVGGradientElement>(*element);
  125. }
  126. return {};
  127. }
  128. void SVGGradientElement::initialize(JS::Realm& realm)
  129. {
  130. Base::initialize(realm);
  131. WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGGradientElement);
  132. }
  133. void SVGGradientElement::visit_edges(Cell::Visitor& visitor)
  134. {
  135. Base::visit_edges(visitor);
  136. SVGURIReferenceMixin::visit_edges(visitor);
  137. }
  138. }