GenerateCSSPropertyID.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. /*
  2. * Copyright (c) 2018-2020, 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 "GeneratorUtil.h"
  8. #include <AK/GenericShorthands.h>
  9. #include <AK/SourceGenerator.h>
  10. #include <AK/StringBuilder.h>
  11. #include <LibCore/ArgsParser.h>
  12. #include <LibMain/Main.h>
  13. ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file);
  14. ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file);
  15. static bool type_name_is_enum(StringView type_name)
  16. {
  17. return !AK::first_is_one_of(type_name, "angle"sv, "color"sv, "frequency"sv, "image"sv, "integer"sv, "length"sv, "number"sv, "percentage"sv, "rect"sv, "resolution"sv, "string"sv, "time"sv, "url"sv);
  18. }
  19. ErrorOr<int> serenity_main(Main::Arguments arguments)
  20. {
  21. StringView generated_header_path;
  22. StringView generated_implementation_path;
  23. StringView properties_json_path;
  24. Core::ArgsParser args_parser;
  25. args_parser.add_option(generated_header_path, "Path to the PropertyID header file to generate", "generated-header-path", 'h', "generated-header-path");
  26. args_parser.add_option(generated_implementation_path, "Path to the PropertyID implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
  27. args_parser.add_option(properties_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
  28. args_parser.parse(arguments);
  29. auto json = TRY(read_entire_file_as_json(properties_json_path));
  30. VERIFY(json.is_object());
  31. auto properties = json.as_object();
  32. auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
  33. auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
  34. TRY(generate_header_file(properties, *generated_header_file));
  35. TRY(generate_implementation_file(properties, *generated_implementation_file));
  36. return 0;
  37. }
  38. ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file)
  39. {
  40. StringBuilder builder;
  41. SourceGenerator generator { builder };
  42. generator.append(R"~~~(
  43. #pragma once
  44. #include <AK/NonnullRefPtr.h>
  45. #include <AK/StringView.h>
  46. #include <AK/Traits.h>
  47. #include <LibJS/Forward.h>
  48. #include <LibWeb/Forward.h>
  49. namespace Web::CSS {
  50. enum class PropertyID {
  51. Invalid,
  52. Custom,
  53. )~~~");
  54. Vector<DeprecatedString> shorthand_property_ids;
  55. Vector<DeprecatedString> longhand_property_ids;
  56. properties.for_each_member([&](auto& name, auto& value) {
  57. VERIFY(value.is_object());
  58. if (value.as_object().has("longhands"sv))
  59. shorthand_property_ids.append(name);
  60. else
  61. longhand_property_ids.append(name);
  62. });
  63. auto first_property_id = shorthand_property_ids.first();
  64. auto last_property_id = longhand_property_ids.last();
  65. for (auto& name : shorthand_property_ids) {
  66. auto member_generator = generator.fork();
  67. member_generator.set("name:titlecase", title_casify(name));
  68. member_generator.append(R"~~~(
  69. @name:titlecase@,
  70. )~~~");
  71. }
  72. for (auto& name : longhand_property_ids) {
  73. auto member_generator = generator.fork();
  74. member_generator.set("name:titlecase", title_casify(name));
  75. member_generator.append(R"~~~(
  76. @name:titlecase@,
  77. )~~~");
  78. }
  79. generator.set("first_property_id", title_casify(first_property_id));
  80. generator.set("last_property_id", title_casify(last_property_id));
  81. generator.set("first_shorthand_property_id", title_casify(shorthand_property_ids.first()));
  82. generator.set("last_shorthand_property_id", title_casify(shorthand_property_ids.last()));
  83. generator.set("first_longhand_property_id", title_casify(longhand_property_ids.first()));
  84. generator.set("last_longhand_property_id", title_casify(longhand_property_ids.last()));
  85. generator.append(R"~~~(
  86. };
  87. PropertyID property_id_from_camel_case_string(StringView);
  88. PropertyID property_id_from_string(StringView);
  89. StringView string_from_property_id(PropertyID);
  90. bool is_inherited_property(PropertyID);
  91. ErrorOr<NonnullRefPtr<StyleValue>> property_initial_value(JS::Realm&, PropertyID);
  92. enum class ValueType {
  93. Angle,
  94. Color,
  95. FilterValueList,
  96. Frequency,
  97. Image,
  98. Integer,
  99. Length,
  100. Number,
  101. Paint,
  102. Percentage,
  103. Position,
  104. Rect,
  105. Resolution,
  106. String,
  107. Time,
  108. Url,
  109. };
  110. bool property_accepts_type(PropertyID, ValueType);
  111. bool property_accepts_identifier(PropertyID, ValueID);
  112. bool property_accepts_value(PropertyID, StyleValue&);
  113. size_t property_maximum_value_count(PropertyID);
  114. bool property_affects_layout(PropertyID);
  115. bool property_affects_stacking_context(PropertyID);
  116. constexpr PropertyID first_property_id = PropertyID::@first_property_id@;
  117. constexpr PropertyID last_property_id = PropertyID::@last_property_id@;
  118. constexpr PropertyID first_shorthand_property_id = PropertyID::@first_shorthand_property_id@;
  119. constexpr PropertyID last_shorthand_property_id = PropertyID::@last_shorthand_property_id@;
  120. constexpr PropertyID first_longhand_property_id = PropertyID::@first_longhand_property_id@;
  121. constexpr PropertyID last_longhand_property_id = PropertyID::@last_longhand_property_id@;
  122. enum class Quirk {
  123. // https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
  124. HashlessHexColor,
  125. // https://quirks.spec.whatwg.org/#the-unitless-length-quirk
  126. UnitlessLength,
  127. };
  128. bool property_has_quirk(PropertyID, Quirk);
  129. } // namespace Web::CSS
  130. namespace AK {
  131. template<>
  132. struct Traits<Web::CSS::PropertyID> : public GenericTraits<Web::CSS::PropertyID> {
  133. static unsigned hash(Web::CSS::PropertyID property_id) { return int_hash((unsigned)property_id); }
  134. };
  135. } // namespace AK
  136. )~~~");
  137. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  138. return {};
  139. }
  140. ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file)
  141. {
  142. StringBuilder builder;
  143. SourceGenerator generator { builder };
  144. generator.append(R"~~~(
  145. #include <AK/Assertions.h>
  146. #include <LibWeb/CSS/Enums.h>
  147. #include <LibWeb/CSS/Parser/Parser.h>
  148. #include <LibWeb/CSS/PropertyID.h>
  149. #include <LibWeb/CSS/StyleValue.h>
  150. #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
  151. #include <LibWeb/Infra/Strings.h>
  152. namespace Web::CSS {
  153. PropertyID property_id_from_camel_case_string(StringView string)
  154. {
  155. )~~~");
  156. properties.for_each_member([&](auto& name, auto& value) {
  157. VERIFY(value.is_object());
  158. auto member_generator = generator.fork();
  159. member_generator.set("name", name);
  160. member_generator.set("name:titlecase", title_casify(name));
  161. member_generator.set("name:camelcase", camel_casify(name));
  162. member_generator.append(R"~~~(
  163. if (string.equals_ignoring_ascii_case("@name:camelcase@"sv))
  164. return PropertyID::@name:titlecase@;
  165. )~~~");
  166. });
  167. generator.append(R"~~~(
  168. return PropertyID::Invalid;
  169. }
  170. PropertyID property_id_from_string(StringView string)
  171. {
  172. )~~~");
  173. properties.for_each_member([&](auto& name, auto& value) {
  174. VERIFY(value.is_object());
  175. auto member_generator = generator.fork();
  176. member_generator.set("name", name);
  177. member_generator.set("name:titlecase", title_casify(name));
  178. member_generator.append(R"~~~(
  179. if (Infra::is_ascii_case_insensitive_match(string, "@name@"sv))
  180. return PropertyID::@name:titlecase@;
  181. )~~~");
  182. });
  183. generator.append(R"~~~(
  184. return PropertyID::Invalid;
  185. }
  186. StringView string_from_property_id(PropertyID property_id) {
  187. switch (property_id) {
  188. )~~~");
  189. properties.for_each_member([&](auto& name, auto& value) {
  190. VERIFY(value.is_object());
  191. auto member_generator = generator.fork();
  192. member_generator.set("name", name);
  193. member_generator.set("name:titlecase", title_casify(name));
  194. member_generator.append(R"~~~(
  195. case PropertyID::@name:titlecase@:
  196. return "@name@"sv;
  197. )~~~");
  198. });
  199. generator.append(R"~~~(
  200. default:
  201. return "(invalid CSS::PropertyID)"sv;
  202. }
  203. }
  204. bool is_inherited_property(PropertyID property_id)
  205. {
  206. switch (property_id) {
  207. )~~~");
  208. properties.for_each_member([&](auto& name, auto& value) {
  209. VERIFY(value.is_object());
  210. bool inherited = false;
  211. if (value.as_object().has("inherited"sv)) {
  212. auto inherited_value = value.as_object().get_bool("inherited"sv);
  213. VERIFY(inherited_value.has_value());
  214. inherited = inherited_value.value();
  215. }
  216. if (inherited) {
  217. auto member_generator = generator.fork();
  218. member_generator.set("name:titlecase", title_casify(name));
  219. member_generator.append(R"~~~(
  220. case PropertyID::@name:titlecase@:
  221. return true;
  222. )~~~");
  223. }
  224. });
  225. generator.append(R"~~~(
  226. default:
  227. return false;
  228. }
  229. }
  230. bool property_affects_layout(PropertyID property_id)
  231. {
  232. switch (property_id) {
  233. )~~~");
  234. properties.for_each_member([&](auto& name, auto& value) {
  235. VERIFY(value.is_object());
  236. bool affects_layout = true;
  237. if (value.as_object().has("affects-layout"sv))
  238. affects_layout = value.as_object().get_bool("affects-layout"sv).value_or(false);
  239. if (affects_layout) {
  240. auto member_generator = generator.fork();
  241. member_generator.set("name:titlecase", title_casify(name));
  242. member_generator.append(R"~~~(
  243. case PropertyID::@name:titlecase@:
  244. )~~~");
  245. }
  246. });
  247. generator.append(R"~~~(
  248. return true;
  249. default:
  250. return false;
  251. }
  252. }
  253. bool property_affects_stacking_context(PropertyID property_id)
  254. {
  255. switch (property_id) {
  256. )~~~");
  257. properties.for_each_member([&](auto& name, auto& value) {
  258. VERIFY(value.is_object());
  259. bool affects_stacking_context = false;
  260. if (value.as_object().has("affects-stacking-context"sv))
  261. affects_stacking_context = value.as_object().get_bool("affects-stacking-context"sv).value_or(false);
  262. if (affects_stacking_context) {
  263. auto member_generator = generator.fork();
  264. member_generator.set("name:titlecase", title_casify(name));
  265. member_generator.append(R"~~~(
  266. case PropertyID::@name:titlecase@:
  267. )~~~");
  268. }
  269. });
  270. generator.append(R"~~~(
  271. return true;
  272. default:
  273. return false;
  274. }
  275. }
  276. ErrorOr<NonnullRefPtr<StyleValue>> property_initial_value(JS::Realm& context_realm, PropertyID property_id)
  277. {
  278. static Array<RefPtr<StyleValue>, to_underlying(last_property_id) + 1> initial_values;
  279. if (auto initial_value = initial_values[to_underlying(property_id)])
  280. return initial_value.release_nonnull();
  281. // Lazily parse initial values as needed.
  282. // This ensures the shorthands will always be able to get the initial values of their longhands.
  283. // This also now allows a longhand have its own longhand (like background-position-x).
  284. Parser::ParsingContext parsing_context(context_realm);
  285. switch (property_id) {
  286. )~~~");
  287. auto output_initial_value_code = [&](auto& name, auto& object) {
  288. if (!object.has("initial"sv)) {
  289. dbgln("No initial value specified for property '{}'", name);
  290. VERIFY_NOT_REACHED();
  291. }
  292. auto initial_value = object.get_deprecated_string("initial"sv);
  293. VERIFY(initial_value.has_value());
  294. auto& initial_value_string = initial_value.value();
  295. auto member_generator = generator.fork();
  296. member_generator.set("name:titlecase", title_casify(name));
  297. member_generator.set("initial_value_string", initial_value_string);
  298. member_generator.append(
  299. R"~~~( case PropertyID::@name:titlecase@:
  300. {
  301. auto parsed_value = TRY(parse_css_value(parsing_context, "@initial_value_string@"sv, PropertyID::@name:titlecase@));
  302. VERIFY(!parsed_value.is_null());
  303. auto initial_value = parsed_value.release_nonnull();
  304. initial_values[to_underlying(PropertyID::@name:titlecase@)] = initial_value;
  305. return initial_value;
  306. }
  307. )~~~");
  308. };
  309. properties.for_each_member([&](auto& name, auto& value) {
  310. VERIFY(value.is_object());
  311. output_initial_value_code(name, value.as_object());
  312. });
  313. generator.append(
  314. R"~~~( default: VERIFY_NOT_REACHED();
  315. }
  316. VERIFY_NOT_REACHED();
  317. }
  318. bool property_has_quirk(PropertyID property_id, Quirk quirk)
  319. {
  320. switch (property_id) {
  321. )~~~");
  322. properties.for_each_member([&](auto& name, auto& value) {
  323. VERIFY(value.is_object());
  324. if (value.as_object().has("quirks"sv)) {
  325. auto quirks_value = value.as_object().get_array("quirks"sv);
  326. VERIFY(quirks_value.has_value());
  327. auto& quirks = quirks_value.value();
  328. if (!quirks.is_empty()) {
  329. auto property_generator = generator.fork();
  330. property_generator.set("name:titlecase", title_casify(name));
  331. property_generator.append(R"~~~(
  332. case PropertyID::@name:titlecase@: {
  333. switch (quirk) {
  334. )~~~");
  335. for (auto& quirk : quirks.values()) {
  336. VERIFY(quirk.is_string());
  337. auto quirk_generator = property_generator.fork();
  338. quirk_generator.set("quirk:titlecase", title_casify(quirk.as_string()));
  339. quirk_generator.append(R"~~~(
  340. case Quirk::@quirk:titlecase@:
  341. return true;
  342. )~~~");
  343. }
  344. property_generator.append(R"~~~(
  345. default:
  346. return false;
  347. }
  348. }
  349. )~~~");
  350. }
  351. }
  352. });
  353. generator.append(R"~~~(
  354. default:
  355. return false;
  356. }
  357. }
  358. bool property_accepts_type(PropertyID property_id, ValueType value_type)
  359. {
  360. switch (property_id) {
  361. )~~~");
  362. properties.for_each_member([&](auto& name, auto& value) {
  363. VERIFY(value.is_object());
  364. auto& object = value.as_object();
  365. if (auto maybe_valid_types = object.get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  366. auto& valid_types = maybe_valid_types.value();
  367. auto property_generator = generator.fork();
  368. property_generator.set("name:titlecase", title_casify(name));
  369. property_generator.append(R"~~~(
  370. case PropertyID::@name:titlecase@: {
  371. switch (value_type) {
  372. )~~~");
  373. bool did_output_accepted_type = false;
  374. for (auto& type : valid_types.values()) {
  375. VERIFY(type.is_string());
  376. auto type_name = type.as_string().split_view(' ').first();
  377. if (type_name_is_enum(type_name))
  378. continue;
  379. if (type_name == "angle") {
  380. property_generator.appendln(" case ValueType::Angle:");
  381. } else if (type_name == "color") {
  382. property_generator.appendln(" case ValueType::Color:");
  383. } else if (type_name == "frequency") {
  384. property_generator.appendln(" case ValueType::Frequency:");
  385. } else if (type_name == "image") {
  386. property_generator.appendln(" case ValueType::Image:");
  387. } else if (type_name == "integer") {
  388. property_generator.appendln(" case ValueType::Integer:");
  389. } else if (type_name == "length") {
  390. property_generator.appendln(" case ValueType::Length:");
  391. } else if (type_name == "number") {
  392. property_generator.appendln(" case ValueType::Number:");
  393. } else if (type_name == "percentage") {
  394. property_generator.appendln(" case ValueType::Percentage:");
  395. } else if (type_name == "rect") {
  396. property_generator.appendln(" case ValueType::Rect:");
  397. } else if (type_name == "resolution") {
  398. property_generator.appendln(" case ValueType::Resolution:");
  399. } else if (type_name == "string") {
  400. property_generator.appendln(" case ValueType::String:");
  401. } else if (type_name == "time") {
  402. property_generator.appendln(" case ValueType::Time:");
  403. } else if (type_name == "url") {
  404. property_generator.appendln(" case ValueType::Url:");
  405. } else {
  406. VERIFY_NOT_REACHED();
  407. }
  408. did_output_accepted_type = true;
  409. }
  410. if (did_output_accepted_type)
  411. property_generator.appendln(" return true;");
  412. property_generator.append(R"~~~(
  413. default:
  414. return false;
  415. }
  416. }
  417. )~~~");
  418. }
  419. });
  420. generator.append(R"~~~(
  421. default:
  422. return false;
  423. }
  424. }
  425. bool property_accepts_identifier(PropertyID property_id, ValueID identifier)
  426. {
  427. switch (property_id) {
  428. )~~~");
  429. properties.for_each_member([&](auto& name, auto& value) {
  430. VERIFY(value.is_object());
  431. auto& object = value.as_object();
  432. auto property_generator = generator.fork();
  433. property_generator.set("name:titlecase", title_casify(name));
  434. property_generator.appendln(" case PropertyID::@name:titlecase@: {");
  435. if (auto maybe_valid_identifiers = object.get_array("valid-identifiers"sv); maybe_valid_identifiers.has_value() && !maybe_valid_identifiers->is_empty()) {
  436. property_generator.appendln(" switch (identifier) {");
  437. auto& valid_identifiers = maybe_valid_identifiers.value();
  438. for (auto& identifier : valid_identifiers.values()) {
  439. auto identifier_generator = generator.fork();
  440. identifier_generator.set("identifier:titlecase", title_casify(identifier.as_string()));
  441. identifier_generator.appendln(" case ValueID::@identifier:titlecase@:");
  442. }
  443. property_generator.append(R"~~~(
  444. return true;
  445. default:
  446. break;
  447. }
  448. )~~~");
  449. }
  450. if (auto maybe_valid_types = object.get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  451. auto& valid_types = maybe_valid_types.value();
  452. for (auto& valid_type : valid_types.values()) {
  453. auto type_name = valid_type.as_string().split_view(' ').first();
  454. if (!type_name_is_enum(type_name))
  455. continue;
  456. auto type_generator = generator.fork();
  457. type_generator.set("type_name:snakecase", snake_casify(type_name));
  458. type_generator.append(R"~~~(
  459. if (value_id_to_@type_name:snakecase@(identifier).has_value())
  460. return true;
  461. )~~~");
  462. }
  463. }
  464. property_generator.append(R"~~~(
  465. return false;
  466. }
  467. )~~~");
  468. });
  469. generator.append(R"~~~(
  470. default:
  471. return false;
  472. }
  473. }
  474. bool property_accepts_value(PropertyID property_id, StyleValue& style_value)
  475. {
  476. if (style_value.is_builtin())
  477. return true;
  478. switch (property_id) {
  479. )~~~");
  480. properties.for_each_member([&](auto& name, auto& value) {
  481. VERIFY(value.is_object());
  482. auto& object = value.as_object();
  483. bool has_valid_types = object.has("valid-types"sv);
  484. auto has_valid_identifiers = object.has("valid-identifiers"sv);
  485. if (has_valid_types || has_valid_identifiers) {
  486. auto property_generator = generator.fork();
  487. property_generator.set("name:titlecase", title_casify(name));
  488. property_generator.append(R"~~~(
  489. case PropertyID::@name:titlecase@: {
  490. )~~~");
  491. auto output_numeric_value_check = [](SourceGenerator& generator, StringView type_check_function, StringView value_getter, Span<StringView> resolved_type_names, StringView min_value, StringView max_value) {
  492. auto test_generator = generator.fork();
  493. test_generator.set("type_check_function", type_check_function);
  494. test_generator.set("value_getter", value_getter);
  495. test_generator.append(R"~~~(
  496. if ((style_value.@type_check_function@())~~~");
  497. if (!min_value.is_empty() && min_value != "-∞") {
  498. test_generator.set("minvalue", min_value);
  499. test_generator.append(" && (style_value.@value_getter@ >= @minvalue@)");
  500. }
  501. if (!max_value.is_empty() && max_value != "∞") {
  502. test_generator.set("maxvalue", max_value);
  503. test_generator.append(" && (style_value.@value_getter@ <= @maxvalue@)");
  504. }
  505. test_generator.append(")");
  506. if (!resolved_type_names.is_empty()) {
  507. test_generator.append(R"~~~(
  508. || (style_value.is_calculated() && ()~~~");
  509. bool first = true;
  510. for (auto& type_name : resolved_type_names) {
  511. test_generator.set("resolved_type_name", type_name);
  512. if (!first)
  513. test_generator.append(" || ");
  514. test_generator.append("style_value.as_calculated().resolved_type() == CalculatedStyleValue::ResolvedType::@resolved_type_name@");
  515. first = false;
  516. }
  517. test_generator.append("))");
  518. }
  519. test_generator.append(R"~~~() {
  520. return true;
  521. }
  522. )~~~");
  523. };
  524. if (has_valid_types) {
  525. auto valid_types_value = object.get_array("valid-types"sv);
  526. VERIFY(valid_types_value.has_value());
  527. auto& valid_types = valid_types_value.value();
  528. if (!valid_types.is_empty()) {
  529. for (auto& type : valid_types.values()) {
  530. VERIFY(type.is_string());
  531. auto type_parts = type.as_string().split_view(' ');
  532. auto type_name = type_parts.first();
  533. auto type_args = type_parts.size() > 1 ? type_parts[1] : ""sv;
  534. StringView min_value;
  535. StringView max_value;
  536. if (!type_args.is_empty()) {
  537. VERIFY(type_args.starts_with('[') && type_args.ends_with(']'));
  538. auto comma_index = type_args.find(',').value();
  539. min_value = type_args.substring_view(1, comma_index - 1);
  540. max_value = type_args.substring_view(comma_index + 1, type_args.length() - comma_index - 2);
  541. }
  542. if (type_name == "angle") {
  543. output_numeric_value_check(property_generator, "is_angle"sv, "as_angle().angle().to_degrees()"sv, Array { "Angle"sv }, min_value, max_value);
  544. } else if (type_name == "color") {
  545. property_generator.append(R"~~~(
  546. if (style_value.has_color())
  547. return true;
  548. )~~~");
  549. } else if (type_name == "filter-value-list") {
  550. property_generator.append(R"~~~(
  551. if (style_value.is_filter_value_list())
  552. return true;
  553. )~~~");
  554. } else if (type_name == "frequency") {
  555. output_numeric_value_check(property_generator, "is_frequency"sv, "as_frequency().frequency().to_hertz()"sv, Array { "Frequency"sv }, min_value, max_value);
  556. } else if (type_name == "image") {
  557. property_generator.append(R"~~~(
  558. if (style_value.is_abstract_image())
  559. return true;
  560. )~~~");
  561. } else if (type_name == "integer") {
  562. output_numeric_value_check(property_generator, "has_integer"sv, "to_integer()"sv, Array { "Integer"sv }, min_value, max_value);
  563. } else if (type_name == "length") {
  564. output_numeric_value_check(property_generator, "has_length"sv, "to_length().raw_value()"sv, Array { "Length"sv }, min_value, max_value);
  565. } else if (type_name == "number") {
  566. output_numeric_value_check(property_generator, "has_number"sv, "to_number()"sv, Array { "Integer"sv, "Number"sv }, min_value, max_value);
  567. } else if (type_name == "percentage") {
  568. output_numeric_value_check(property_generator, "is_percentage"sv, "as_percentage().percentage().value()"sv, Array { "Percentage"sv }, min_value, max_value);
  569. } else if (type_name == "rect") {
  570. property_generator.append(R"~~~(
  571. if (style_value.has_rect())
  572. return true;
  573. )~~~");
  574. } else if (type_name == "resolution") {
  575. output_numeric_value_check(property_generator, "is_resolution"sv, "as_resolution().resolution().to_dots_per_pixel()"sv, Array<StringView, 0> {}, min_value, max_value);
  576. } else if (type_name == "string") {
  577. property_generator.append(R"~~~(
  578. if (style_value.is_string())
  579. return true;
  580. )~~~");
  581. } else if (type_name == "time") {
  582. output_numeric_value_check(property_generator, "is_time"sv, "as_time().time().to_seconds()"sv, Array { "Time"sv }, min_value, max_value);
  583. } else if (type_name == "url") {
  584. // FIXME: Handle urls!
  585. } else if (type_name == "paint") {
  586. // https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint
  587. property_generator.append(R"~~~(
  588. if (style_value.has_color() || style_value.is_url())
  589. return true;
  590. )~~~");
  591. } else {
  592. // Assume that any other type names are defined in Enums.json.
  593. // If they're not, the output won't compile, but that's fine since it's invalid.
  594. property_generator.set("type_name:snakecase", snake_casify(type_name));
  595. property_generator.append(R"~~~(
  596. if (auto converted_identifier = value_id_to_@type_name:snakecase@(style_value.to_identifier()); converted_identifier.has_value())
  597. return true;
  598. )~~~");
  599. }
  600. }
  601. }
  602. }
  603. if (has_valid_identifiers) {
  604. auto valid_identifiers_value = object.get_array("valid-identifiers"sv);
  605. VERIFY(valid_identifiers_value.has_value());
  606. auto& valid_identifiers = valid_identifiers_value.value();
  607. if (!valid_identifiers.is_empty()) {
  608. property_generator.append(R"~~~(
  609. switch (style_value.to_identifier()) {
  610. )~~~");
  611. for (auto& identifier : valid_identifiers.values()) {
  612. VERIFY(identifier.is_string());
  613. auto identifier_generator = generator.fork();
  614. identifier_generator.set("identifier:titlecase", title_casify(identifier.as_string()));
  615. identifier_generator.append(R"~~~(
  616. case ValueID::@identifier:titlecase@:
  617. )~~~");
  618. }
  619. property_generator.append(R"~~~(
  620. return true;
  621. default:
  622. break;
  623. }
  624. )~~~");
  625. }
  626. }
  627. generator.append(R"~~~(
  628. return false;
  629. }
  630. )~~~");
  631. }
  632. });
  633. generator.append(R"~~~(
  634. default:
  635. return true;
  636. }
  637. }
  638. size_t property_maximum_value_count(PropertyID property_id)
  639. {
  640. switch (property_id) {
  641. )~~~");
  642. properties.for_each_member([&](auto& name, auto& value) {
  643. VERIFY(value.is_object());
  644. if (value.as_object().has("max-values"sv)) {
  645. auto max_values = value.as_object().get("max-values"sv);
  646. VERIFY(max_values.has_value() && max_values->is_number() && !max_values->is_double());
  647. auto property_generator = generator.fork();
  648. property_generator.set("name:titlecase", title_casify(name));
  649. property_generator.set("max_values", max_values->to_deprecated_string());
  650. property_generator.append(R"~~~(
  651. case PropertyID::@name:titlecase@:
  652. return @max_values@;
  653. )~~~");
  654. }
  655. });
  656. generator.append(R"~~~(
  657. default:
  658. return 1;
  659. }
  660. }
  661. } // namespace Web::CSS
  662. )~~~");
  663. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  664. return {};
  665. }