StyleProperties.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065
  1. /*
  2. * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/TypeCasts.h>
  8. #include <LibCore/DirIterator.h>
  9. #include <LibWeb/CSS/Clip.h>
  10. #include <LibWeb/CSS/StyleProperties.h>
  11. #include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
  12. #include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
  13. #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
  14. #include <LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h>
  15. #include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
  16. #include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
  17. #include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
  18. #include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h>
  19. #include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
  20. #include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
  21. #include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
  22. #include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
  23. #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
  24. #include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
  25. #include <LibWeb/CSS/StyleValues/RectStyleValue.h>
  26. #include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
  27. #include <LibWeb/CSS/StyleValues/StringStyleValue.h>
  28. #include <LibWeb/CSS/StyleValues/StyleValueList.h>
  29. #include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
  30. #include <LibWeb/Layout/BlockContainer.h>
  31. #include <LibWeb/Layout/Node.h>
  32. #include <LibWeb/Platform/FontPlugin.h>
  33. namespace Web::CSS {
  34. void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<StyleValue const> value, CSS::CSSStyleDeclaration const* source_declaration)
  35. {
  36. m_property_values[to_underlying(id)] = StyleAndSourceDeclaration { move(value), source_declaration };
  37. }
  38. NonnullRefPtr<StyleValue const> StyleProperties::property(CSS::PropertyID property_id) const
  39. {
  40. auto value = m_property_values[to_underlying(property_id)];
  41. // By the time we call this method, all properties have values assigned.
  42. VERIFY(value.has_value());
  43. return value->style;
  44. }
  45. RefPtr<StyleValue const> StyleProperties::maybe_null_property(CSS::PropertyID property_id) const
  46. {
  47. auto value = m_property_values[to_underlying(property_id)];
  48. if (value.has_value())
  49. return value->style;
  50. return {};
  51. }
  52. CSS::CSSStyleDeclaration const* StyleProperties::property_source_declaration(CSS::PropertyID property_id) const
  53. {
  54. return m_property_values[to_underlying(property_id)].map([](auto& value) { return value.declaration; }).value_or(nullptr);
  55. }
  56. CSS::Size StyleProperties::size_value(CSS::PropertyID id) const
  57. {
  58. auto value = property(id);
  59. if (value->is_identifier()) {
  60. switch (value->to_identifier()) {
  61. case ValueID::Auto:
  62. return CSS::Size::make_auto();
  63. case ValueID::MinContent:
  64. return CSS::Size::make_min_content();
  65. case ValueID::MaxContent:
  66. return CSS::Size::make_max_content();
  67. case ValueID::FitContent:
  68. return CSS::Size::make_fit_content();
  69. case ValueID::None:
  70. return CSS::Size::make_none();
  71. default:
  72. VERIFY_NOT_REACHED();
  73. }
  74. }
  75. if (value->is_calculated())
  76. return CSS::Size::make_calculated(const_cast<CalculatedStyleValue&>(value->as_calculated()));
  77. if (value->is_percentage())
  78. return CSS::Size::make_percentage(value->as_percentage().percentage());
  79. if (value->is_length()) {
  80. auto length = value->as_length().length();
  81. if (length.is_auto())
  82. return CSS::Size::make_auto();
  83. return CSS::Size::make_length(length);
  84. }
  85. // FIXME: Support `fit-content(<length>)`
  86. dbgln("FIXME: Unsupported size value: `{}`, treating as `auto`", value->to_string());
  87. return CSS::Size::make_auto();
  88. }
  89. LengthPercentage StyleProperties::length_percentage_or_fallback(CSS::PropertyID id, LengthPercentage const& fallback) const
  90. {
  91. return length_percentage(id).value_or(fallback);
  92. }
  93. Optional<LengthPercentage> StyleProperties::length_percentage(CSS::PropertyID id) const
  94. {
  95. auto value = property(id);
  96. if (value->is_calculated())
  97. return LengthPercentage { const_cast<CalculatedStyleValue&>(value->as_calculated()) };
  98. if (value->is_percentage())
  99. return value->as_percentage().percentage();
  100. if (value->is_length())
  101. return value->as_length().length();
  102. if (value->has_auto())
  103. return LengthPercentage { Length::make_auto() };
  104. return {};
  105. }
  106. LengthBox StyleProperties::length_box(CSS::PropertyID left_id, CSS::PropertyID top_id, CSS::PropertyID right_id, CSS::PropertyID bottom_id, const CSS::Length& default_value) const
  107. {
  108. LengthBox box;
  109. box.left() = length_percentage_or_fallback(left_id, default_value);
  110. box.top() = length_percentage_or_fallback(top_id, default_value);
  111. box.right() = length_percentage_or_fallback(right_id, default_value);
  112. box.bottom() = length_percentage_or_fallback(bottom_id, default_value);
  113. return box;
  114. }
  115. Color StyleProperties::color_or_fallback(CSS::PropertyID id, Layout::NodeWithStyle const& node, Color fallback) const
  116. {
  117. auto value = property(id);
  118. if (!value->has_color())
  119. return fallback;
  120. return value->to_color(node);
  121. }
  122. NonnullRefPtr<Gfx::Font const> StyleProperties::font_fallback(bool monospace, bool bold)
  123. {
  124. if (monospace && bold)
  125. return Platform::FontPlugin::the().default_fixed_width_font().bold_variant();
  126. if (monospace)
  127. return Platform::FontPlugin::the().default_fixed_width_font();
  128. if (bold)
  129. return Platform::FontPlugin::the().default_font().bold_variant();
  130. return Platform::FontPlugin::the().default_font();
  131. }
  132. // FIXME: This implementation is almost identical to line_height(Layout::Node) below. Maybe they can be combined somehow.
  133. CSSPixels StyleProperties::line_height(CSSPixelRect const& viewport_rect, Length::FontMetrics const& font_metrics, Length::FontMetrics const& root_font_metrics) const
  134. {
  135. auto line_height = property(CSS::PropertyID::LineHeight);
  136. if (line_height->is_identifier() && line_height->to_identifier() == ValueID::Normal)
  137. return font_metrics.line_height;
  138. if (line_height->is_length()) {
  139. auto line_height_length = line_height->as_length().length();
  140. if (!line_height_length.is_auto())
  141. return line_height_length.to_px(viewport_rect, font_metrics, root_font_metrics);
  142. }
  143. if (line_height->is_number())
  144. return Length(line_height->as_number().number(), Length::Type::Em).to_px(viewport_rect, font_metrics, root_font_metrics);
  145. if (line_height->is_percentage()) {
  146. // Percentages are relative to 1em. https://www.w3.org/TR/css-inline-3/#valdef-line-height-percentage
  147. auto& percentage = line_height->as_percentage().percentage();
  148. return Length(percentage.as_fraction(), Length::Type::Em).to_px(viewport_rect, font_metrics, root_font_metrics);
  149. }
  150. if (line_height->is_calculated()) {
  151. // FIXME: Handle `line-height: calc(...)` despite not having a LayoutNode here.
  152. return font_metrics.line_height;
  153. }
  154. return font_metrics.line_height;
  155. }
  156. CSSPixels StyleProperties::line_height(Layout::Node const& layout_node) const
  157. {
  158. auto line_height = property(CSS::PropertyID::LineHeight);
  159. if (line_height->is_identifier() && line_height->to_identifier() == ValueID::Normal)
  160. return CSSPixels::nearest_value_for(layout_node.first_available_font().pixel_metrics().line_spacing());
  161. if (line_height->is_length()) {
  162. auto line_height_length = line_height->as_length().length();
  163. if (!line_height_length.is_auto())
  164. return line_height_length.to_px(layout_node);
  165. }
  166. if (line_height->is_number())
  167. return Length(line_height->as_number().number(), Length::Type::Em).to_px(layout_node);
  168. if (line_height->is_percentage()) {
  169. // Percentages are relative to 1em. https://www.w3.org/TR/css-inline-3/#valdef-line-height-percentage
  170. auto& percentage = line_height->as_percentage().percentage();
  171. return Length(percentage.as_fraction(), Length::Type::Em).to_px(layout_node);
  172. }
  173. if (line_height->is_calculated()) {
  174. if (line_height->as_calculated().resolves_to_number()) {
  175. auto resolved = line_height->as_calculated().resolve_number();
  176. if (!resolved.has_value()) {
  177. dbgln("FIXME: Failed to resolve calc() line-height (number): {}", line_height->as_calculated().to_string());
  178. return CSSPixels::nearest_value_for(layout_node.first_available_font().pixel_metrics().line_spacing());
  179. }
  180. return Length(resolved.value(), Length::Type::Em).to_px(layout_node);
  181. }
  182. auto resolved = line_height->as_calculated().resolve_length(layout_node);
  183. if (!resolved.has_value()) {
  184. dbgln("FIXME: Failed to resolve calc() line-height: {}", line_height->as_calculated().to_string());
  185. return CSSPixels::nearest_value_for(layout_node.first_available_font().pixel_metrics().line_spacing());
  186. }
  187. return resolved->to_px(layout_node);
  188. }
  189. return CSSPixels::nearest_value_for(layout_node.first_available_font().pixel_metrics().line_spacing());
  190. }
  191. Optional<int> StyleProperties::z_index() const
  192. {
  193. auto value = property(CSS::PropertyID::ZIndex);
  194. if (value->has_auto())
  195. return {};
  196. if (value->is_integer()) {
  197. // Clamp z-index to the range of a signed 32-bit integer for consistency with other engines.
  198. auto integer = value->as_integer().integer();
  199. if (integer >= NumericLimits<int>::max())
  200. return NumericLimits<int>::max();
  201. if (integer <= NumericLimits<int>::min())
  202. return NumericLimits<int>::min();
  203. return static_cast<int>(integer);
  204. }
  205. return {};
  206. }
  207. static float resolve_opacity_value(CSS::StyleValue const& value)
  208. {
  209. float unclamped_opacity = 1.0f;
  210. if (value.is_number()) {
  211. unclamped_opacity = value.as_number().number();
  212. } else if (value.is_calculated()) {
  213. auto& calculated = value.as_calculated();
  214. if (calculated.resolves_to_percentage()) {
  215. auto maybe_percentage = value.as_calculated().resolve_percentage();
  216. if (maybe_percentage.has_value())
  217. unclamped_opacity = maybe_percentage->as_fraction();
  218. else
  219. dbgln("Unable to resolve calc() as opacity (percentage): {}", value.to_string());
  220. } else if (calculated.resolves_to_number()) {
  221. auto maybe_number = const_cast<CalculatedStyleValue&>(value.as_calculated()).resolve_number();
  222. if (maybe_number.has_value())
  223. unclamped_opacity = maybe_number.value();
  224. else
  225. dbgln("Unable to resolve calc() as opacity (number): {}", value.to_string());
  226. }
  227. } else if (value.is_percentage()) {
  228. unclamped_opacity = value.as_percentage().percentage().as_fraction();
  229. }
  230. return clamp(unclamped_opacity, 0.0f, 1.0f);
  231. }
  232. float StyleProperties::opacity() const
  233. {
  234. auto value = property(CSS::PropertyID::Opacity);
  235. return resolve_opacity_value(*value);
  236. }
  237. float StyleProperties::fill_opacity() const
  238. {
  239. auto value = property(CSS::PropertyID::FillOpacity);
  240. return resolve_opacity_value(*value);
  241. }
  242. float StyleProperties::stroke_opacity() const
  243. {
  244. auto value = property(CSS::PropertyID::StrokeOpacity);
  245. return resolve_opacity_value(*value);
  246. }
  247. float StyleProperties::stop_opacity() const
  248. {
  249. auto value = property(CSS::PropertyID::StopOpacity);
  250. return resolve_opacity_value(*value);
  251. }
  252. Optional<CSS::FillRule> StyleProperties::fill_rule() const
  253. {
  254. auto value = property(CSS::PropertyID::FillRule);
  255. return value_id_to_fill_rule(value->to_identifier());
  256. }
  257. Optional<CSS::FlexDirection> StyleProperties::flex_direction() const
  258. {
  259. auto value = property(CSS::PropertyID::FlexDirection);
  260. return value_id_to_flex_direction(value->to_identifier());
  261. }
  262. Optional<CSS::FlexWrap> StyleProperties::flex_wrap() const
  263. {
  264. auto value = property(CSS::PropertyID::FlexWrap);
  265. return value_id_to_flex_wrap(value->to_identifier());
  266. }
  267. Optional<CSS::FlexBasis> StyleProperties::flex_basis() const
  268. {
  269. auto value = property(CSS::PropertyID::FlexBasis);
  270. if (value->is_identifier() && value->to_identifier() == CSS::ValueID::Content)
  271. return CSS::FlexBasisContent {};
  272. return size_value(CSS::PropertyID::FlexBasis);
  273. }
  274. float StyleProperties::flex_grow() const
  275. {
  276. auto value = property(CSS::PropertyID::FlexGrow);
  277. if (!value->is_number())
  278. return 0;
  279. return value->as_number().number();
  280. }
  281. float StyleProperties::flex_shrink() const
  282. {
  283. auto value = property(CSS::PropertyID::FlexShrink);
  284. if (!value->is_number())
  285. return 1;
  286. return value->as_number().number();
  287. }
  288. int StyleProperties::order() const
  289. {
  290. auto value = property(CSS::PropertyID::Order);
  291. if (!value->is_integer())
  292. return 0;
  293. return value->as_integer().integer();
  294. }
  295. Optional<CSS::ImageRendering> StyleProperties::image_rendering() const
  296. {
  297. auto value = property(CSS::PropertyID::ImageRendering);
  298. return value_id_to_image_rendering(value->to_identifier());
  299. }
  300. CSS::Length StyleProperties::border_spacing_horizontal() const
  301. {
  302. auto value = property(CSS::PropertyID::BorderSpacing);
  303. if (value->is_length())
  304. return value->as_length().length();
  305. auto const& list = value->as_value_list();
  306. return list.value_at(0, false)->as_length().length();
  307. }
  308. CSS::Length StyleProperties::border_spacing_vertical() const
  309. {
  310. auto value = property(CSS::PropertyID::BorderSpacing);
  311. if (value->is_length())
  312. return value->as_length().length();
  313. auto const& list = value->as_value_list();
  314. return list.value_at(1, false)->as_length().length();
  315. }
  316. Optional<CSS::CaptionSide> StyleProperties::caption_side() const
  317. {
  318. auto value = property(CSS::PropertyID::CaptionSide);
  319. return value_id_to_caption_side(value->to_identifier());
  320. }
  321. CSS::Clip StyleProperties::clip() const
  322. {
  323. auto value = property(CSS::PropertyID::Clip);
  324. if (!value->is_rect())
  325. return CSS::Clip::make_auto();
  326. return CSS::Clip(value->as_rect().rect());
  327. }
  328. Optional<CSS::JustifyContent> StyleProperties::justify_content() const
  329. {
  330. auto value = property(CSS::PropertyID::JustifyContent);
  331. return value_id_to_justify_content(value->to_identifier());
  332. }
  333. Optional<CSS::JustifyItems> StyleProperties::justify_items() const
  334. {
  335. auto value = property(CSS::PropertyID::JustifyItems);
  336. return value_id_to_justify_items(value->to_identifier());
  337. }
  338. Optional<CSS::JustifySelf> StyleProperties::justify_self() const
  339. {
  340. auto value = property(CSS::PropertyID::JustifySelf);
  341. return value_id_to_justify_self(value->to_identifier());
  342. }
  343. Vector<CSS::Transformation> StyleProperties::transformations() const
  344. {
  345. auto value = property(CSS::PropertyID::Transform);
  346. if (value->is_identifier() && value->to_identifier() == CSS::ValueID::None)
  347. return {};
  348. if (!value->is_value_list())
  349. return {};
  350. auto& list = value->as_value_list();
  351. Vector<CSS::Transformation> transformations;
  352. for (auto& it : list.values()) {
  353. if (!it->is_transformation())
  354. return {};
  355. auto& transformation_style_value = it->as_transformation();
  356. auto function = transformation_style_value.transform_function();
  357. Vector<TransformValue> values;
  358. for (auto& transformation_value : transformation_style_value.values()) {
  359. if (transformation_value->is_calculated()) {
  360. auto& calculated = transformation_value->as_calculated();
  361. if (calculated.resolves_to_length()) {
  362. values.append(CSS::LengthPercentage { calculated });
  363. } else if (calculated.resolves_to_percentage()) {
  364. values.append({ calculated.resolve_percentage().value() });
  365. } else if (calculated.resolves_to_number()) {
  366. values.append({ calculated.resolve_number().value() });
  367. } else if (calculated.resolves_to_angle()) {
  368. values.append({ calculated.resolve_angle().value() });
  369. } else {
  370. dbgln("FIXME: Unsupported calc value in transform! {}", calculated.to_string());
  371. }
  372. } else if (transformation_value->is_length()) {
  373. values.append({ transformation_value->as_length().length() });
  374. } else if (transformation_value->is_percentage()) {
  375. values.append({ transformation_value->as_percentage().percentage() });
  376. } else if (transformation_value->is_number()) {
  377. values.append({ transformation_value->as_number().number() });
  378. } else if (transformation_value->is_angle()) {
  379. values.append({ transformation_value->as_angle().angle() });
  380. } else {
  381. dbgln("FIXME: Unsupported value in transform! {}", transformation_value->to_string());
  382. }
  383. }
  384. transformations.empend(function, move(values));
  385. }
  386. return transformations;
  387. }
  388. static Optional<LengthPercentage> length_percentage_for_style_value(StyleValue const& value)
  389. {
  390. if (value.is_length())
  391. return value.as_length().length();
  392. if (value.is_percentage())
  393. return value.as_percentage().percentage();
  394. return {};
  395. }
  396. CSS::TransformOrigin StyleProperties::transform_origin() const
  397. {
  398. auto value = property(CSS::PropertyID::TransformOrigin);
  399. if (!value->is_value_list() || value->as_value_list().size() != 2)
  400. return {};
  401. auto const& list = value->as_value_list();
  402. auto x_value = length_percentage_for_style_value(list.values()[0]);
  403. auto y_value = length_percentage_for_style_value(list.values()[1]);
  404. if (!x_value.has_value() || !y_value.has_value()) {
  405. return {};
  406. }
  407. return { x_value.value(), y_value.value() };
  408. }
  409. Optional<Color> StyleProperties::accent_color(Layout::NodeWithStyle const& node) const
  410. {
  411. auto value = property(CSS::PropertyID::AccentColor);
  412. if (value->has_color())
  413. return value->to_color(node);
  414. return {};
  415. }
  416. Optional<CSS::AlignContent> StyleProperties::align_content() const
  417. {
  418. auto value = property(CSS::PropertyID::AlignContent);
  419. return value_id_to_align_content(value->to_identifier());
  420. }
  421. Optional<CSS::AlignItems> StyleProperties::align_items() const
  422. {
  423. auto value = property(CSS::PropertyID::AlignItems);
  424. return value_id_to_align_items(value->to_identifier());
  425. }
  426. Optional<CSS::AlignSelf> StyleProperties::align_self() const
  427. {
  428. auto value = property(CSS::PropertyID::AlignSelf);
  429. return value_id_to_align_self(value->to_identifier());
  430. }
  431. Optional<CSS::Appearance> StyleProperties::appearance() const
  432. {
  433. auto value = property(CSS::PropertyID::Appearance);
  434. auto appearance = value_id_to_appearance(value->to_identifier());
  435. if (appearance.has_value()) {
  436. switch (*appearance) {
  437. // Note: All these compatibility values can be treated as 'auto'
  438. case CSS::Appearance::Textfield:
  439. case CSS::Appearance::MenulistButton:
  440. case CSS::Appearance::Searchfield:
  441. case CSS::Appearance::Textarea:
  442. case CSS::Appearance::PushButton:
  443. case CSS::Appearance::SliderHorizontal:
  444. case CSS::Appearance::Checkbox:
  445. case CSS::Appearance::Radio:
  446. case CSS::Appearance::SquareButton:
  447. case CSS::Appearance::Menulist:
  448. case CSS::Appearance::Listbox:
  449. case CSS::Appearance::Meter:
  450. case CSS::Appearance::ProgressBar:
  451. case CSS::Appearance::Button:
  452. appearance = CSS::Appearance::Auto;
  453. break;
  454. default:
  455. break;
  456. }
  457. }
  458. return appearance;
  459. }
  460. CSS::BackdropFilter StyleProperties::backdrop_filter() const
  461. {
  462. auto value = property(CSS::PropertyID::BackdropFilter);
  463. if (value->is_filter_value_list())
  464. return BackdropFilter(value->as_filter_value_list());
  465. return BackdropFilter::make_none();
  466. }
  467. Optional<CSS::Positioning> StyleProperties::position() const
  468. {
  469. auto value = property(CSS::PropertyID::Position);
  470. return value_id_to_positioning(value->to_identifier());
  471. }
  472. bool StyleProperties::operator==(StyleProperties const& other) const
  473. {
  474. if (m_property_values.size() != other.m_property_values.size())
  475. return false;
  476. for (size_t i = 0; i < m_property_values.size(); ++i) {
  477. auto const& my_style = m_property_values[i];
  478. auto const& other_style = other.m_property_values[i];
  479. if (!my_style.has_value()) {
  480. if (other_style.has_value())
  481. return false;
  482. continue;
  483. }
  484. if (!other_style.has_value())
  485. return false;
  486. auto const& my_value = *my_style->style;
  487. auto const& other_value = *other_style->style;
  488. if (my_value.type() != other_value.type())
  489. return false;
  490. if (my_value != other_value)
  491. return false;
  492. }
  493. return true;
  494. }
  495. Optional<CSS::TextAnchor> StyleProperties::text_anchor() const
  496. {
  497. auto value = property(CSS::PropertyID::TextAnchor);
  498. return value_id_to_text_anchor(value->to_identifier());
  499. }
  500. Optional<CSS::TextAlign> StyleProperties::text_align() const
  501. {
  502. auto value = property(CSS::PropertyID::TextAlign);
  503. return value_id_to_text_align(value->to_identifier());
  504. }
  505. Optional<CSS::TextJustify> StyleProperties::text_justify() const
  506. {
  507. auto value = property(CSS::PropertyID::TextJustify);
  508. return value_id_to_text_justify(value->to_identifier());
  509. }
  510. Optional<CSS::PointerEvents> StyleProperties::pointer_events() const
  511. {
  512. auto value = property(CSS::PropertyID::PointerEvents);
  513. return value_id_to_pointer_events(value->to_identifier());
  514. }
  515. Optional<CSS::WhiteSpace> StyleProperties::white_space() const
  516. {
  517. auto value = property(CSS::PropertyID::WhiteSpace);
  518. return value_id_to_white_space(value->to_identifier());
  519. }
  520. Optional<CSS::LineStyle> StyleProperties::line_style(CSS::PropertyID property_id) const
  521. {
  522. auto value = property(property_id);
  523. return value_id_to_line_style(value->to_identifier());
  524. }
  525. Optional<CSS::OutlineStyle> StyleProperties::outline_style() const
  526. {
  527. auto value = property(CSS::PropertyID::OutlineStyle);
  528. return value_id_to_outline_style(value->to_identifier());
  529. }
  530. Optional<CSS::Float> StyleProperties::float_() const
  531. {
  532. auto value = property(CSS::PropertyID::Float);
  533. return value_id_to_float(value->to_identifier());
  534. }
  535. Optional<CSS::Clear> StyleProperties::clear() const
  536. {
  537. auto value = property(CSS::PropertyID::Clear);
  538. return value_id_to_clear(value->to_identifier());
  539. }
  540. StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(u32 initial_quote_nesting_level) const
  541. {
  542. auto value = property(CSS::PropertyID::Content);
  543. auto quotes_data = quotes();
  544. auto quote_nesting_level = initial_quote_nesting_level;
  545. auto get_quote_string = [&](bool open, auto depth) {
  546. switch (quotes_data.type) {
  547. case QuotesData::Type::None:
  548. return String {};
  549. case QuotesData::Type::Auto:
  550. // FIXME: "A typographically appropriate used value for quotes is automatically chosen by the UA
  551. // based on the content language of the element and/or its parent."
  552. if (open)
  553. return depth == 0 ? "“"_string : "‘"_string;
  554. return depth == 0 ? "”"_string : "’"_string;
  555. case QuotesData::Type::Specified:
  556. // If the depth is greater than the number of pairs, the last pair is repeated.
  557. auto& level = quotes_data.strings[min(depth, quotes_data.strings.size() - 1)];
  558. return open ? level[0] : level[1];
  559. }
  560. VERIFY_NOT_REACHED();
  561. };
  562. if (value->is_content()) {
  563. auto& content_style_value = value->as_content();
  564. CSS::ContentData content_data;
  565. // FIXME: The content is a list of things: strings, identifiers or functions that return strings, and images.
  566. // So it can't always be represented as a single String, but may have to be multiple boxes.
  567. // For now, we'll just assume strings since that is easiest.
  568. StringBuilder builder;
  569. for (auto const& item : content_style_value.content().values()) {
  570. if (item->is_string()) {
  571. builder.append(item->as_string().string_value());
  572. } else if (item->is_identifier()) {
  573. switch (item->to_identifier()) {
  574. case ValueID::OpenQuote:
  575. builder.append(get_quote_string(true, quote_nesting_level++));
  576. break;
  577. case ValueID::CloseQuote:
  578. // A 'close-quote' or 'no-close-quote' that would make the depth negative is in error and is ignored
  579. // (at rendering time): the depth stays at 0 and no quote mark is rendered (although the rest of the
  580. // 'content' property's value is still inserted).
  581. // - https://www.w3.org/TR/CSS21/generate.html#quotes-insert
  582. // (This is missing from the CONTENT-3 spec.)
  583. if (quote_nesting_level > 0)
  584. builder.append(get_quote_string(false, --quote_nesting_level));
  585. break;
  586. case ValueID::NoOpenQuote:
  587. quote_nesting_level++;
  588. break;
  589. case ValueID::NoCloseQuote:
  590. // NOTE: See CloseQuote
  591. if (quote_nesting_level > 0)
  592. quote_nesting_level--;
  593. break;
  594. default:
  595. dbgln("`{}` is not supported in `content` (yet?)", item->to_string());
  596. break;
  597. }
  598. } else {
  599. // TODO: Implement counters, images, and other things.
  600. dbgln("`{}` is not supported in `content` (yet?)", item->to_string());
  601. }
  602. }
  603. content_data.type = ContentData::Type::String;
  604. content_data.data = MUST(builder.to_string());
  605. if (content_style_value.has_alt_text()) {
  606. StringBuilder alt_text_builder;
  607. for (auto const& item : content_style_value.alt_text()->values()) {
  608. if (item->is_string()) {
  609. alt_text_builder.append(item->as_string().string_value());
  610. } else {
  611. // TODO: Implement counters
  612. }
  613. }
  614. content_data.alt_text = MUST(alt_text_builder.to_string());
  615. }
  616. return { content_data, quote_nesting_level };
  617. }
  618. switch (value->to_identifier()) {
  619. case ValueID::None:
  620. return { { ContentData::Type::None }, quote_nesting_level };
  621. case ValueID::Normal:
  622. return { { ContentData::Type::Normal }, quote_nesting_level };
  623. default:
  624. break;
  625. }
  626. return { {}, quote_nesting_level };
  627. }
  628. Optional<CSS::Cursor> StyleProperties::cursor() const
  629. {
  630. auto value = property(CSS::PropertyID::Cursor);
  631. return value_id_to_cursor(value->to_identifier());
  632. }
  633. Optional<CSS::Visibility> StyleProperties::visibility() const
  634. {
  635. auto value = property(CSS::PropertyID::Visibility);
  636. if (!value->is_identifier())
  637. return {};
  638. return value_id_to_visibility(value->to_identifier());
  639. }
  640. Display StyleProperties::display() const
  641. {
  642. auto value = property(PropertyID::Display);
  643. if (value->is_display()) {
  644. return value->as_display().display();
  645. }
  646. return Display::from_short(Display::Short::Inline);
  647. }
  648. Vector<CSS::TextDecorationLine> StyleProperties::text_decoration_line() const
  649. {
  650. auto value = property(CSS::PropertyID::TextDecorationLine);
  651. if (value->is_value_list()) {
  652. Vector<CSS::TextDecorationLine> lines;
  653. auto& values = value->as_value_list().values();
  654. for (auto const& item : values) {
  655. lines.append(value_id_to_text_decoration_line(item->to_identifier()).value());
  656. }
  657. return lines;
  658. }
  659. if (value->is_identifier() && value->to_identifier() == ValueID::None)
  660. return {};
  661. dbgln("FIXME: Unsupported value for text-decoration-line: {}", value->to_string());
  662. return {};
  663. }
  664. Optional<CSS::TextDecorationStyle> StyleProperties::text_decoration_style() const
  665. {
  666. auto value = property(CSS::PropertyID::TextDecorationStyle);
  667. return value_id_to_text_decoration_style(value->to_identifier());
  668. }
  669. Optional<CSS::TextTransform> StyleProperties::text_transform() const
  670. {
  671. auto value = property(CSS::PropertyID::TextTransform);
  672. return value_id_to_text_transform(value->to_identifier());
  673. }
  674. Optional<CSS::ListStyleType> StyleProperties::list_style_type() const
  675. {
  676. auto value = property(CSS::PropertyID::ListStyleType);
  677. return value_id_to_list_style_type(value->to_identifier());
  678. }
  679. Optional<CSS::ListStylePosition> StyleProperties::list_style_position() const
  680. {
  681. auto value = property(CSS::PropertyID::ListStylePosition);
  682. return value_id_to_list_style_position(value->to_identifier());
  683. }
  684. Optional<CSS::Overflow> StyleProperties::overflow_x() const
  685. {
  686. return overflow(CSS::PropertyID::OverflowX);
  687. }
  688. Optional<CSS::Overflow> StyleProperties::overflow_y() const
  689. {
  690. return overflow(CSS::PropertyID::OverflowY);
  691. }
  692. Optional<CSS::Overflow> StyleProperties::overflow(CSS::PropertyID property_id) const
  693. {
  694. auto value = property(property_id);
  695. return value_id_to_overflow(value->to_identifier());
  696. }
  697. Vector<ShadowData> StyleProperties::shadow(PropertyID property_id, Layout::Node const& layout_node) const
  698. {
  699. auto value = property(property_id);
  700. auto resolve_to_length = [&layout_node](NonnullRefPtr<StyleValue const> const& value) -> Optional<Length> {
  701. if (value->is_length())
  702. return value->as_length().length();
  703. if (value->is_calculated())
  704. return value->as_calculated().resolve_length(layout_node);
  705. return {};
  706. };
  707. auto make_shadow_data = [resolve_to_length](ShadowStyleValue const& value) -> Optional<ShadowData> {
  708. auto maybe_offset_x = resolve_to_length(value.offset_x());
  709. if (!maybe_offset_x.has_value())
  710. return {};
  711. auto maybe_offset_y = resolve_to_length(value.offset_y());
  712. if (!maybe_offset_y.has_value())
  713. return {};
  714. auto maybe_blur_radius = resolve_to_length(value.blur_radius());
  715. if (!maybe_blur_radius.has_value())
  716. return {};
  717. auto maybe_spread_distance = resolve_to_length(value.spread_distance());
  718. if (!maybe_spread_distance.has_value())
  719. return {};
  720. return ShadowData {
  721. value.color(),
  722. maybe_offset_x.release_value(),
  723. maybe_offset_y.release_value(),
  724. maybe_blur_radius.release_value(),
  725. maybe_spread_distance.release_value(),
  726. value.placement()
  727. };
  728. };
  729. if (value->is_value_list()) {
  730. auto const& value_list = value->as_value_list();
  731. Vector<ShadowData> shadow_data;
  732. shadow_data.ensure_capacity(value_list.size());
  733. for (auto const& layer_value : value_list.values()) {
  734. auto maybe_shadow_data = make_shadow_data(layer_value->as_shadow());
  735. if (!maybe_shadow_data.has_value())
  736. return {};
  737. shadow_data.append(maybe_shadow_data.release_value());
  738. }
  739. return shadow_data;
  740. }
  741. if (value->is_shadow()) {
  742. auto maybe_shadow_data = make_shadow_data(value->as_shadow());
  743. if (!maybe_shadow_data.has_value())
  744. return {};
  745. return { maybe_shadow_data.release_value() };
  746. }
  747. return {};
  748. }
  749. Vector<ShadowData> StyleProperties::box_shadow(Layout::Node const& layout_node) const
  750. {
  751. return shadow(PropertyID::BoxShadow, layout_node);
  752. }
  753. Vector<ShadowData> StyleProperties::text_shadow(Layout::Node const& layout_node) const
  754. {
  755. return shadow(PropertyID::TextShadow, layout_node);
  756. }
  757. Optional<CSS::BoxSizing> StyleProperties::box_sizing() const
  758. {
  759. auto value = property(CSS::PropertyID::BoxSizing);
  760. return value_id_to_box_sizing(value->to_identifier());
  761. }
  762. Variant<CSS::VerticalAlign, CSS::LengthPercentage> StyleProperties::vertical_align() const
  763. {
  764. auto value = property(CSS::PropertyID::VerticalAlign);
  765. if (value->is_identifier())
  766. return value_id_to_vertical_align(value->to_identifier()).release_value();
  767. if (value->is_length())
  768. return CSS::LengthPercentage(value->as_length().length());
  769. if (value->is_percentage())
  770. return CSS::LengthPercentage(value->as_percentage().percentage());
  771. if (value->is_calculated())
  772. return LengthPercentage { const_cast<CalculatedStyleValue&>(value->as_calculated()) };
  773. VERIFY_NOT_REACHED();
  774. }
  775. Optional<CSS::FontVariant> StyleProperties::font_variant() const
  776. {
  777. auto value = property(CSS::PropertyID::FontVariant);
  778. return value_id_to_font_variant(value->to_identifier());
  779. }
  780. CSS::GridTrackSizeList StyleProperties::grid_auto_columns() const
  781. {
  782. auto value = property(CSS::PropertyID::GridAutoColumns);
  783. return value->as_grid_track_size_list().grid_track_size_list();
  784. }
  785. CSS::GridTrackSizeList StyleProperties::grid_auto_rows() const
  786. {
  787. auto value = property(CSS::PropertyID::GridAutoRows);
  788. return value->as_grid_track_size_list().grid_track_size_list();
  789. }
  790. CSS::GridTrackSizeList StyleProperties::grid_template_columns() const
  791. {
  792. auto value = property(CSS::PropertyID::GridTemplateColumns);
  793. return value->as_grid_track_size_list().grid_track_size_list();
  794. }
  795. CSS::GridTrackSizeList StyleProperties::grid_template_rows() const
  796. {
  797. auto value = property(CSS::PropertyID::GridTemplateRows);
  798. return value->as_grid_track_size_list().grid_track_size_list();
  799. }
  800. CSS::GridAutoFlow StyleProperties::grid_auto_flow() const
  801. {
  802. auto value = property(CSS::PropertyID::GridAutoFlow);
  803. if (!value->is_grid_auto_flow())
  804. return CSS::GridAutoFlow {};
  805. auto& grid_auto_flow_value = value->as_grid_auto_flow();
  806. return CSS::GridAutoFlow { .row = grid_auto_flow_value.is_row(), .dense = grid_auto_flow_value.is_dense() };
  807. }
  808. CSS::GridTrackPlacement StyleProperties::grid_column_end() const
  809. {
  810. auto value = property(CSS::PropertyID::GridColumnEnd);
  811. return value->as_grid_track_placement().grid_track_placement();
  812. }
  813. CSS::GridTrackPlacement StyleProperties::grid_column_start() const
  814. {
  815. auto value = property(CSS::PropertyID::GridColumnStart);
  816. return value->as_grid_track_placement().grid_track_placement();
  817. }
  818. CSS::GridTrackPlacement StyleProperties::grid_row_end() const
  819. {
  820. auto value = property(CSS::PropertyID::GridRowEnd);
  821. return value->as_grid_track_placement().grid_track_placement();
  822. }
  823. CSS::GridTrackPlacement StyleProperties::grid_row_start() const
  824. {
  825. auto value = property(CSS::PropertyID::GridRowStart);
  826. return value->as_grid_track_placement().grid_track_placement();
  827. }
  828. Optional<CSS::BorderCollapse> StyleProperties::border_collapse() const
  829. {
  830. auto value = property(CSS::PropertyID::BorderCollapse);
  831. return value_id_to_border_collapse(value->to_identifier());
  832. }
  833. Vector<Vector<String>> StyleProperties::grid_template_areas() const
  834. {
  835. auto value = property(CSS::PropertyID::GridTemplateAreas);
  836. return value->as_grid_template_area().grid_template_area();
  837. }
  838. String StyleProperties::grid_area() const
  839. {
  840. auto value = property(CSS::PropertyID::GridArea);
  841. return value->as_string().string_value();
  842. }
  843. Optional<CSS::ObjectFit> StyleProperties::object_fit() const
  844. {
  845. auto value = property(CSS::PropertyID::ObjectFit);
  846. return value_id_to_object_fit(value->to_identifier());
  847. }
  848. CSS::PositionStyleValue const& StyleProperties::object_position() const
  849. {
  850. auto value = property(CSS::PropertyID::ObjectPosition);
  851. return value->as_position();
  852. }
  853. Optional<CSS::TableLayout> StyleProperties::table_layout() const
  854. {
  855. auto value = property(CSS::PropertyID::TableLayout);
  856. return value_id_to_table_layout(value->to_identifier());
  857. }
  858. Optional<CSS::MaskType> StyleProperties::mask_type() const
  859. {
  860. auto value = property(CSS::PropertyID::MaskType);
  861. return value_id_to_mask_type(value->to_identifier());
  862. }
  863. Color StyleProperties::stop_color() const
  864. {
  865. auto value = property(CSS::PropertyID::StopColor);
  866. if (value->is_identifier()) {
  867. // Workaround lack of layout node to resolve current color.
  868. auto& ident = value->as_identifier();
  869. if (ident.id() == CSS::ValueID::Currentcolor)
  870. value = property(CSS::PropertyID::Color);
  871. }
  872. if (value->has_color()) {
  873. // FIXME: This is used by the SVGStopElement, which does not participate in layout,
  874. // so can't pass a layout node (so can't resolve some colors, e.g. palette ones)
  875. return value->to_color({});
  876. }
  877. return Color::Black;
  878. }
  879. void StyleProperties::set_math_depth(int math_depth)
  880. {
  881. m_math_depth = math_depth;
  882. // Make our children inherit our computed value, not our specified value.
  883. set_property(PropertyID::MathDepth, MathDepthStyleValue::create_integer(IntegerStyleValue::create(math_depth)));
  884. }
  885. QuotesData StyleProperties::quotes() const
  886. {
  887. auto value = property(CSS::PropertyID::Quotes);
  888. if (value->is_identifier()) {
  889. switch (value->to_identifier()) {
  890. case ValueID::Auto:
  891. return QuotesData { .type = QuotesData::Type::Auto };
  892. case ValueID::None:
  893. return QuotesData { .type = QuotesData::Type::None };
  894. default:
  895. break;
  896. }
  897. }
  898. if (value->is_value_list()) {
  899. auto& value_list = value->as_value_list();
  900. QuotesData quotes_data { .type = QuotesData::Type::Specified };
  901. VERIFY(value_list.size() % 2 == 0);
  902. for (auto i = 0u; i < value_list.size(); i += 2) {
  903. quotes_data.strings.empend(
  904. value_list.value_at(i, false)->as_string().string_value(),
  905. value_list.value_at(i + 1, false)->as_string().string_value());
  906. }
  907. return quotes_data;
  908. }
  909. return InitialValues::quotes();
  910. }
  911. }